Help with extending MAP

Monkey Forums/Monkey Beginners/Help with extending MAP

Trez(Posted 2014) [#1]
I have been trying to create a FSM Manager that will allow me to create FSM's for any objects and add/ execute states on the FSM's.

The Idea is any class I need a FSM on I implement IFsmObject ( all code is below) I can then register states against this object:

FSM.GetInstance().RegisterState(Self, "normal", New NormalState())


Basic FsmState

Class NormalState Extends FsmState
	
	Method ExecuteState:String(obj:IFsmObject)
		Return ""
	End

End


This seems to work OK if the IFsmObjects are the same type but as soon as I add a new type of object that implements IFsmObject with Register it looks like it somehow uses that same object type. Could be my Extension of Map or something else ! any ideas ?

#REM
	summary: A FsmMap is a convenience class for mapping IFsmObject objects to StringMap<FsmState>. 
#END
Class FsmMap<V> Extends Map<IFsmObject, V>
	
	Method Compare:Int(lhs:IFsmObject, rhs:IFsmObject)
		If lhs = rhs Return 0
		Return 1
	End
End


'------------------------------------------------------------------------------------
#REM 
	summary: interface that defines required properties for a Finite State Machine.
#END
'------------------------------------------------------------------------------------
Interface IFsmObject
		
	Method CurrentState:FsmState() Property
	Method CurrentState:Void(state:FsmState) Property
	Method PreviousState:FsmState() Property
	Method PreviousState:Void(state:FsmState) Property

End


#REM 
	summary:Abstract class that defines the basic State structure for Finite State Machines.
	Classes that Extend FsmState must implement ExscuteState
#END
Class FsmState Abstract
	
	Private
	Field stateName:String

	Public
	Method StateName:String() Property
		Return Self.stateName
	End
	
	Method StateName:Void(stateName:String) Property
		Self.stateName = stateName
	End
	
	Method EnterState:Void(obj:IFsmObject)
	End
	
	Method ExitState:Void(obj:IFsmObject)
	End
	
	Method ExecuteState:String(obj:IFsmObject) Abstract

End


Strict

#REFLECTION_FILTER+="fsm" 

Import reflection

'------------------------------------------------------------------------------------
#Rem
summary:The main singleton Finite State Machine manager Class.
The purpose of this Class is To create and manage all FSMState states for all IFSMObject Finite State Machines.
Individual states are instantiated and hashed on a per-FSM basis. The FSM class will also handle transitions 
and call all the neccesary transition callbacks on based on the specifications of individual states.
#End
Class FSM
	
Private
	Global instance:FSM = Null
	Field stateHash:FsmMap<StringMap<FsmState>> = New FsmMap<StringMap<FsmState>>

	Method New()		
	End
	
Public
	
	#Rem
	summary: The FSM singleton object. 
	Use GetInstance() to access all FSM functionality.
	#End
	Function GetInstance:FSM()
		If instance = Null Then
			instance = New FSM()
		EndIf
		Return instance
	End
	
	#Rem
	summary:Executes the specified Finite State Machine.
	Specifically, this executes the CurrentState of the specified IFSMObject and performs any neccesary 
	transitions via the SetState method.
	#End
	Method Execute:Void(obj:IFsmObject)
	
		If obj.CurrentState <> Null
	
			Local FsmTable:StringMap<FsmState> = StringMap<FsmState>(Self.stateHash.Get(obj))
			
			If FsmTable = Null Then
				Return
			EndIf

			Local targetState:FsmState = GetState(obj, obj.CurrentState().ExecuteState(obj))
			
			SetState(obj, targetState)
			
		EndIf
	
	End

	#Rem	
	summary:Attempt To set the CurrentState of the specified IFSMObject To the state specified and call any appropriate state transitions.
    State is specified by name.
	#End
	Method SetState:Void(obj:IFsmObject, stateName:String)
		
		If stateName = "" Then Return
		SetState(obj, GetState(obj, stateName))
		
	End
	
	#Rem
	summary:Attempt To set the CurrentState of the specified IFSMObject To the state specified and call any appropriate state transitions.
    The actual instance of the desired state is passed. This should normally only be used internally by the FSM class. The preferred
    method is SetState(IFSMObject obj, string stateName).
	#End
	Method SetState:Void(obj:IFsmObject, state:FsmState)
	
		Local FsmTable:StringMap<FsmState> = StringMap<FsmState>(Self.stateHash.Get(obj))
	
		If FsmTable = Null Then Return
		
		If obj <> Null And state <> Null And FsmTable.Contains(state.StateName())
			
			If obj.CurrentState() <> Null Then obj.CurrentState().ExitState(obj)
			
			obj.PreviousState(obj.CurrentState())
			obj.CurrentState(state)
			obj.CurrentState().EnterState(obj)
		
		EndIf
	
	End
	
	#REM
	summary: Register a state To be accessible To the specified Finite State Machine.
	This Method will create an instance of the FSMState and hash it under the specified name in a table created specifically
	for the specified IFSMObject's class type. The state can later be retrieved by name using GetState.
	#END
	Method RegisterState:Void(obj:IFsmObject, stateName:String, fsmObject:FsmState)
	
		Print("Adding State : " + stateName + " to " + GetClass(obj).Name)
		
		If Not Self.stateHash.Contains(obj)
			
			Self.stateHash.Add(obj, New StringMap<FsmState>)

		EndIf
		
		Local FsmTable:StringMap<FsmState> = StringMap<FsmState>(Self.stateHash.Get(obj))
		
		If FsmTable = Null
			' Throw an exception here !!!!
		EndIf
		
		If FsmTable.Contains(stateName)
			If FsmTable.Get(stateName) = fsmObject Then Return
			FsmTable.Remove(stateName)			
		EndIf
		
		fsmObject.StateName(stateName)
		FsmTable.Add(stateName, fsmObject)
		
	End
	
	#REM
		summary:Get the instance of the specified state that's registered for the specified IFSMObject.
	#END
	Method GetState:FsmState(obj:IFsmObject, stateName:String)
	
		Local FsmTable:StringMap<FsmState> = StringMap<FsmState>(Self.stateHash.Get(obj))
		
		If FsmTable = Null Or stateName = ""
			Return Null
		EndIf
		
		Return FsmState(FsmTable.Get(stateName))
				
	End
		
End



muddy_shoes(Posted 2014) [#2]
Your compare method doesn't meet the documented requirements:

"The compare method must return a negative value if lhs < rhs, a positive value if lhs > rhs, or 0 if lhs = rhs. "


Trez(Posted 2014) [#3]
Hi muddy_shoes thanks for the reply but how do I compare < or > when its an object of type IFsmObject ?


muddy_shoes(Posted 2014) [#4]
You need to add your own ordering value/calculation of some kind.

This recent thread discussed the same issue: http://www.monkey-x.com/Community/posts.php?topic=8043


Trez(Posted 2014) [#5]
Thanks again ill check that out now.

Also when using reflection to see the class name when registering states in RegisterState they all return the same class name monkey.lang.Object. I was expecting the type to be returned. Any ideas why ?

Print("Adding State : " + stateName + " to " + GetClass(obj).Name)


Adding State : normal to monkey.lang.Object
Adding State : alert to monkey.lang.Object
Adding State : dead to monkey.lang.Object
Adding State : normal to monkey.lang.Object
Adding State : alert to monkey.lang.Object
Adding State : dead to monkey.lang.Object
Adding State : refuel to monkey.lang.Object
Adding State : attack to monkey.lang.Object
Adding State : gotobase to monkey.lang.Object
Adding State : find to monkey.lang.Object
Adding State : dead to monkey.lang.Object


muddy_shoes(Posted 2014) [#6]
"Any ideas why?"

I haven't used reflection much but I'd guess that you aren't including the appropriate classes under your reflection filter. Maybe check that the classes you're interested in are included with GetClasses?


Trez(Posted 2014) [#7]
Ill Check the reflection filters, first time i have attempted to use reflection in monkey.

Anyway more to the point I added an fsmid:Int and used that in the comparer, works great now.

thanks very much for your time :)