Skip to content

Project Data Structure

M. H. Golkar edited this page Aug 26, 2023 · 8 revisions

Arrow's saved project documents and exports share a similar core structure.

Many exports do not include metadata and/or developer node notes, and some formats such as playable .html come with embedded runtime. These files are suitable for testing and review, but if you need to re-import data to edit, best option would be going with .arrow save files (formatted in JSON including all data).

A project file looks like this:

{
    "title": "Untitled Adventure",
    "entry": 1, // Resource-UID of the project's main (active) entry node
    "meta": {
        // Native (default) distributed UID metadata:
        // > For larger projects which are divided into multiple documents,
        // > setting different chapter IDs per sub-project guarantees global uniqueness of resource UIDs.
        // > Up to 1024 chapters _represented as the 1st 9 bits in each UID_ can exist.
        "chapter": 0, // (0 - 1024; Order is optional.)
        // > Arrow uses an incremental seed tracker for each author to guarantee resource UID uniqueness
        // > when multiple users work on the same document at the same time.
        "authors": {
            // (Up to 64 authors _represented as the next 6 bits in each UID_ can contribute simultaneously.)
            0: [Settings.ANONYMOUS_AUTHOR_INFO, 3] // [Author info, and incremental seed for the next UID]
        },
        // ...
        // Time-based distributed UID epoch:
        // > This field is unix time (UTC in microseconds) on creation of the project.
        // > If you set it, you'll get 64-bit time-based distributed IDs inspired by Snowflake-IDs.
        // > This method is not recommended; For most of the projects the default method is a better choice.
        // "epoch": null,
        // ...
        "last_save": null, // UTC date-time (ISO 8601) string
        "editor": Settings.ARROW_VERSION, // for version compatibility checks.
        // ...
        // Arrow has a vcs-friendly project structure (i.e. unique & never-reused resource-ids, JSON exports, etc.)
        // so you can easily use your favorite revision system, such as Git.
        // `offline` and `remote` properties are reserved for possible editor vcs integration in the future.
        "offline": true,
        "remote": {},
    },
    // ...
    // Local incremental UID tracker (deprecated):
    // > If exists, we move this global seed to the author `0` on chapter `0` for backward compatibility.
    // "next_resource_seed": <int>,
    // ...
    // And finally where all the resources (such as scenes, nodes, variables, etc.) are:
	"resources": {
        // ...
    }
}

Building blocks of every Arrow project are resources.

Each resource is known to Arrow with a Unique Identifier (UID.) These UIDs are distributed by design and would not exceed 64 bits.

Default Native algorithm generates 53-bit (fitting into double-precision float) UIDs.

UIDs are set by Arrow and shall not be edited manually in the saved data (unless you really know what you're doing). The UID field in the inspectors, where you can edit a resource's unique identifier, actually edits the name parameter of each resource that reflects the underlying immutable UID in String (with base-36 representation), and helps identifying nodes more human-friendly. Arrow does not recycle UIDs, and never (re-)uses a UID or name for different resources by default.

For more information on UID and Name parameters, check out Project Organization.

There are our main resource types:

  • Scenes (& Macros)
  • Nodes
  • Variables
  • Characters

All the resources are sorted by UIDs, grouped under their respective types, in the resources tree. Following pseudo-code shows their overall structure:

{
    // ...,
    "resources": {
        "scenes" : { // (& macros)
            int<uid>: { // <Resource UID of the scene>
                "name": String<display-name>,
                "entry": int<entry-node-uid>,
                "map": { // How and in which order, nodes are connected in the parent scene:
                    int<uid>: { // <Resource UID of the (child) node in the scene>
                        "offset": [int<x>, int<y>], // (position on the grid)
                        "skip": bool'optional,
                        "io": [ // (list of the node's _outgoing_ connections, *not sorted*)
                            [ int<from_uid>, int<from_slot>, int<to_uid>, int<to_slot> ],
                            ...
                        ],
                    }, 
                    ...
                },
                "macro": bool'optional // (marks this scene as a reusable asset, a macro)
            },
            ...
        },
        "nodes" : {
            int<uid>: {
                "type": String/Enum<node-type>,
                "name": String<display-name>,
                "data": { <depends-on-the-node-type> },
                "notes": String'optional
            },
            ...
        },
        "characters": {
            int<uid>: {
                "name": String<display-name>,
                "color": String<display-color>, // RGB (hex)
                "tags": {
                    String<key>: String<value>,
                    ...
                }
            },
            ... 
        },
        "variables": {
            int<uid>: {
                "type": String/Enum<num|str|bool>,
                "name": String<display-name>,
                "init": Variant<initial-value>
            },
            ...
        }
    }
}

Variable and node resources have their own type property that defines what sort of value or data we expect to have there, so the behavior of the resource.

For brevity, only one side of each graph connection (two nodes on the grid), needs to, and does keep the io data. They are outgoing connections, conventionally, offering advantage of faster next node lookup.

There can be an optional use property (array) for each resource as well, indicating which other (user) resources rely on this one. Another property ref complements use by listing all the used (referred) resources for any dependent resource. use and ref are implemented to safeguard continuities during operations such as node removal.

At least one scene and one Entry node shall exist in every valid project, so the console or runtime knows where to start.

Projects are JSON-compatible, so complex data types are converted on saves, imports and exports.

e.g. Vector2(x,y) <-> Array[x,y] and Integer keys (i.e. UIDs) are saved as Strings.

Macros are scenes with macro = true property, which enjoy special treatments by the editor and runtime(s).

Make sure to check out Project Organization for more detailed technical information about underlying procedures and how you can use them to stay in control of your projects growing in size.

Clone this wiki locally