Impact

This forum is read only and just serves as an archive. If you have any questions, please post them on github.com/phoboslab/impact

8 years ago by StuartTresadern

Hi, I am currently using a FSM to control entity and game state. How do I turn this into a plugin ?.

This is the FSM I am using :

https://github.com/jakesgordon/javascript-state-machine/

Thanks Stuart

8 years ago by drailing

Hi Stuart,

here you have the core synopsis for a plugin like the other classes in Dominics documentation:

ig.module(
	'plugins.PLUGINFILENAME'
)
.requires(
	'impact.impact'
).defines(function() {

	ig.PLUGINNAME= ig.Class.extend({
		
		yourAttributes: "nice!",
		
		init: function(){
			// constructor
		},
                
		yourFunctions: function(){

		}

		
	});
});

if im correct, you can just copy the statemachine code in the class extend obj param like:


ig.module(
	'plugins.PLUGINFILENAME'
)
.requires(
	'impact.impact'
).defines(function() {

	ig.PLUGINNAME= ig.Class.extend({

    //---------------------------------------------------------------------------

    // VERSION: "2.1.0",

    //---------------------------------------------------------------------------

    Result: {
      SUCCEEDED:    1, // the event transitioned successfully from one state to another
      NOTRANSITION: 2, // the event was successfull but no state transition was necessary
      CANCELLED:    3, // the event was cancelled by the caller in a beforeEvent callback
      ASYNC:        4, // the event is asynchronous and the caller is in control of when the transition occurs
    },

    Error: {
      INVALID_TRANSITION: 100, // caller tried to fire an event that was innapropriate in the current state
      PENDING_TRANSITION: 200, // caller tried to fire an event while an async transition was still pending
      INVALID_CALLBACK:   300, // caller provided callback function threw an exception
    },

    WILDCARD: '*',	

[... here the rest ....]

		
	});
});

8 years ago by StuartTresadern

Hi, Thanks for the reply. I used the sample code you supplied and created some sample plugins just to make sure I could get them working but when I attempt to use it with fsm code I can not get the plugin to work. Any idea why ?

thanks again Stuart

8 years ago by drailing

if you post any console error/output... perhaps.
but not with the message "it doesnt work" :-)

8 years ago by StuartTresadern

Hi,

It seems its more down to me to understand the javascript used in the StateMachine. I attempted to create the plugin but the console reports that the statemachine does not have a create function when i call it. Im pretty new to Javascript but I think its somthing to do with the StateMachine not having a constructor. Currently when I create the StateMachine I call


var fsm = StateMachine.create(....);


(it does not use new StateMachine)

More about the state machine can be found here if anyone else is interested

http://codeincomplete.com/posts/2011/8/19/javascript_state_machine_v2/

Thanks again for your help with the core synopsis for the plugin, its helped me get 3 other plugins working.

Stuart

8 years ago by drailing

hi,

i just copied the code and added an init function - NOT TESTED

instantiate it with
fsm: new StateMachine(param1, param2);

ig.module(
    'plugins.statemachine'
)
.requires(
    'impact.impact'
).defines(function() {

    ig.StateMachine= ig.Class.extend({

		//---------------------------------------------------------------------------

		VERSION: "2.1.0",

		//---------------------------------------------------------------------------

		Result: {
		  SUCCEEDED: 1, // the event transitioned successfully from one state to another
		  NOTRANSITION: 2, // the event was successfull but no state transition was necessary
		  CANCELLED: 3, // the event was cancelled by the caller in a beforeEvent callback
		  ASYNC: 4, // the event is asynchronous and the caller is in control of when the transition occurs
		},

		Error: {
		  INVALID_TRANSITION: 100, // caller tried to fire an event that was innapropriate in the current state
		  PENDING_TRANSITION: 200, // caller tried to fire an event while an async transition was still pending
		  INVALID_CALLBACK: 300, // caller provided callback function threw an exception
		},

		WILDCARD: '*',
		ASYNC: 'async',

		//---------------------------------------------------------------------------
		
		init: function(cfg, target){
			this.create(cfg, target);
		},
		
		create: function(cfg, target) {

		  var initial = (typeof cfg.initial == 'string') ? { state: cfg.initial } : cfg.initial; // allow for a simple string, or an object with { state: 'foo', event: 'setup', defer: true|false }
		  var fsm = target || cfg.target || {};
		  var events = cfg.events || [];
		  var callbacks = cfg.callbacks || {};
		  var map = {};

		  var add = function(e) {
			var from = (e.from instanceof Array) ? e.from : (e.from ? [e.from] : [StateMachine.WILDCARD]); // allow 'wildcard' transition if 'from' is not specified
			map[e.name] = map[e.name] || {};
			for (var n = 0 ; n < from.length ; n++)
			  map[e.name][from[n]] = e.to || from[n]; // allow no-op transition if 'to' is not specified
		  };

		  if (initial) {
			initial.event = initial.event || 'startup';
			add({ name: initial.event, from: 'none', to: initial.state });
		  }

		  for(var n = 0 ; n < events.length ; n++)
			add(events[n]);

		  for(var name in map) {
			if (map.hasOwnProperty(name))
			  fsm[name] = StateMachine.buildEvent(name, map[name]);
		  }

		  for(var name in callbacks) {
			if (callbacks.hasOwnProperty(name))
			  fsm[name] = callbacks[name]
		  }

		  fsm.current = 'none';
		  fsm.is = function(state) { return this.current == state; };
		  fsm.can = function(event) { return !this.transition && (map[event].hasOwnProperty(this.current) || map[event].hasOwnProperty(StateMachine.WILDCARD)); }
		  fsm.cannot = function(event) { return !this.can(event); };
		  fsm.error = cfg.error || function(name, from, to, args, error, msg) { throw msg; }; // default behavior when something unexpected happens is to throw an exception, but caller can override this behavior if desired (see github issue #3)

		  if (initial && !initial.defer)
			fsm[initial.event]();

		  return fsm;

		},

		//===========================================================================

		doCallback: function(fsm, func, name, from, to, args) {
		  if (func) {
			try {
			  return func.apply(fsm, [name, from, to].concat(args));
			}
			catch(e) {
			  return fsm.error(name, from, to, args, StateMachine.Error.INVALID_CALLBACK, "an exception occurred in a caller-provided callback function");
			}
		  }
		},

		beforeEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); },
		afterEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); },
		leaveState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); },
		enterState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); },
		changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); },


		buildEvent: function(name, map) {
		  return function() {

			var from = this.current;
			var to = map[from] || map[StateMachine.WILDCARD] || from;
			var args = Array.prototype.slice.call(arguments); // turn arguments into pure array

			if (this.transition)
			  return this.error(name, from, to, args, StateMachine.Error.PENDING_TRANSITION, "event " + name + " inappropriate because previous transition did not complete");

			if (this.cannot(name))
			  return this.error(name, from, to, args, StateMachine.Error.INVALID_TRANSITION, "event " + name + " inappropriate in current state " + this.current);

			if (false === StateMachine.beforeEvent(this, name, from, to, args))
			  return StateMachine.CANCELLED;

			if (from === to) {
			  StateMachine.afterEvent(this, name, from, to, args);
			  return StateMachine.NOTRANSITION;
			}

			// prepare a transition method for use EITHER lower down, or by caller if they want an async transition (indicated by an ASYNC return value from leaveState)
			var fsm = this;
			this.transition = function() {
			  fsm.transition = null; // this method should only ever be called once
			  fsm.current = to;
			  StateMachine.enterState( fsm, name, from, to, args);
			  StateMachine.changeState(fsm, name, from, to, args);
			  StateMachine.afterEvent( fsm, name, from, to, args);
			};

			var leave = StateMachine.leaveState(this, name, from, to, args);
			if (false === leave) {
			  this.transition = null;
			  return StateMachine.CANCELLED;
			}
			else if ("async" === leave) {
			  return StateMachine.ASYNC;
			}
			else {
			  if (this.transition)
				this.transition(); // in case user manually called transition() but forgot to return ASYNC
			  return StateMachine.SUCCEEDED;
			}

		  };
		}

	});
});

