-
Notifications
You must be signed in to change notification settings - Fork 16
Overview over lively.next Internals
linusha edited this page May 8, 2021
·
1 revision
- Conceptual:
- A world is a "workbench". The place where everything is built.
- Offers multiple workspaces between which can be navigated.
- Technical:
- The whole state is defined within the world.
--> When saving and loading a world, the current state of the world is unaltered (in theory, see exceptions in section on Saving a world). - A world is independent from the browser.
- The whole state is defined within the world.
- Contains all JavaScript objects in the world.
- Objects reference each other.
- Is a cyclic structure.
--> Cannot be directly serialized to JSON.
- Takes a snapshot of the whole world state (in contrast to e.g. just saving changes).
- Use starting point (the World).
- Object Graph is traversed by serializer:
- Serialize primitives directly (bool, string, number, ...)
- Objects and their referenced objects are serialized recursively:
- Newly created objects receive a unique Morphic ID from the serializer. This one will be unaltered for the lifetime of the object, even beyond saving and (re-)loading a world.
- Objects alrady visited during the serialization process are remembered (via the object identity of the JS VM); recursion will be terminated at these, they will now be referenced via their Morhpic ID.
- Traversion results in linearized graph. Cyclic dependencies are solved via Morphic IDs.
- Linearized graph was serialized to JSON string while traversing.
- Caveats: Not everything can or should be serialized
- Custom objects are usually small and can be fully serialized.
- Morphs are tricky; they contain references to renderer, meta state, ...
- By default, all properties will be serialized --> not desirable
--> Serializer checks for different getters, e.g.get __serialize_only__()
, which either define properties to serialize or properties that should not be serialized
--> Referenced objects that should not be serialized (that should be "pruned") can be added to the pruning process by implementing those special getters - Morphs with
epiMorph = true
will not be serialized, e.g. the toolbar
- By default, all properties will be serialized --> not desirable
- Types and classes should not be serialized recursively, since they may come from a large third-party module --> only the type info (path to class) is serialized together with the object
- Closures (i.e. their contexts) cannot be serialized and will be lost
- Module lively.lang/closures supports saving closures partially --> Types, classes, closures, ... need to be re-initialized upon loading of the world (see details at respective section).
- Deserialization of JSON string to objects with Morphic IDs and properties. Starts at a given ID (usually the world).
- Recursive (re-)construction of objects using the constructors of the respective classes --> results in world's object graph
- Closures (i.e. their contexts) were not saved --> need to be re-initialized via property setters
- Type definitions/classes were not saved (to give flexibility and optimize file size)
- Module system of Lively allows to find respective classes easily (path to class was saved by serializer)
- Breaks if path is no longer valid --> Migrations.js: Pre-Process of deserialization. Patchs unreferenced/no longer present classes within the given snapshot.
- When copying objects, the original JS object is serialized.
- The copy is then instantiated from this JSON string.
- During the serialization process, the Serializer creates a new Morphic ID for the copied object.
- See section on versioning.
- Core elements of Lively are versioned via Git.
- Changes in core modules need to be persisted via commits.
- Everything within a world (everything that is serialized) is stored and versioned within a MorphicDB.
- MorphicDB: LevelDB which supports interactions using an interface identical to CouchDB thanks to PouchDB
--> ergo: MorphicDB is used like a CouchDB, but works using a LevelDB internally. All thanks to the magic of the JS library PouchDB. - When running Lively locally: Usage of LevelDB (with CouchDB interface)
- When collaborating with others: Usage of remote CouchDB (set up on a shared server)
- MorphicDBSynchronizer allows one to use remote CouchDB
- Everything is still saved in local LevelDB
- CouchDB runs on server
- Synchronizer checks LevelDB periodically
- Serialized world (JSON file), created by serializer.
- Saved at:
lively.morphic/objectdb/morphicdb/snapshots
- Directory contains sub-directories named after combinations of two hex digits.
- These are buckets of all commits whose hash start with thes two hex digits.
- A commit consists of:
- Commit ID (path to JSON file containing the snapshot)
- Commit info
- Predecessor --> creates commit history
- Author
- Hash
- Timestamp
- Name
- Saved in database at:
lively.morphic/objectdb/morphicdb-commits
(.ldb-file, LevelDB-file)
- The conception MorphicDB seems rather complicated. We want to discuss the reasons.
- CouchDB can detect differences between DBs --> synchronisation of worlds possible
--> Usable for collaboration between multiple worlds
--> Usually, a remote CouchDB (without underlying LevelDB) is set up on shared server - Caveat: Only compares commit IDs
--> Can detect possible conflicts, but cannot merge/show diffs between commits
--> Allows for sequential work in one world, only (as of now)
- The setup of CouchDB is difficult.
- Lively should be rather easy to set up.
- LevelDB is easy to set up.
- It's a match.
- Especially useful for local instances.
- Loading same world as two instances A and B at a time works without issues, since same snapshot is loaded.
- Saving world A works without a problem, commit (and thus snapshot) is created as usual.
- Saving world B:
- Would raise warning, because commit from world A is not a predecessor and already present.
- Options:
- Cancel new commit.
- Overwrite old commit from world A: New commit is appended to history. Old commit is not deleted (just overlaid by new commit) and could be restored.
- Merge conflict resolution currently not possible.
- Freezing a Morph/Object creates own world around it
- Makes it loadable on it's own, without meta information on how to edit the object
- Used to publish finished results
- Lively works on a Virtual DOM, comparable to VueJS or React
- Workflow: Browser requests animation frame --> render pass / render frame
- Traverse Object Graph
- Generate Virtual DOM Nodes
- Render()
- Advantages:
- Rendering code is easy to write:
- No concerns of sycnhronizity
- Unidirectional data flow
- Slight performance boost in certain situations:
- Changes can are queued and then applied at once.
- Only objects that changed need to be updated.
- Rendering code is easy to write:
- Disadvantages:
- On every render pass, a big tree of objecs must be generated. --> New objects must be created, others deleted --> Garbage collector needs to run often --> power intensive
- Problems with changes of objects:
- Are not instantanious
- Code may no be certain that changes have already been applied (e.g. that making a window fullscreen has already changed the object's extent in the next line)
--> Requires waiting on next render frame (async)
-
whenRendered
gives promise await(this.whenRendered())
- execute next code with JS promise syntax
.then
-
- All JS based, no CSS transitions
- Change properties during animation via
withAnimationDo
, e.g.- change vertices of a path (e.g. for a sad smiley)
- colors
- Changing CSS in lively is possible
- Don't do this!
- CSS is overwritten anyway (since lively applies the properties using Inline-CSS)
- Can be prevented with
important
, but causes side effects, as a Morph's properties are having no effect anymore
- Can be prevented with
- Demolishs the layer of abstraction that should have been achieved --> Does not allow the advantages of lively/morphic
- Experimental feature
- Normally, lively also loads all modules when loading a snapshot
--> Requires a lot of meta information to be loaded (Source, Classes, AST, Depedencies, ...)
- Takes a lot of time
- But: Actually only required for objects that are changed in the system (i.e. newly developed features or adjusted objects) --> Not required for most objects (e.g. parts of the development system, like windows)
- Idea: Similar to freezing
- Only load everything needed to start, no meta information --> Does not allow editing of source code of those objects (e.g. system parts)
- Current state:
- Viewing source code possible
- Load meta information on demand impossible (e.g. you cannot change that window behaviour, if you have used FastLoad)