tmedwards / sugarcube-2

SugarCube is a free (gratis and libre) story format for Twine/Twee.
https://www.motoslave.net/sugarcube/2/
BSD 2-Clause "Simplified" License
177 stars 41 forks source link

diff(orig, dest) improvement #84

Closed Rizean closed 2 years ago

Rizean commented 3 years ago

I created a class called PersistentObject which is the foundation of all objects to be store/serialized. To deal with how SugarCube2 clones/serializes I added a closure in the constructor to prevent SugarCube from getting specific data. Adding in fromJSON/clone/toJSON to present serialized that would include anything from the closure that should be saved.

class PersistentObject {
    constructor(state) {
        this._state = {}

        // Using Closures for Private Variables to work around cycle clone bug in SugarCube
        this._private = (() => {
            let _private = {}
            return (key, value) => {
                if (typeof value === 'undefined') return key ? _private[key] : undefined
                _private[key] = value
            }
        })()

        // todo - I dont think we should do this...
        // this.fromJSON(state)
    }
...
}

I though this was working well until I started using it more. I ran into an issue where everything worked as expected until you hit F5 or loaded a save. That was when I figured out that even though toJSON was presenting the correct data it was not being saved. After many hours of stepping through the code I found the issue lies in the way diff does it's job. I tried to take the following approach but it failed.

    function diff(orig, dest) /* diff object */ {
                /* check if orig/dest has serialize - to support closures and non-generic objects with circular references */
        const _orig = orig.serialize && typeof orig.serialize === 'function' ? orig.serialize() : orig;
        const _dest = dest.serialize && typeof dest.serialize === 'function' ? dest.serialize() : dest;
               // replace orig/dest with _orig/_dest where needed 

This improved the results ie the delta that was generated had the expected changes but it is somehow not correct and causes an error on load.

At this point I'm going to change my usage of the private closures to only handle circular references along with a global object registry map to handle the restores. Just going through this has helped me better understand how SugarCube serializes and what you can get away with on non-generic objects and composition.

On a side note some very complex and impressive coding here. Has always amused me that some of the most impressive code is in game engines. If being a game dev didn't pay crap I'd do it in a heartbeat.