Story Graph#
A story graph is represented by Nodes which are connected via Edges. Each Node contains a script which consists of Cells. These cells allow to control what happens on the script and to which Node we jump. Therefore a story is represented by a graph.
Graph#
Model graph for the story graph app.#
Engine#
- class story_graph.engine.Engine(graph, stream, raise_exceptions=False, run_cleanup_procedure=None)[source]#
An engine executes a
Graphfor a givenStreamPoint. Executing means to iterate over theNodeand executing eachScriptCellwithin such a node.The engine runs in an async manner so it is possible to do awaits without blocking the server, which means execution is halted until a specific condition is met.
- Parameters:
graph (
Graph) – The graph to executestream (
Stream) – The stream where the graph should be executed onraise_exceptions (
bool) – Decides if an exception within e.g. a Python script cell can bring down the execution or if it ignores it but logs it. Defaults to False so an invalid Python script cell does not stop the whole graph.run_cleanup_procedure (
Optional[bool]) – IfTrueit executesCmdPeriod.runon the SuperCollider server in order to clear all running sounds, patterns and any left running tasks, creating a clean environment. The default isNonewhich will derive the necessary action based if there are already users on the stream (in which case no reset will be executed).
- async execute_audio_cell(audio_cell)[source]#
Plays the associated
AudioFileof anAudioCell.Todo
This does not respect the different Playback formats
- Return type:
- async execute_markdown_code(cell_code)[source]#
Runs the code of a markdown cell by parsing its content with the
GencasterRenderer.
- async execute_node(node, blocking_sleep_time=10000)[source]#
Executes all
ScriptCellof a givenNode.- Return type:
- async execute_python_cell(cell_code)[source]#
Executes a python
ScriptCell. A python cell is run as an async generator, which allows to not just run synchronous code but also asynchronous mode.It is possible to yield immediate results from this. Currently only the yielding of a
Dialoginstance is possible, but this could be extended.In order to secure at least a little bit the execution within such a script cell everything that is a available for execution needs to be stated explicitly here.
- Return type:
- static get_engine_global_vars(runtime_values=None)[source]#
Generates the dictionary which contains all objects which are available for the execution engine of the graph. This acts as a security measurement.
Important
If anything is changed here please execute
make engine-variables-json
which will create an updated autocomplete JSON for the editor.
- Parameters:
runtime_values (
Optional[Dict[str,Any]]) –Allows to add additional objects to the module namespace at runtime. These are injected within
execute_python_cell()and consist ofRuntime vars# key
value
info
looploop
the current asyncio loop - can be used to execute additional async code
varsa dictionary of all stream variables
selfCurrent
Engineinstanceget_stream_variablesCallable
wait_for_stream_variableCallable
- Return type:
- async get_next_node()[source]#
Iterates over each exit
NodeDoorof the current node and evaluates its boolean value and decides.If the node door code consists of invalid code it will be skipped. If all boolean evaluations result in
Falseor invalid code, the default exit will be used.If multiple out-going edges are connected to an active door, a random edge will be picked to follow for the next node.
If the node does not have any out-going edges a
GraphDeadEndexception will be raised.- Return type:
- async get_stream_variables()[source]#
Returns the associated
StreamVariablewithin thisStreamsession.Todo
Could be a @property but this can be difficult in async contexts so we use explicit async via a getter method.
- async start(max_steps=1000)[source]#
Starts the execution of the engine. This method is an async generator which eithor yields a
StreamInstructionor aDialog.Note
In order to avoid a clumping of the database a lay off period of 0.1 seconds is added between jumping nodes.
- Return type:
AsyncGenerator[Union[StreamInstruction,Dialog,GraphDeadEnd],None]
- async wait_for_stream_variable(name, timeout=100.0, update_speed=0.5)[source]#
Waits for a stream variable to be set. If the variable was not found/set within the time period of
timeoutthis function will raise the exceptionScriptCellTimeout.Danger
Within a script cell it is necessary to await this async function
await wait_for_stream_variable('start')
Markdown parser#
A ScriptCell can hold markdown content in our own markdown
dialect to control breaks, change of speakers and emphasis but also allows us to access
variables which are defined within a stream.models.Stream.
In the end this will be transformed into SSML
which will be used for stream.models.TextToSpeech
Choosing markdown as a scripting language has been made because it still can be written easily by humans and treats written text as first class citizen.
The dialect is described in GencasterRenderer.
Use md_to_ssml() to convert markdown text within a Python context.
- class story_graph.markdown_parser.GencasterRenderer(stream_variables=None)[source]#
Acts as a python parser for the Gencaster markdown dialect.
- add_break(text)[source]#
Adds a break between words, see break in GC docs.
Example: Add a break of 300ms between hello and world.
hello {break}`300ms` world- Return type:
- chars(text)[source]#
Speaks surrounded words as characters, so “can” becomes “C A N”, see say as in GC docs.
how {chars}`can` you talk- Return type:
- eval_python(text)[source]#
Execute a python inline script via eval, e.g.
two plus two is {eval_python}`2+2`will result in two plus two is 4.
Eval does not allow for variable assignment but we obtain a return value.
Todo
Store variables in
story_graph.models.GraphSessioncontext.- Return type:
- exec_python(text)[source]#
Executes a Python statement which allows to assign variables.
{exec_python}`a=2` A is now {eval_python}`a`.becomes A is now 2.
See also
Use
var()to access stream variables.- Return type:
- female(text)[source]#
Speaks as
DE_STANDARD_A__FEMALEfromstream.models.TextToSpeech.VoiceNameChoices.hello {female}`world`- Return type:
- male(text)[source]#
Speaks as
DE_STANDARD_B__MALEfromstream.models.TextToSpeech.VoiceNameChoices.hello {male}`world`- Return type:
- moderate(text)[source]#
Speaks surrounded words in a moderate manner, see emphasis in GC docs.
speak {moderate}`something` to me- Return type:
- raw_ssml(text)[source]#
Allows to use raw ssml statements to extend functionality that may not be covered by this parser.
Example:
Hello {raw_ssml}`<emphasis level="moderate">world</emphasis>`- Return type:
- validate_gencaster_tokens(text)[source]#
Validates if the used tags are known to Gencaster
Todo
this is not implemented yet and will raise an exception
- Return type:
- var(text)[source]#
Refers to the value of a
StreamVariable.Example:
Assuming we have set a streaming variable
{"foo": "world"}Hello {var}`foo`becomes Hello World.
If the streaming variable does not exist it will be replaced with an empty string “”, but we can provide a fallback value via
|.Hello {var}`something_unknown|foobar`becomes Hello foobar if the streaming variable
something_unknowndoes not exist.- Return type:
Models#
- class story_graph.models.AudioCell(*args, **kwargs)[source]#
Stores information for playback of static audio files.
- Parameters:
uuid (UUIDField) – Primary key: Uuid
playback (CharField) – Playback
volume (FloatField) – Volume
Relationship fields:
- Parameters:
audio_file (
ForeignKeytoAudioFile) – Audio file (related name:audio_cells)
Reverse relationships:
- Parameters:
script_cell (Reverse
OneToOneFieldfromScriptCell) – The script cell of this audio cell (related name ofaudio_cell)
- class PlaybackChoices(value)[source]#
Different kinds of playback.
Playback types# Name
Description
SYNCPlays back an audio file and waits for the playback to finish before continuing the execution of the script cells.
ASYNCPlays back an audio file and immediately continues the execution of script cells. This is fitting for e.g. background music.
- class story_graph.models.CellType(value)[source]#
A
ScriptCellcan contain different types of code, each with unique functionality.Both, the database and
Engine, implement some specific details according to these types.Cell types# Name
Description
Database
Engine
Markdown
Allows to write arbitrary text which will get rendered as an audio file via a text to speech service, see
TextToSpeechfor conversion andGencasterRendererfor the extended Markdown syntax.Python
Allows to execute python code via
exec()which allows to trigger e.g. Dialogs in the frontend (seeDialog) or calculate or fetch any kind of data and store its value as aStreamVariable.SuperCollider
Executes sclang code on the associated server. This can be used to control the sonic content on the server.
Comment
Does not get executed, but allows to put comments into the graph.
Audio
Allows to playback static audio files. The instruction will be translated into sclang code and will be executed as such on the associated stream.
- class story_graph.models.Edge(*args, **kwargs)[source]#
Connects two
Nodewith each other by using their respectiveNodeDoor.Important
It is important to note that an edge flows from
out_node_doortoin_node_dooras we follow the notion from the perspective of astory_graph.models.Noderather than from the edge.- Parameters:
uuid (UUIDField) – Primary key: Uuid
Relationship fields:
- Parameters:
in_node_door (
ForeignKeytoNodeDoor) – In node door (related name:in_edges)out_node_door (
ForeignKeytoNodeDoor) – Out node door (related name:out_edges)
- class story_graph.models.Graph(*args, **kwargs)[source]#
A collection of
NodeandEdge. This can be considered a score as well as a program as it has an entry point as aNodeand can jump to any otherNode, also allowing for recursive loops/cycles.Each node can be considered a little program on its own which can consist of multiple
ScriptCellwhich can be coded in a variety of languages which can control the frontend and the audio (by e.g. speaking on the stream) or setting a background music.The story graph is a core concept and can be edited with a native editor.
- Parameters:
uuid (UUIDField) – Primary key: Uuid
name (CharField) – Name. Name of the graph
display_name (CharField) – Display name. Will be used as a display name in the frontend
slug_name (SlugField) – Slug name. Will be used as a URL
stream_assignment_policy (CharField) – Stream assignment policy. Manages the stream assignment for this graph
public_visible (BooleanField) – Public visible?. If the graph is not public it will not be listed in the frontend, yet it is still accessible via URL
template_name (CharField) – Frontend template. Allows to switch to a different template in the frontend with different connection flows or UI
start_text (TextField) – Start text (markdown). Text about the graph which will be displayed at the start of a stream - only if this is set
about_text (TextField) – About text (markdown). Text about the graph which can be accessed during a stream - only if this is set
end_text (TextField) – End text (markdown). Text which will be displayed at the end of a stream
Reverse relationships:
- Parameters:
nodes (Reverse
ForeignKeyfromNode) – All nodes of this Graph (related name ofgraph)stream (Reverse
ForeignKeyfromStream) – All Streams of this Graph (related name ofgraph)
- class StreamAssignmentPolicy(value)[source]#
Each graph can handle different “connection” mechanisms when a listener accesses a graph.
The implementation of each policy is defined in
Engine.StreamAssignmentPolicy
Comment
ONE_GRAPH_ONE_STREAM
All users share the same stream. When the first user visits a graph, a new stream will be set up. Any following user visiting the same story graph stream will be “redirected” to the same stream as long as there is still any user listening to the graph. All users still execute the story graph from the beginning.
ONE_USER_ONE_STREAM
Upon connection, each user will obtain a new and exclusive stream and the graph will be executed upon the stream.
DEACTIVATE
Non functional, for administrative work.
- async acreate_entry_node()[source]#
Every graph needs a deterministic, unique entry node which is used to start the iteration over the graph.
The creator of the graph is responsible for calling this method as we can not implicit call it because there are a multitude of ways of creating a Graph (async (asave), sync (save) or in a bulk where we do not have a handle at all).
- Return type:
- class story_graph.models.Node(*args, **kwargs)[source]#
A node.
- Parameters:
uuid (UUIDField) – Primary key: Uuid
name (CharField) – Name. Name of the node
color (CharField) – HEX color of the node in graph canvas
position_x (FloatField) – Position x. x-Position in graph canvas
position_y (FloatField) – Position y. y-Position in graph canvas
is_entry_node (BooleanField) – Is Entry node?. Acts as a singular entrypoint for our graph.Only one such node can exist per graph.
is_blocking_node (BooleanField) – Is blocking node?. If we encounter this node during graph execution we will halt execution indefinitely on this node. This is useful if we have setup a state and do not want to change it anymore.
Relationship fields:
- Parameters:
graph (
ForeignKeytoGraph) – Graph (related name:nodes)
Reverse relationships:
- Parameters:
node_doors (Reverse
ForeignKeyfromNodeDoor) – All node doors of this node (related name ofnode)script_cells (Reverse
ForeignKeyfromScriptCell) – All script cells of this node (related name ofnode)
- save(*args, **kwargs)[source]#
Save the current instance. Override this in a subclass if you want to control the saving process.
The ‘force_insert’ and ‘force_update’ parameters can be used to insist that the “save” must be an SQL insert or update (or equivalent for non-SQL backends), respectively. Normally, they should not be set.
- class story_graph.models.NodeDoor(*args, **kwargs)[source]#
A
Nodecan be entered and exited via multiple paths, where each of these exits and entrances is called a door.A connection between nodes can only be made via their doors. There are two types of doors:
Door types# Kind
Description
INPUT
Allows to enter a node. Currently each Node only has one entry point but for future development and a nicer database operations it is also represented.
OUTPUT
Allows to exit a node. After all script cells of a node has been executed, the condition of each door will be evaluated (like in a switch case). Once a condition has been met, the door will be stepped through. This allows to have a visual representation of logic branches.
It is only possible to connect an OUTPUT to an INPUT door via an
Edge.- Parameters:
uuid (UUIDField) – Primary key: Uuid
door_type (CharField) – Door type
name (CharField) – Name
order (IntegerField) – Order
is_default (BooleanField) – Is default
code (TextField) – Code
Relationship fields:
- Parameters:
node (
ForeignKeytoNode) – Node (related name:node_doors)
Reverse relationships:
- Parameters:
in_edges (Reverse
ForeignKeyfromEdge) – All in edges of this Node door (related name ofin_node_door)out_edges (Reverse
ForeignKeyfromEdge) – All out edges of this Node door (related name ofout_node_door)
- save(*args, **kwargs)[source]#
Save the current instance. Override this in a subclass if you want to control the saving process.
The ‘force_insert’ and ‘force_update’ parameters can be used to insist that the “save” must be an SQL insert or update (or equivalent for non-SQL backends), respectively. Normally, they should not be set.
- exception story_graph.models.NodeDoorMissing[source]#
Exception that can be thrown if a node door is missing. Normally each node should have a default in- and out
NodeDoorvia a signal, but as this is not forced via the database it is necessary to check for it. In case this check fails, this exception can be raised.
- class story_graph.models.ScriptCell(*args, **kwargs)[source]#
Stores a script which can be executed with our
Engineon aStream.- Parameters:
uuid (UUIDField) – Primary key: Uuid
cell_type (CharField) – Cell type
cell_code (TextField) – Cell code
cell_order (IntegerField) – Cell order
Relationship fields:
- Parameters:
node (
ForeignKeytoNode) – Node (related name:script_cells)audio_cell (
OneToOneFieldtoAudioCell) – Audio cell (related name:script_cell)