Live Sync
Live Sync connects Godot to the StoryFlow Editor via WebSocket, letting you edit your project and see changes reflected in the engine in real time - no manual re-export and re-import after every change.
Overview
Live Sync establishes a WebSocket connection between the Godot Editor and the StoryFlow Editor running on the same machine. When you save changes in the StoryFlow Editor, those changes are automatically pushed to Godot and re-imported, keeping your in-engine project data up to date without any manual steps.
- Real-time synchronization - edit in StoryFlow, see results in Godot immediately
- WebSocket-based communication using
ws://localhost:9000by default - Automatic re-import of the full project when changes are detected
- Built-in editor dock UI for connecting, disconnecting, and configuring the port
Editor-Only Feature
Live Sync is provided by the editor dock and the StoryFlowWebSocketSync class, which live in the editor/ folder of the plugin. Only the plugin's EditorPlugin creates the dock, so Live Sync functions in the Godot Editor and nowhere else. This is intentional - Live Sync is a development workflow tool, not a runtime feature. Do not exclude addons/storyflow/editor/ from your export preset, though: the runtime autoload loads the importer from that folder. See Exporting Your Game.
Architecture
Live Sync is built from two components that work together to manage the connection and synchronization lifecycle:
- Editor Dock - The UI panel added to the Godot Editor when the plugin is enabled. It provides a port field, Connect, Disconnect and Sync buttons, and a status label showing the current connection state. The dock uses a timer to call
poll()at a 0.1-second interval to process incoming WebSocket messages. - StoryFlowWebSocketSync - The GDScript class that handles the low-level WebSocket connection, reconnection logic, message parsing, and project re-import. It receives incoming project update messages from the StoryFlow Editor and coordinates the re-import process through
StoryFlowImporter.
Setup
Getting Live Sync running takes just a few steps. Make sure both the StoryFlow Editor and Godot Editor are open on the same machine.
Step 1: Open your project in the StoryFlow Editor
The StoryFlow Editor runs a sync server that listens for incoming WebSocket connections. Open the project you want to synchronize - the server starts automatically.
Step 2: Connect from Godot using the editor dock
In the Godot Editor, look for the StoryFlow dock panel. It shows a port field (default: 9000) and a Connect button. Click Connect to establish the WebSocket connection to the StoryFlow Editor running on the same machine.
Step 3: Edit and iterate
From this point on, any changes you save in the StoryFlow Editor are automatically pushed to Godot. The dock's status label updates to show when a sync is in progress or complete. You do not need to manually re-export or re-import - it happens automatically.
First Sync
On initial connection, the editor dock can request a full sync to pull the complete project from the StoryFlow Editor. This ensures your Godot project matches the current state of your story, even if you made changes before connecting.
Connection Controls
The editor dock provides the following controls:
- Port field - The port number for the WebSocket connection. Defaults to
9000. Must match the port configured in the StoryFlow Editor. - Connect button - Establishes the WebSocket connection to the StoryFlow Editor's sync server.
- Disconnect button - Closes the active WebSocket connection and stops any reconnection attempts.
- Sync button - Manually triggers a full re-sync of the project while connected.
- Status label - Shows the current connection state: disconnected, connecting, connected, or syncing.
- Result line - Below the status label, the outcome of the last sync or manual import:
Sync: My Story (12 scripts). Since v1.2.2 it appends, N errors. Check Output log.when any write failed, instead of reporting a clean success. See Failed Writes.
You can also interact with StoryFlowWebSocketSync directly from GDScript if you need programmatic control:
# Programmatic access to WebSocket sync
var sync: StoryFlowWebSocketSync = StoryFlowWebSocketSync.new()
# Connect with default port (9000)
sync.connect_to_editor()
# Connect with custom port
sync.connect_to_editor(9000)
# Check connection status
if sync.is_connected_to_editor():
print("Connected to StoryFlow Editor")
# Request a full project sync
sync.request_sync()
# Set the output directory for imported files
# Set the output directory for imported files. Keep it at res://storyflow:
# the runtime autoload discovers a project only through
# res://storyflow/storyflow_import_meta.json.
sync.set_output_dir("res://storyflow")
# Disconnect
sync.disconnect_from_editor() Signals
StoryFlowWebSocketSync exposes three signals you can connect to for responding to connection and sync events:
-
connected()- Fires when the WebSocket connection to the StoryFlow Editor is successfully established. -
disconnected()- Fires when the connection is lost, whether due to the editor closing, a network issue, or an explicitdisconnect_from_editor()call. -
sync_complete(project: StoryFlowProject, error_count: int)- Fires when a full project sync finishes. The first parameter is the newly imported project resource, ready to be used by yourStoryFlowComponentinstances. The second is the number of non-fatal write failures the import recorded, so a partially failed sync can be told apart from a clean one.
Breaking Change in v1.2.2
sync_complete gained the error_count argument in v1.2.2. A callback connected to it must now take two arguments. A one-argument handler written against an earlier plugin version fails with a signal/callback argument count mismatch.
# Connecting to Live Sync signals
func _ready() -> void:
var sync: StoryFlowWebSocketSync = get_sync_instance()
sync.connected.connect(_on_sync_connected)
sync.disconnected.connect(_on_sync_disconnected)
sync.sync_complete.connect(_on_sync_complete)
func _on_sync_connected() -> void:
print("StoryFlow Live Sync connected")
func _on_sync_disconnected() -> void:
print("StoryFlow Live Sync disconnected")
func _on_sync_complete(project: StoryFlowProject, error_count: int) -> void:
print("StoryFlow project synced: ", project.title)
if error_count > 0:
push_warning("Sync finished with %d failed writes" % error_count) How It Works Internally
Understanding the internal flow helps when debugging sync issues or extending the system:
- Connection -
StoryFlowWebSocketSyncestablishes a WebSocket connection to the StoryFlow Editor's sync server. The target is alwaysws://localhoston the configured port; there is no host setting. - Polling - The editor dock runs a timer at a 0.1-second interval that calls
poll()on the sync instance. This processes any incoming WebSocket messages. - Change detection - When you save changes in the StoryFlow Editor, it pushes the updated project data over the WebSocket. The sync class handles
"project-updated"messages (push from editor) and"pong"keep-alive responses. - Re-import - The
project-updatedmessage carries only the project path, soStoryFlowWebSocketSyncpointsStoryFlowImporterat that path'sbuilddirectory. The importer reads the exported files from disk (scripts, characters, assets) and creates or updates the corresponding Godot resources.
Line 200 needs the matching correction: "it pushes the updated project data over the WebSocket" becomes "it pushes the project's path over the WebSocket"., which processes all project files (scripts, characters, assets) and creates or updates the corresponding Godot resources.
- Completion - The
sync_completesignal fires with the newStoryFlowProjectand the import's error count, notifying any listeners that the project has been updated and telling them whether any write failed along the way. - Component update - Active
StoryFlowComponentinstances automatically pick up the updated project through the manager, so running dialogues can reflect the latest changes.
What a Sync Writes
Every sync and every manual import writes into the output directory configured in the dock (res://storyflow by default). Four kinds of file land there:
project.json- The project file the import parsed, published under this name. Added in v1.2.2. This is the file the runtime autoload reads at startup, and publishing it is what lets an exported game find a synced project at all. See Exporting Your Game.- The rest of the build directory - Your script JSON files and anything else the build contains, copied verbatim with their directory structure. Files already published elsewhere in the output directory are not copied a second time.
- Media - Copied by asset type into
images/,audio/andmedia/subdirectories rather than at their build-relative path. storyflow_import_meta.json- The metadata the runtime autoload uses to discover the imported project. It is staged in a sibling temp file and renamed over the target, so a sync interrupted mid-write leaves the previously published metadata intact.
Cleanup After Upgrading
The first sync or import after upgrading to v1.2.2 removes stale leftovers that older plugin versions wrote into the output directory: a duplicate copy of every media file at its build-relative path, and a verbatim project.storyflow. A removed duplicate takes its orphaned .import sidecar with it, and its directory is dropped when that leaves it empty. Files the current sync published are never touched, and the output directory itself is never removed.
Failed Writes
Added in v1.2.2. Failed media copies, build directory copies, directory creation and metadata writes used to push a line into the Output log while the dock still announced a clean success. They are now counted, and the dock's result line appends N errors. Check Output log. after a sync or manual import that failed partway. The Output log holds the detail for each one.
The import's own count travels with the sync_complete signal as its error_count argument, and is available on the importer as get_error_count(). The dock adds its own metadata refresh to that number before showing it. A non-zero count means the imported project is in memory and usable, but at least one file on disk is not what it should be - which usually shows up later as missing media or a project that fails to load on the next launch.
Reconnection
If the WebSocket connection drops unexpectedly - for example, if the StoryFlow Editor is restarted or the connection is interrupted - the sync class automatically attempts to reconnect.
- Up to 5 reconnection attempts are made automatically, one per poll cycle
- Connection state changes are broadcast through the
connected()anddisconnected()signals - If all reconnection attempts fail, the
disconnected()signal fires and you will need to click Connect again in the dock (or callconnect_to_editor()) manually
Monitoring Connection Status
Use is_connected_to_editor() to check the current connection state at any time. The editor dock's status label also updates automatically, so you always know whether Live Sync is active without writing any code.
Data-Only Sync
Added in v1.1.0. When the StoryFlow Editor pushes a sync with Data Only enabled, the editor sends only the script graphs, variables and character data - image and audio files are not copied into the Godot project. The plugin's importer detects the missing source files and transparently reuses the existing imported assets in your project instead of failing or re-importing blanks.
This is intended for fast script-only iteration. Once your image and audio assets are imported once via a full sync, you can flip the editor into Data Only mode and keep editing the story graph without paying the cost of copying media files on every save.
How to enable it:
- Perform at least one full sync so the plugin has imported all media assets into the StoryFlow output path (configured in the StoryFlow panel inside the Godot editor).
- In the StoryFlow Editor, toggle Data Only in the live-sync options for your Godot target.
- Continue editing - script and variable changes push instantly; media stays untouched.
What happens on the Godot side:
- When the importer looks for a media source file and does not find it on disk, it checks whether the corresponding resource already exists at the computed
res://path. - If the resource exists, it is loaded and registered against the script's resolved-assets map - no re-import, no warning.
- If the resource does not exist (you added a new image but haven't done a full sync yet), the importer logs a warning and skips that asset. Trigger a full sync to pick it up.
When to Use Each Mode
Use full sync when you add or replace media assets, or when starting a new project. Switch to data-only sync once your media is stable and you are iterating on dialogue, branching logic or variables - the round-trip from "save in editor" to "playing in Godot" is significantly faster because no texture/audio re-import happens.
Limitations
There are a few limitations to be aware of when using Live Sync:
- Editor-only - Live Sync lives in the
editor/folder of the plugin and is only instantiated by the editor dock, so it never runs in a shipped game. It is strictly a development workflow tool. The folder itself still has to ship - see Exporting Your Game. - Local only - The connection always targets
localhost, which requires the StoryFlow Editor to be running on the same machine as the Godot Editor. - Full re-parse - Each sync re-parses the entire project rather than applying incremental updates. Writes are cheaper since v1.2.2: each copy compares content (size first, then MD5) and skips the write when the destination already matches, and each media file is written once per sync instead of twice. Anything unreadable or missing is copied anyway, so doubt always resolves toward copying.
- Plugin version matters - Projects using map variables or modulo nodes need plugin 1.2.0 or newer. Older plugins log unsupported-node warnings and skip those nodes, and the StoryFlow Editor also shows a warning toast when a pre-1.2.0 plugin connects to a map-using project. See Forward-Compatibility Warnings for the warning messages and how to resolve them.