Process and State
What is a
process object stores the state of some in-game object and tells the GOAL kernel how to update this object on each frame.
For example, there is a process for Jak, a process for each orb, and a process for each enemy. There is also a process for the time-of-day system and the pause menu.
In most cases,
process is used as a parent type for a specific game object. For example,
money (orb) is a child of
process-drawable, which is a child of
process-drawable is a process that can be drawn as part of the
What does a process store?
process stores a small amount (112 bytes) of metadata, fields from child classes, some unknown stuff, and a process heap. The process heap will automatically contain the "main thread" of the process, which contains space to back up the stack and registers when the thread suspends. You may also allocate objects on the process heap yourself (not supported in OpenGOAL yet).
How is a process run?
process class is a child of
process-tree, which is a left-child right-sibling binary tree. On each frame, the kernel iterates through the
*active-pool* and runs each process. Each run consists of three steps:
- Run the
trans-hookof the process in a temporary stack.
- Resume the main thread of the process.
- After the main thread suspends, run the
How do I create a process?
Setting up a process requires three steps:
- Getting an actual process object
- "Activating" a process so it will be run by the kernel
- Setting up the code for the process to run
There are a few "dead pools" which contain process objects that are not in use. The
*4k-dead-pool* contains processes that are 4kb each. There is also a dynamic pool called the
*nk-dead-pool* that allows you to create dynamically sized processes. You must do all allocations during initialization with these processes because they automatically "shrink" their heap as small as possible. Also,
*nk-dead-pool* processes will be relocated in memory as part of the process GC system, so you must make sure that all objects on the process heap support relocation, and you must use a
handle to safely refer to the process, not just a normal
For example, to get a process:
gc> (define *test-proc* (get-process *nk-dead-pool* process 1024))
#<process process dead :state #f :stack -1904/1441188 :heap 0/1024 @ #x193454>
This shows that:
- The process name is
process(just a temporary name, until we activate)
- The status is
- The process is not in a
- The stack is bogus because we don't have a main thread yet.
- We have used 0 out of 1024 bytes of our process heap.
Next, we need to activate it:
(activate *test-proc* *active-pool* 'hello *kernel-dram-stack*)
- We put it in the
*active-pool*. We could specify another process in the
*active-pool*if we wanted this to be a child process of an existing process.
- Our name is
- When we run code, it will run on the
Now, if we
(print *test-proc*) we will see:
#<process hello ready :state #f :stack 0/256 :heap 384/1024 @ #x193454>
Indicating that we are "ready" to be initialized, and that we now have a correctly set up main thread/stack.
If we run
inspect, it will print out all objects on the process heap, including our main thread:
process: #<process hello ready :state #f :stack 0/256 :heap 384/1024 @ #x193454>
suspend-hook: #<compiled function @ #x1679c4>
resume-hook: #<compiled function @ #x167b24>
rreg @ #x1934e8
freg @ #x193520
stack @ #x193540
If we want a reference to this process, we must create a handle. For example:
gc> (process->handle *test-proc*)
#<handle :process #<process hello ready :state #f :stack 0/256 :heap 384/1024 @ #x192fe4> :pid 2>
this is now a safe reference to this process, even if it is relocated or deactivated.
How do I make a process do something?
state system is used to control a process. Each process can be in a
state, which specifies what functions should run. To switch states in the current process, use
For example, we can create a simple test state like this:
(defstate test-state (process)
:enter (lambda () (format #t "enter!~%"))
:exit (lambda () (format #t "exit!~%"))
:trans (lambda () (format #t "trans!~%"))
:post (lambda () (format #t "post!~%"))
:code (lambda ()
(dotimes (i 5)
(format #t "Code ~D~%" i)
code is the function to run in the main thread. This code should
suspend itself, and the kernel will resume it after the suspend on each frame. Once the process is done, it can call
process-deactivate. This will cause it to exit the current state, immediately exit the
code, and clean up the process, returning it to the dead pool.
To switch the process to this state, you can use the
run-now-in-process to switch to the test process and run the given code.
(run-now-in-process *test-proc* (lambda () (go test-state)))
And you will see:
Note 1: After deactivation, the handle is no longer valid as the process is dead and it will print like this:
#<handle :process #f :pid 2>
Note 2: There is also a
run-next-time-in-process that sets up the process to run your initialization stub function as the
code on the next time the kernel iterates through the process tree.
Some notes on "the current process"
When the kernel runs a process, it sets
(-> *kernel-context* current-process) and the
pp register to that process. This process is called the "current kernel process".
This process may then "run code in another process". This can be done with
run-now-in-process, by deactivating another process, or using
go on another process. This changes
pp, but not the kernel context. The process in
pp is called the "current pp process".
The value of the
pp register determines the current process.
Some notes on
To stop a process, you can do call the
deactivate method of that process. The
process-deactivate macro just does this for the current process.
This does the following:
- Set state to
entity-deactivate-handler, if you have an entity
- Cleans up any pending
protect-frame(calling them with pp set for the process)
- Disconnects it from the
- Deactivates all children process
- Returns itself to the pool
- If you deactivated the process that the kernel-dispatcher started running, immediately bail out of the thread
- If you deactivated during a
run-now-in-process, immediately bail out of the initialization and return to caller of
Some notes on
go macro is used to change the state of the current process.
If you use
go when in
run-now-in-process, it will immediately return to the caller of
run-now-in-process, and the actual state change will happen on the next execution of the main thread of that process.
If you use
go-process on another process, the
go-process will return immediately and the state transition will happen on the next run of that process.
If you use
go in the main thread, it will immediately transition states, run exits, enter, trans, and begin running the new state
If you use
trans it will set up the next run of the main thread, then abandon the current
If you use
post, it will set up the next run of the main thread to transition, but not abandon the current