Jump to content
  • Advertisement
  • entries
    11
  • comments
    3
  • views
    2177

Project: The Berg

State changes (Reprise)

Greedy Goblin

651 views

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!).



0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!