Files
libsst/Include/ZSimulation/ZSimulation.hpp
2026-04-03 00:22:39 -05:00

328 lines
14 KiB
C++

/*
ZSimulation.hpp
Author: James Russell <jcrussell@762studios.com>
Created: 2/4/2013
Purpose:
Simulation base class for the ZSimulation project. Simulation is intended to be a server
that handles the simulation of a large number of interacting entities. Entities will
interact with each other via a messaging queue which will have handlers defined per
message type.
Per this simulation, Actors are simply Entities with a 'think' function defined.
The nominal difference between server and client instances is that client instances
can only ever connect to a single server instance, whereas server instances listen
for client connections and can connect to other server instances to share the
workload.
License:
Copyright 2013, 762 Studios
*/
#pragma once
#ifndef ZSIMULATION_HPP
#define ZSIMULATION_HPP
#include <SST/SST_Concurrency.h>
#include <ZSTL/ZArray.hpp>
#include <ZSTL/ZArrayAlgo.hpp>
#include <ZUtil/ZUtil.hpp>
#include "ZSimulationDefs.hpp"
#include "ZMessageStream.hpp"
#include "ZEntity.hpp"
#include "ZPropertyBuffer.hpp"
#include "ZNetworkService.hpp"
/*
Simulation class, which handles the following sphere of responsibilities:
* Fixed Timestep and Entity Update
* Entity Message Passing
* Entity Lifetime and Memory Management
* Entity Property Allocation
* Entity Property Network Synchronization
* Entity Type Declaration
* Entity State Declaration
*/
class ZSimulation : public ZThread
{
public:
// c'tor
ZSimulation();
// d'tor
virtual ~ZSimulation();
// initialization methods
bool InitAsServer(bool start = true);
bool InitAsClient(bool start = true);
// shutdown method (this will deallocate entities)
void Shutdown();
// getters
uint32_t GetCurrentEntityCount() const { return CurrentEntityCount; }
const ZEntity* GetEntity(eID entityId) const { return Entities[entityId]; }
ZEntity* GetEntity(eID entityId) { return Entities[entityId]; }
ZArray<ZEntity*>& GetBaseEntities() { return BaseEntities; }
ZArray<ZEntity*>& GetActorEntities() { return ActorEntities; }
uint64_t GetCurrentFrame() const { return CurrentFrame; }
double GetCurrentFrameStartTime() const { return CurrentFrameStartTime; }
int16_t GetSimulationHz() const { return SimulationHz; }
double GetSimulationTimestep() const { return 1.0 / (double)SimulationHz; }
size_t GetSimulationThreadCount() const { return ThreadCount; }
ZPropertyBuffer& GetPropertyBuffer() { return PropertyBuffer; }
ZRandomGenerator& GetPRNG() { return PRNG; }
uint16_t GetListenPort() { return ListenPort; }
uint8_t GetSyncRate() { return SyncRate; }
ZNetworkService& GetNetworkService() { return NetworkService; }
ZNetworkUpdateStream& GetNetworkUpdateStream() { return NetworkUpdateStream; }
uint8_t GetNetworkSendRate() { return NetSendRate; }
ZString& GetNetworkName() { return NetName; }
bool IsServer() { return bIsServer; }
// function getters (can return NULL)
ZEntity::InitFunc GetEntityInitFunc(const ZName& type);
ZEntity::InitFunc GetEntityInitFunc(ZHashValue type);
ZEntity::StateFunc GetEntityStateFunc(const ZName& name);
ZEntity::StateFunc GetEntityStateFunc(ZHashValue name);
// setters
void SetHz(int16_t simHz) { SimulationHz = simHz; }
void SetThreadCount(int16_t threadCount) { ThreadCount = threadCount; }
void SetListenPort(uint16_t port) { ListenPort = port; }
void SetSyncRate(uint8_t rate) { SyncRate = rate; }
void SetNetworkName(const ZString& name) { NetName = name; }
/*
Connection method. Connects to another simulation instance. Returns the network request
object which can be used to check status of the connection attempt. Delete the request
when done.
*/
ZNetworkRequest* Connect(const char* remoteHost);
/*
Declares an entity and initialization function. All entity initialization functions
must be declared in order to ensure proper allocation of properties on all simulations,
client and server.
It is recommended that all mapping be done during simulation initialization - no locking
or concurrent access mechanisms are used when looking up mapped data.
The initialization function can be NULL, which will create an empty entity.
Ensure when declaring entity types that the name provided has its string representation.
@assert - on name collision (name hash resolves to same or entry already exists)
*/
void DeclareEntityType(const ZName& type, ZEntity::InitFunc init);
/*
Declares an entity state function. Entity states are looked up by name to ensure recognition
on all simulations, client and server.
It is recommended that all state mapping be done during simulation initialization - no locking
or concurrent access mechanisms are used when looking up mapped data.
State functions cannot be NULL.
@assert - on name collision (name hash resolves to same or entry already exists)
*/
void DeclareEntityState(const ZName& name, ZEntity::StateFunc state);
/*
Given the name, gives a string name for an entity type or state. These exist because names
can be constructed without a string representation (such as when used for network communication).
*/
ZString LookupEntityTypeName(const ZName& type);
ZString LookupEntityStateName(const ZName& name);
/*
Spawn methods which add an entity to the simulation, returning the eID (0 if failure).
Variant which takes an eID request that an entity with a specific eID be added (returns
false if unable to create).
@assert - if the entity type is undefined
*/
eID SpawnEntity(const ZName& type);
bool SpawnEntity(const ZName& type, eID id);
/*
Kill method, which removes entities from the simulation. This call cleans up the entity and any
memory it has allocated.
*/
void KillEntity(eID id);
/*
Sends a message through the simulation message queue, which will copy data from
a struct into the message for you (MT must be a POD type). Local messages are not
reflected across the network.
*/
template <typename MT> void SendLocalMessage(mID type, MT payload) const
{ const_cast<ZSimulation*>(this)->MessageQueue.Send(type, ZSIM_EID_SYSTEM, ZSIM_EID_SYSTEM, payload); }
template <typename MT> void SendLocalMessage(mID type, eID sender, eID target, MT payload) const
{ const_cast<ZSimulation*>(this)->MessageQueue.Send(type, sender, target, payload); }
/*
Sends a message across the network, which will be processed by the message queue of
another connected simulation. MT must be POD type and in order to ensure proper
processing it should not contain pointers as these will be invalid on the other side.
*/
template <typename MT> void SendNetworkMessage(mID type, MT payload, cID connection) const
{ const_cast<ZSimulation*>(this)->NetworkUpdateStream.EnqueueOutgoingEvent(new (std::nothrow) ZMessageEvent(type, ZSIM_EID_SYSTEM, ZSIM_EID_SYSTEM, payload, connection)); }
template <typename MT> void SendNetworkMessage(mID type, eID sender, eID target, MT payload, cID connection) const
{ const_cast<ZSimulation*>(this)->NetworkUpdateStream.EnqueueOutgoingEvent(new (std::nothrow) ZMessageEvent(type, sender, target, payload, connection)); }
/*
Pushes a task onto the task stream. This task will be executed by the simulation after
simulation tasks have completed.
*/
void PushTask(ZPtr<ZTask> task);
void PushTasks(ZArray<ZPtr<ZTask>>& tasks);
/*
Pushes a future onto the task stream, which can be used to evaluate task progress.
*/
template <typename RT, typename AT> void PushFuture(ZPtr<ZFuture<RT, AT>> future) {
TaskStream.PushFuture(future);
}
/*
Pushes or Pops a message handler onto the simulation. These message handlers are used for
messages that are directed at the simulation or for messages that do not have a valid
target entity.
*/
void PopHandler(mID type);
void PushHandler(mID type, ZMessageHandler handler);
/*
These methods notify the simulation that a targeted message was handled correctly or was
unhandled. The simulation will return these messages to the message pool. These functions
are primarily used by entities as they handle their own message queues.
*/
void HandledMessage(ZMessage* msg) const;
void UnhandledMessage(ZMessage* msg) const;
/*
This method notifies the simulation that an entity has had one or more of it's properties
deleted. It's not terribly efficient to delete entity properties - use sparingly.
*/
void PropertyDeleted(eID id);
protected:
int16_t SimulationHz; // current simulation Hz (tick rate per second)
size_t ThreadCount; // number of concurrent simulation threads
double CurrentTime; // current tick count (most recent)
double TotalTime; // total number of ticks that have been processed
double Accumulator; // tick accumulator, used to help fix the timestep
double LastSync; // the time we last did a network sync
uint64_t CurrentFrame; // number of simulation frames that have passed
double CurrentFrameStartTime; // time at the start of the current frame
uint32_t CurrentEntityCount; // number of entities we have spawned
size_t CurrentEntityIndex; // next entity we will hand out for simulation entities
ZEntity* Entities[ZSIM_EID_MAX + 1]; // array mapping of entity id -> entity
ZArray<ZEntity*> BaseEntities; // base entities that have been created (no think state)
ZArray<ZEntity*> ActorEntities; // actor entities that have been created (has a think state)
ZMessageStream MessageQueue; // message queue for the simulation
ZTaskStream TaskStream; // task stream for the simulation, which concurrently processes actors
ZRandomGenerator PRNG; // PRNG for the simulation
ZNetworkService NetworkService; // network service for this simulation
ZNetworkUpdateStream NetworkUpdateStream; // the network update stream that processes sync and events
bool bIsServer; // set by which init method we call
uint16_t ListenPort; // listen port for this simulation server (0 = don't listen)
uint8_t SyncRate; // the number of times per second we should send network updates
uint8_t NetSendRate; // the rate at which the buffers are synchronized (updates per second)
ZString NetName; // the name of this server / client on the network
ZPropertyBuffer PropertyBuffer; // allocator and sync manager for entity properties
ZArray<eID> PropertyDeletedStack; // list of entities that have had a property deleted
ZMutex PropertyDeletedLock; // lock for manipulating PropertyDeletedStack
ZHashMap<ZName, ZEntity::InitFunc> EntityTypeMap; // map of entity name to initialization functions
ZHashMap<ZName, ZEntity::StateFunc> EntityStateMap; // map of entity name to initialization functions
// function to get the next entity id available
eID GetNextEntityId()
{
size_t startIndex = CurrentEntityIndex;
ZEntity* ent = Entities[CurrentEntityIndex];
while (ent != NULL)
{
CurrentEntityIndex++;
if (CurrentEntityIndex > ZSIM_EID_MAX)
CurrentEntityIndex = 0;
ZASSERT_RUNTIME(CurrentEntityIndex != startIndex, "No available entity Id's! Also, wow, that's a lot of entities.");
ent = Entities[CurrentEntityIndex];
}
return (eID)CurrentEntityIndex;
}
// Entity allocation methods (override in subclass to use specialized memory management)
virtual ZEntity* AllocEntity(eID entityId);
virtual ZEntity* AllocEntity(eID entityId, ZEntity::StateFunc startState);
virtual void DeallocEntity(ZEntity* entity);
// Thread initialization method and shutdown method (be sure to call ZSimulation::initThread and ZSimulation::shutdownThread if subclassed)
virtual void initThread();
virtual void shutdownThread();
// Overridden 'Run' Method
virtual ZThreadReturn run(uint64_t dt);
// Exposed Init and Shutdown methods for subclass simulations to implement
virtual bool SubInitServer() { return true; }
virtual bool SubInitClient() { return true; }
virtual void SubShutdown() { }
// Exposed 'Simulation Tick' method for subclass simulations to implement
virtual void Tick(double dt) { URFP(dt); }
// Some private static functions for handling the base message types
static void HandleNetworkSystemShutdown(eID sender, eID target, void* payload, ZSimulation& sim); //
static void HandleConnectionEstablished(eID sender, eID target, void* payload, ZSimulation& sim); //
static void HandleConnectionFailed(eID sender, eID target, void* payload, ZSimulation& sim); //
static void HandleConnectionLost(eID sender, eID target, void* payload, ZSimulation& sim); //
static void HandleServerNodeGained(eID sender, eID target, void* payload, ZSimulation& sim); //
static void HandleServerNodeLost(eID sender, eID target, void* payload, ZSimulation& sim); //
static void HandleSpawnEntity(eID sender, eID target, void* payload, ZSimulation& sim); // spawns an entity of a specific type
static void HandleGainState(eID sender, eID target, void* payload, ZSimulation& sim); // transitions entity to 'actor' status
static void HandleLoseState(eID sender, eID target, void* payload, ZSimulation& sim); // transitions from 'actor' to 'base entity' status
static void HandleKill(eID sender, eID target, void* payload, ZSimulation& sim); // removes an entity from the simulation
static void HandleShutdownSimulation(eID sender, eID target, void* payload, ZSimulation& sim); // handles a shutdown message (shuts down this simulation)
static void HandleSetInt(eID sender, eID target, void* payload, ZSimulation& sim); // sets the integer value of a property
static void HandleSetDbl(eID sender, eID target, void* payload, ZSimulation& sim); // sets the real number value of a property
static void HandleSetVec(eID sender, eID target, void* payload, ZSimulation& sim); // sets the vector value of a property
static void HandleSetMat(eID sender, eID target, void* payload, ZSimulation& sim); // sets the matrix value of a property
static void HandleSetStr(eID sender, eID target, void* payload, ZSimulation& sim); // sets the string value of a property
private:
DISABLE_COPY_AND_ASSIGN(ZSimulation);
};
#endif