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#
Engine#
- class story_graph.engine.Engine(graph, stream, raise_exceptions=False, run_cleanup_procedure=None)[source]#
An engine executes a
Graph
for a givenStreamPoint
. Executing means to iterate over theNode
and executing eachScriptCell
within 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
]) – IfTrue
it executesCmdPeriod.run
on the SuperCollider server in order to clear all running sounds, patterns and any left running tasks, creating a clean environment. The default isNone
which 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
AudioFile
of 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
ScriptCell
of 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
Dialog
instance 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 of# key
value
info
loop
loop
the current asyncio loop - can be used to execute additional async code
vars
a dictionary of all stream variables
self
Current
Engine
instanceget_stream_variables
Callable
wait_for_stream_variable
Callable
- Return type:
- async get_next_node()[source]#
Iterates over each exit
NodeDoor
of 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
False
or 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
GraphDeadEnd
exception will be raised.- Return type:
- async get_stream_variables()[source]#
Returns the associated
StreamVariable
within thisStream
session.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
StreamInstruction
or 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
timeout
this 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.GraphSession
context.- 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__FEMALE
fromstream.models.TextToSpeech.VoiceNameChoices
.hello {female}`world`
- Return type:
- male(text)[source]#
Speaks as
DE_STANDARD_B__MALE
fromstream.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_unknown
does 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 (
ForeignKey
toAudioFile
) – Audio file (related name:audio_cells
)
Reverse relationships:
- Parameters:
script_cell (Reverse
OneToOneField
fromScriptCell
) – The script cell of this audio cell (related name ofaudio_cell
)
- class PlaybackChoices(value)[source]#
Different kinds of playback.
# Name
Description
SYNC
Plays back an audio file and waits for the playback to finish before continuing the execution of the script cells.
ASYNC
Plays 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
ScriptCell
can contain different types of code, each with unique functionality.Both, the database and
Engine
, implement some specific details according to these 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
TextToSpeech
for conversion andGencasterRenderer
for 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
Node
with each other by using their respectiveNodeDoor
.Important
It is important to note that an edge flows from
out_node_door
toin_node_door
as we follow the notion from the perspective of astory_graph.models.Node
rather than from the edge.- Parameters:
uuid (UUIDField) – Primary key: Uuid
Relationship fields:
- Parameters:
in_node_door (
ForeignKey
toNodeDoor
) – In node door (related name:in_edges
)out_node_door (
ForeignKey
toNodeDoor
) – Out node door (related name:out_edges
)
- class story_graph.models.Graph(*args, **kwargs)[source]#
A collection of
Node
andEdge
. This can be considered a score as well as a program as it has an entry point as aNode
and 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
ScriptCell
which 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
ForeignKey
fromNode
) – All nodes of this Graph (related name ofgraph
)stream (Reverse
ForeignKey
fromStream
) – 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 (
ForeignKey
toGraph
) – Graph (related name:nodes
)
Reverse relationships:
- Parameters:
node_doors (Reverse
ForeignKey
fromNodeDoor
) – All node doors of this node (related name ofnode
)script_cells (Reverse
ForeignKey
fromScriptCell
) – 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
Node
can 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:
# 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 (
ForeignKey
toNode
) – Node (related name:node_doors
)
Reverse relationships:
- Parameters:
in_edges (Reverse
ForeignKey
fromEdge
) – All in edges of this Node door (related name ofin_node_door
)out_edges (Reverse
ForeignKey
fromEdge
) – 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
NodeDoor
via 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
Engine
on 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 (
ForeignKey
toNode
) – Node (related name:script_cells
)audio_cell (
OneToOneField
toAudioCell
) – Audio cell (related name:script_cell
)