/* ZSimulation.hpp Author: James Russell 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 #include #include #include #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& GetBaseEntities() { return BaseEntities; } ZArray& 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 void SendLocalMessage(mID type, MT payload) const { const_cast(this)->MessageQueue.Send(type, ZSIM_EID_SYSTEM, ZSIM_EID_SYSTEM, payload); } template void SendLocalMessage(mID type, eID sender, eID target, MT payload) const { const_cast(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 void SendNetworkMessage(mID type, MT payload, cID connection) const { const_cast(this)->NetworkUpdateStream.EnqueueOutgoingEvent(new (std::nothrow) ZMessageEvent(type, ZSIM_EID_SYSTEM, ZSIM_EID_SYSTEM, payload, connection)); } template void SendNetworkMessage(mID type, eID sender, eID target, MT payload, cID connection) const { const_cast(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 task); void PushTasks(ZArray>& tasks); /* Pushes a future onto the task stream, which can be used to evaluate task progress. */ template void PushFuture(ZPtr> 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 BaseEntities; // base entities that have been created (no think state) ZArray 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 PropertyDeletedStack; // list of entities that have had a property deleted ZMutex PropertyDeletedLock; // lock for manipulating PropertyDeletedStack ZHashMap EntityTypeMap; // map of entity name to initialization functions ZHashMap 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