🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

State changes (Reprise)

posted in The Berg for project The Berg
Published March 14, 2019
Advertisement

Progress really has been slow of late and I'm just not finding the time I need to make serious progress.  Not that it's a problem.  This is a hobby project with no deadlines and is as much about the learning journey as it is about the end product.

However, I have started making a simple map editor which I'll create a separate blog entry on later, but in the process of doing so I found that I wanted to make more use of my Stateful and When constructs and this led me to realise a few mistakes I'd made and how I could improve things.  It also brought to my attention the Proxy object which had managed to fly under my radar.

So I realised after putting things into practice that I had made a mistake with how I was storing the transitions against the Stateful object (I'd gone and created an array on the prototype which was then shared between instances of Stateful... it worked, but really not what I intended/wanted... duh!).

So I played around with a few ideas and eventually it evolved into something I liked and felt was a substantial improvement on the original.

The Stateful object now utilises Proxy and individual states no longer need to derive from the Stateful prototype, making it much easier to use (IMO).  This is what it looks like now:


const Stateful = {
    from( src ) {
        return new Proxy( Object.create( {
            _transitions: [],
            _state: src,
            set( newState ) {
                let matchingTransitions = this._transitions.filter( x => ( x.from === undefined || x.from === this._state ) && ( x.to === undefined || x.to === newState ) );

                if ( matchingTransitions.length > 0 ) {
                    let prvState = this._state;
                    this._state = newState;
                    return matchingTransitions.forEach( t => t.callback( prvState, newState ) );
                }
                return false;
            },
            get isStateful() {
                return true;
            },
            is( comparitor ) {
                return this._state === comparitor;
            }
        } ), {
            get( target, prop ) {
                if ( [ "set", "_state", "_transitions", "isStateful", "is" ].includes( prop ) ) {
                    return target[ prop ];
                }
                return target[ "_state" ][ prop ];
            }
        } );
    }
}

module.exports = Stateful;

The Proxy acts as a ... errr... proxy... between the Stateful object and the underlying state that it's created from.  So when we want to create a Stateful variable we simply define our states as simple data containers and then create the Stateful variable from one of these. e.g.


const EDITOR_CMD = {
    IDLE: { name: "IDLE" },
    ROTATE: { name: "ROTATE" },
    TRANSLATE: { name: "TRANSLATE" },
    SCALE: { name: "SCALE" },
    X_AXIS: { 
		name: "X_AXIS",
        axis: AXES.X
    },
    Y_AXIS: {
        name: "Y_AXIS",
        axis: AXES.Y
    },
    Z_AXIS: {
        name: "Z_AXIS",
        axis: AXES.Z
    }
};

...

this.currentAction = Stateful.from( EDITOR_CMD.IDLE );

...

Next comes the redefintion of When...


function _then( statefulObj, src, dst, callback ) {
    if ( typeof ( callback ) !== "function" )
        throw new Error( "callback must be of type function" );

    src.forEach( s => {
        dst.forEach( d => {
            statefulObj._transitions.push( {
                from: s,
                to: d,
                callback: callback
            } );
        } );
    } );
}

function When( statefulObj ) {
    if ( !statefulObj.isStateful ) {
        throw "Argument must be a Stateful object";
    }

    return {
        changes: {
            from( ...src ) {
                return {
                    to( ...dst ) {
                        return {
                            then( callback ) {
                                _then( statefulObj, src, dst, callback );
                            }
                        };
                    },

                    then( callback ) {
                        _then( statefulObj, src, [ undefined ], callback );
                    }
                };
            },

            to( ...dst ) {
                return {
                    then( callback ) {
                        _then( statefulObj, [ undefined ], dst, callback );
                    }
                };
            }
        }
    };
}

module.exports = When;

In my mind this seems more straightforward than the previous version and allows us to do cool things like this:


When( this.currentAction ).changes
	.from( EDITOR_CMD.ROTATE, EDITOR_CMD.TRANSLATE, EDITOR_CMD.SCALE )
	.to( EDITOR_CMD.X_AXIS, EDITOR_CMD.Y_AXIS, EDITOR_CMD.Z_AXIS )
	.then( ( prevState, newState ) => {
		console.log( newState.name );
		this.controls.axis = newState.axis;
		this.currentAction.set( prevState );
	} );

Well, it's cool to me anyway (simple pleasures).  ?

That's it for now but I'll try and get a new blog up about my approach to building a simple map editor as soon as possible.  Let me know what you think of the above and any improvements you might suggest (or any mistakes I've made!).

1 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement