Simulation Entry (writing gameplay code in C)
This walkthrough will show you how to create a simulation entry and what a simulation entry is.
If you wish to program gameplay using C code, then you need some way for this code to execute. You can either make lots of entity components that do inter-component communication, but if you want a more classic monolithic approach, then you can use a simulation entry.
In order for your code to execute using a simulation entry you need two things. Firstly you need an implementation of the Simulation Entry interface, tm_simulation_entry_i
(see simulation_entry.h
) and secondly you need a Simulation Entry Component attached to an entity.
Define a tm_simulation_entry_i
in a plugin like this:
static tm_simulation_entry_i simulation_entry_i = {
.id = TM_STATIC_HASH("tm_my_game_simulation_entry", 0x2d5f7dad50097045ULL),
.display_name = TM_LOCALIZE_LATER("My Game Simulate Entry"),
.start = start,
.stop = stop,
.tick = tick,
.hot_reload = hot_reload,
};
Where start
, stop
and tick
are functions that are run when the simulation starts, stop and each frame respectively. Make sure that id
is a unique identifier.
Note: There is also a plugin template available that does this, see
File -> New Plugin -> Simulation Entry
with The Machinery Editor.
Note: to generate the
TM_STATIC_HASH
you need to runhash.exe
ortmbuild.exe --gen-hash
for more info open the hash.exe guide
When your plugin loads (each plugin has a tm_load_plugin
function), make sure to register this implementation of tm_simulation_entry_i
on the tm_simulation_entry_i
interface name, like so:
tm_add_or_remove_implementation(reg, load, tm_simulation_entry_i, &simulation_entry_i);
When this is done and your plugin is loaded, you can add a Simulation Entry Component to any entity and select your registered implementation. Now, whenever you run a simulation (using Simulate Tab or from a Published build) where this entity is present, your code will run.
The same Simulation Entry interface can be used from multiple Simulation Entry Components and their state will not be shared between them.
Note: For more in-depth examples, we refer to the gameplay samples, they all use Simulation Entry.
What happens under the hood?
When the Simulation Entry Component is loaded within the Simulate Tab or Runner, it will set up an entity system. This system will run your start, stop and tick functions. You may then ask, what is the difference between using a Simulation Entry and just registering a system from your plugin? The answer is the lifetime of the code. If you register a system from your plugin, then that system will run no matter what entity is spawned whereas the Simulation Entry Component will add and remove the system that runs your code when the entity is spawned and despawned.
Example: Source Code
static struct tm_localizer_api *tm_localizer_api;
static struct tm_allocator_api *tm_allocator_api;
// beginning of the source file
#include <foundation/api_registry.h>
#include <foundation/localizer.h>
#include <foundation/allocator.h>
#include <plugins/simulation/simulation_entry.h>
struct tm_simulation_state_o
{
tm_allocator_i *allocator;
//..
};
// Starts a new simulation session. Called just before `tick` is called for the first
// time. The return value will later be fed to later calls to `stop` and `update`.
tm_simulation_state_o *start(tm_simulation_start_args_t *args)
{
tm_simulation_state_o *state = tm_alloc(args->allocator, sizeof(*state));
*state = (tm_simulation_state_o){
.allocator = args->allocator,
//...
};
//...
return state;
}
// Called when the entity containing the Simulation Entry Component is destroyed.
void stop(tm_simulation_state_o *state, struct tm_entity_commands_o *commands)
{
//...
tm_allocator_i a = *state->allocator;
tm_free(&a, state, sizeof(*state));
}
// Called each frame. Implement logic such as gameplay here. See `args` for useful
// stuff like duration of the frame etc.
void tick(tm_simulation_state_o *state, tm_simulation_frame_args_t *args)
{
//...
}
// Called whenever a code hot reload has occurred. Note that the start, tick and
// stop functions will be updated to any new version automatically, this callback is for other
// hot reload related tasks such as updating function pointers within the simulation code.
void hot_reload(tm_simulation_state_o *state, struct tm_entity_commands_o *commands)
{
//...
}
static tm_simulation_entry_i simulation_entry_i = {
.id = TM_STATIC_HASH("tm_my_game_simulation_entry", 0x2d5f7dad50097045ULL),
.display_name = TM_LOCALIZE_LATER("My Game Simulate Entry"),
.start = start,
.stop = stop,
.tick = tick,
.hot_reload = hot_reload,
};
TM_DLL_EXPORT void tm_load_plugin(struct tm_api_registry_api *reg, bool load)
{
tm_localizer_api = tm_get_api(reg, tm_localizer_api);
tm_allocator_api = tm_get_api(reg, tm_allocator_api);
tm_add_or_remove_implementation(reg, load, tm_simulation_entry_i, &simulation_entry_i);
}