7 years ago by soybean

Hi,

I'm new with Impact and was looking for a FSM solution. The code from drailing didn't quite work (yes, it wasn't tested) so I had some fixes.

Here's a working FSM port from jakesgordon's javascript-state-machine:

ig.module
(
	'plugin.statemachine'
)
.requires
(
	'impact.impact'
)
.defines(function() 
{
	ig.StateMachine = ig.Class.extend(
	{
		fsm: {},
		
		//---------------------------------------------------------------------------
		init: function(cfg, target) 
		{
			this.fsm = this.create(cfg, target);
		},

		create: function(cfg, target) 
		{
			var initial = (typeof cfg.initial == 'string') ? {
				state: cfg.initial
			}: cfg.initial;
			// allow for a simple string, or an object with { state: 'foo', event: 'setup', defer: true|false }
			var fsm = target || cfg.target || {};
			var events = cfg.events || [];
			var callbacks = cfg.callbacks || {};
			var map = {};

			var add = function(e) 
			{
				var from = (e.from instanceof Array) ? e.from: (e.from ? [e.from] : [ig.StateMachine.WILDCARD]);
				// allow 'wildcard' transition if 'from' is not specified
				map[e.name] = map[e.name] || {};
				for (var n = 0; n < from.length; n++)
					map[e.name][from[n]] = e.to || from[n];
				// allow no-op transition if 'to' is not specified
			};
			
			if (initial) 
			{
				initial.event = initial.event || 'startup';
				add({
					name: initial.event,
					from: 'none',
					to: initial.state
				});
			}
			
			for (var n = 0; n < events.length; n++)
				add(events[n]);
			
			for (var name in map) 
			{
				if (map.hasOwnProperty(name))
				{
					fsm[name] = this.buildEvent(name, map[name]);
					console.log("BUILT EVENT, name: " + name + ", fsm: " + JSON.stringify(fsm));
				}
			}
			
			for (var name in callbacks) 
			{
				if (callbacks.hasOwnProperty(name))
					fsm[name] = callbacks[name]
			}
			
			fsm.current = 'none';
			fsm.is = function(state) 
			{
				return this.current == state;
			};
			fsm.can = function(event) 
			{
				return ! this.transition && (map[event].hasOwnProperty(this.current) || map[event].hasOwnProperty(StateMachine.WILDCARD));
			}
			fsm.cannot = function(event) 
			{
				return ! this.can(event);
			};
			fsm.error = cfg.error || function(name, from, to, args, error, msg) { throw msg; };
			// default behavior when something unexpected happens is to throw an exception, but caller can override this behavior if desired (see github issue #3)
			if (initial && !initial.defer)
				fsm[initial.event]();
			
			console.log("INITIAL: " + initial.event + ", fsm: " + JSON.stringify(fsm));
			
			return fsm;
		},
		
		//===========================================================================
		doCallback: function(fsm, func, name, from, to, args) 
		{
			if (func) 
			{
				try 
				{
					console.log("DO CALLBACK, name: " + name + ", from: " + from + ", to: " + to + ", args: " + args);
					return func.apply(fsm, [name, from, to].concat(args));
				}
				catch(e) 
				{
					return fsm.error(name, from, to, args, ig.StateMachine.Error.INVALID_CALLBACK, "an exception occurred in a caller-provided callback function");
				}
			}
		},

		beforeEvent: function(fsm, name, from, to, args) 
		{
			return this.doCallback(fsm, fsm['onbefore' + name], name, from, to, args);
		},
		afterEvent: function(fsm, name, from, to, args) 
		{
			return this.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args);
		},
		leaveState: function(fsm, name, from, to, args) 
		{
			return this.doCallback(fsm, fsm['onleave' + from], name, from, to, args);
		},
		enterState: function(fsm, name, from, to, args) 
		{
			return this.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args);
		},
		changeState: function(fsm, name, from, to, args) 
		{
			return this.doCallback(fsm, fsm['onchangestate'], name, from, to, args);
		},


		buildEvent: function(name, map) 
		{
			console.log("BUILDING EVENT, name: " + name + ", map: " + JSON.stringify(map));
			var self = this;
			return function() 
			{
				var from = this.current;
				var to = map[from] || map[ig.StateMachine.WILDCARD] || from;
				var args = Array.prototype.slice.call(arguments);
				// turn arguments into pure array
				if (this.transition)
					return this.error(name, from, to, args, ig.StateMachine.Error.PENDING_TRANSITION, "event " + name + " inappropriate because previous transition did not complete");
					
				if (this.cannot(name))
					return this.error(name, from, to, args, ig.StateMachine.Error.INVALID_TRANSITION, "event " + name + " inappropriate in current state " + this.current);
					
				if (false === self.beforeEvent(this, name, from, to, args))
					return ig.StateMachine.Result.CANCELLED;
					
				if (from === to) 
				{
					self.afterEvent(this, name, from, to, args);
					return ig.StateMachine.Result.NOTRANSITION;
				}
				// prepare a transition method for use EITHER lower down, or by caller if they want an async transition (indicated by an ASYNC return value from leaveState)
				var fsm = this;
				this.transition = function() 
				{
					fsm.transition = null;
					// this method should only ever be called once
					fsm.current = to;
					self.enterState(fsm, name, from, to, args);
					self.changeState(fsm, name, from, to, args);
					self.afterEvent(fsm, name, from, to, args);
				};
				
				var leave = self.leaveState(this, name, from, to, args);
				if (false === leave) 
				{
					this.transition = null;
					return ig.StateMachine.Result.CANCELLED;
				}
				else if ("async" === leave) 
				{
					return ig.StateMachine.ASYNC;
				}
				else 
				{
					if (this.transition)
					this.transition();
					// in case user manually called transition() but forgot to return ASYNC
					return ig.StateMachine.Result.SUCCEEDED;
				}
			};
		}
	});
	
	//---------------------------------------------------------------------------
	ig.StateMachine.VERSION = "2.1.0";
	//---------------------------------------------------------------------------
	ig.StateMachine.Result = {
		SUCCEEDED: 1,
		// the event transitioned successfully from one state to another
		NOTRANSITION: 2,
		// the event was successfull but no state transition was necessary
		CANCELLED: 3,
		// the event was cancelled by the caller in a beforeEvent callback
		ASYNC: 4,
		// the event is asynchronous and the caller is in control of when the transition occurs
	};
	ig.StateMachine.Error = {
		INVALID_TRANSITION: 100,
		// caller tried to fire an event that was innapropriate in the current state
		PENDING_TRANSITION: 200,
		// caller tried to fire an event while an async transition was still pending
		INVALID_CALLBACK: 300,
		// caller provided callback function threw an exception
	};
	ig.StateMachine.WILDCARD = '*';
	ig.StateMachine.ASYNC = 'async';
	
});

7 years ago by soybean

Some of the difference with jakesgordon's code is that you'll need to access the fsm property once created a ig.StateMachine instance.

example:

this.play_fsm = new ig.StateMachine(
{
	initial: "ready",
	events: [
		{ name: "pick", from: "ready", to: "select_1" },
		{ name: "pick", from: "select_1", to: "select_2" },
		{ name: "check", from: "select_2", to: "ready" }
	]
});

console.log("SETUP FSM, check current: " + this.play_fsm.fsm.current);
console.log("SETUP FSM, play_fsm: " + JSON.stringify(this.play_fsm));
this.play_fsm.fsm.pick();
console.log("SETUP FSM, check current: " + this.play_fsm.fsm.current);

7 years ago by soybean

Oh btw, sorry for the console.logs.. you can remove it if you like :)

7 years ago by StuartTresadern

Wow that seems like a very Long time ago. I did get it working in the end with changes pretty much the same as yours. Thanks for posting anyway I am sure may other developers will find it useful.
Page 1 of 1
« first « previous next › last »