Arch - A powerful hierarchical state machine library.

I wanted to access a state’s history so that I could check to see if a state was set to active before or after a different state. This was so that I could know which state was the latest state to be set to active between the two. Although, I figured out a much similar way to do this so problem solved :+1:. It would still be nice to know how to access a state’s history though, for future reference :slight_smile:.

Info is much appreciated :slight_smile:. Thanks.

firstly, thank you for making Arch! I am really enjoying trying to use it to build out an NPC behaviour. Most of the logic feels very familiar as I have used a similar pattern in a js library called Xstate. I do have a question though that I couldn’t see an example of in the docs.

What do you think the best way to organise code that should run per state? For example i have module scripts that import the state machine and loop continuously, if the state matches then some code is run. So a loop for idle, alerted, chasing etc. does that make sense? Is there a better way?

Thanks in advance

1 Like

For anyone else stumbling on this, putting states onto module scripts and importing them into the script creating the machine seems to work well… e.g

local idle = {}

idle.state = {
	janitor = true,
	OnEntry = function(context)
		local connection = RunService.Heartbeat:Connect(function()
			local rig = context.rig
			--do stuff here every frame
			
		end)
		context.janitor:Add(connection, "Disconnect") -- automatically disconnects the heartbeat connection when the state is exited
	end,
	events = {
		startAlert = "alerted"
	} 
}

return idle

Edit.

Having problems when transitioning from a child state into another child state. The heartbeat connection from the first isn’t disconnected and continues (or maybe restarts?!) while in the second child state.

alerted.state = {
	janitor = true,
	initial = "notFocused",
	states = {
		notFocused = {
			OnEntry = function(context)
				print("OnEntry notFocused")
				task.wait(5)
				context.Send("startFocus")
					local connection = RunService.Heartbeat:Connect(function()
					--	--do stuff here every frame
					print("every frame even when in focused state")
			end)
				
				context.janitor:Add(connection, "Disconnect") -- automatically disconnects the heartbeat connection when the state is exited
			end,
			events = {
				startFocus = "focused",
				startIdle = "idle",
			}
		},
		focused = {
			OnEntry = function()
				task.wait(1)
				print("focused")
			end
		}
	}
}
1 Like

Apologies for taking so long to respond, I am much more active on the discord. The reason you are running into that issue in the last code snippet you sent is that you set the janitor to be at the alerted state level, but you are accessing it in the child states. The janitor only runs the cleanup method whenever the state that contains it is exited. So what you want to do is put janitor = true into the notFocused state and whichever other states need direct access to the janitor.

Also, using modules for containing states is definitely a recommended practice for machine scalability.