Live Sync
Live Sync connects the Godot Editor to StoryFlow Editor via WebSocket. Request a sync to update the imported project files, then start your game to test them.
Overview
Live Sync connects the Godot Editor to StoryFlow Editor on the same machine. Click Sync in either editor to export and import your latest changes. Saving a file alone does not send an update. If the game is running, restart it after syncing to load the updated files.
- Export and import project changes with one sync request
- WebSocket-based communication using
ws://localhost:9000by default - Automatic project re-import after a sync request
- 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
Open your project in StoryFlow Editor, select WebSocket in the Export menu and click Start Server. The default port is 9000. Use the same port in the Godot dock.
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
Click Sync in the Godot dock or in StoryFlow Editor after each set of edits. Wait for the dock to report completion, then start the game to test the updated files. Restart an already running game after syncing.
First Sync
After connecting, click Sync to import the project. Leave Data Only (Skip Assets) off in StoryFlow Editor's Export menu for the first sync so referenced media is included.
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. - Sync request - Clicking Sync in either editor makes StoryFlow Editor save and export the project, then send its path in a
"project-updated"message. The sync class also handles"pong"keep-alive responses. - Re-import - The importer reads the exported files from that project's build directory, including scripts, characters, Data Assets, localization and media. It then updates the Godot project.
- 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. - Testing - The runtime loads the imported files when the game starts. Restart a running game to use the new data; the editor dock does not refresh its active dialogue.
What a Sync Writes
Every sync and every manual import writes into the output directory configured in the dock (res://storyflow by default). These files include:
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.
- Shared project data -
global-variables.json,characters.json,character-index.json,data-assets.jsonand, when the project has target languages,localization.json. - 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
When StoryFlow Editor pushes a sync with Data Only enabled, project data includes scripts, variables, characters, Data Assets and localization. Image and audio files are not copied into the Godot project. The importer reuses media already imported by a full sync.
This is intended for iteration when your image and audio files have not changed. Run a full sync when you add or replace media, including media used by a Data Asset.
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 StoryFlow Editor's Export menu, select WebSocket and enable Data Only (Skip Assets). This setting applies to all connected clients.
- Continue editing and request another sync when ready. Project data updates while media files stay 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. Use data-only sync for dialogue, branching logic or variable edits while your media is unchanged. Restart the game after syncing to test the result.
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.