git » sdk » omemo » tree

[omemo] / snikket / FSM.hx

package snikket;

import haxe.Exception;

typedef FSMTransitionName = String;
typedef FSMStateName = String;

typedef FSMEvent = {
	var fsm : FSM;

	var ?name : FSMTransitionName;

	var to : FSMStateName;
	var ?toAttr : Dynamic;
	var ?from : FSMStateName;
	var ?fromAttr : Dynamic;
};

typedef FSMTransition = {
	var name : FSMTransitionName;
	var from : Array<FSMStateName>;
	var to : FSMStateName;
};

typedef FSMStateHandler = (FSMEvent)->Void;
typedef FSMTransitionHandler = (FSMEvent)->Bool;

typedef FSMDescription = {
	var transitions : Array<FSMTransition>;

	var ?state_handlers : Map<FSMStateName,FSMStateHandler>;
	var ?transition_handlers : Map<FSMTransitionName,FSMTransitionHandler>;
};

class FSM extends EventEmitter {
	private var states : Map<FSMStateName,Map<FSMTransitionName,FSMStateName>> = [];
	private var currentState : FSMStateName = null;
	private var currentStateAttributes : Dynamic = null;
	
	public function new(desc:FSMDescription, initialState:FSMStateName, ?initialAttr:Dynamic) {
		super();
		for(transition in desc.transitions) {
			var from_states = transition.from;
			for (from_state in from_states) {
				var from_state_def = states.get(from_state);
				if (from_state_def == null) {
					from_state_def = [];
					states.set(from_state, from_state_def);
				}
				var to_state_def = states.get(transition.to);
				if (to_state_def == null) {
					to_state_def = [];
					states.set(transition.to, to_state_def);
				}
				if (states.get(from_state).get(transition.name) != null) {
					throw new Exception("Duplicate transition in FSM specification: " + transition.name + " from " + from_state);
				}
				states.get(from_state).set(transition.name, transition.to);
			}
		}

		if(desc.state_handlers != null) {
			for (state => handler in desc.state_handlers) {
				this.on('enter/$state', function (data) {
					handler(data);
					return EventHandled;
				});
			}
		}

		if(desc.transition_handlers != null) {
			for (transition => handler in desc.transition_handlers) {
				this.on('transition/$transition', function (data) {
					if(handler(data) == false) {
						return EventStop;
					}
					return EventHandled;
				});
			}
		}
		
		currentState = initialState;
		currentStateAttributes = initialAttr;
		var initialEvent:FSMEvent = {
			fsm: this,
			to: initialState,
			toAttr: initialAttr,
		};
		this.notifyTransitioned(initialEvent, true);
	}

	public function can(name:FSMTransitionName):Bool {
		return states.get(currentState).get(name) != null;
	}

	public function getCurrentState():String {
		return currentState;
	}

	public function event(name:FSMTransitionName, ?attr:Dynamic):Bool {
		var newState = states.get(currentState).get(name);
		if(newState == null) {
			throw new Exception("Invalid state transition: " + currentState + " cannot " + name);
		}

		var event:FSMEvent = {
			fsm: this,

			name: name,
			to: newState,
			toAttr: attr,

			from: currentState,
			fromAttr: currentStateAttributes,
		};

		if(notifyTransition(event) == false) {
			return false;
		}

		this.currentState = newState;
		this.currentStateAttributes = attr;

		notifyTransitioned(event, false);
		return true;
	}

	private function notifyTransition(event:FSMEvent):Bool {
		var ret;
		ret = this.trigger("transition", event);
		if(ret == EventStop) {
			return false;
		}
		if(event.to != event.from) {
			ret = this.trigger("leave/"+event.from, event);
			if (ret == EventStop) {
				return false;
			}
		}
		ret = this.trigger("transition/"+event.name, event);
		if(ret == EventStop) {
			return false;
		}
		return true;
	}

	private function notifyTransitioned(event:FSMEvent, isInitial:Bool):Void {
		if(event.to != event.from) {
			this.trigger("enter/"+event.to, event);
		}
		if(isInitial == false) {
			if(event.name != null) {
				trigger("transitioned/"+event.name, event);
			}
			trigger("transitioned", event);
		}
	}
}