328 lines
14 KiB
C++
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
|