Initial commit

This commit is contained in:
2026-04-03 00:22:39 -05:00
commit eca1e8c458
945 changed files with 218160 additions and 0 deletions

View File

@@ -0,0 +1,379 @@
/*
ZEntity.hpp
Author: James Russell <jcrussell@762studios.com>
Created: 2/5/2013
Purpose:
Entity class for the ZSimulation framework.
All objects affecting the simulation will exist as an entity. Entities can either
be created with state or without state. Entities with and without state can
be messaged by other entities within the simulation. Entities with state
have their state function called once per tick, and this state function
can modify the entity state, making the entity an autonomous state machine.
// ENTITY MESSAGING //
Entities are created in order to be processed in a threaded fashion. When
entities have their current state function called, they should be expected to be
able to process this in a thread-safe manner, able to call their own non-const methods
but only able to affect other entities via messaging.
After the state functions are called, remaining messages are delivered to the entity
'mailbox'. Any action that could mutate an entity that is not done from within the
call to the state function must be handled via a message.
These delivered messages are then processed in a multi threaded manner, but each entity
has it's messages processed serially, meaning that modifications can take place as per
normal.
Entity messages are processed by deferring to the handler installed for a particular
message type on an entity. If no message handler is installed for that message type
on an entity, the message handler installed for that type on the simulation message
stream will be called. If no message handler is installed there, the message is
dropped.
// ENTITY PROPERTIES //
Entities hold their state via allocation into the simulation 'property buffer', which
allows for contiguous space allocation for properties to be stored. The property
buffer is a raw memory allocator and does not care what the underlying data actually
is.
Entity properties can be allocated as 'local' or 'synchronized'. Data stored locally
is not updated to other simulation instances. Data stored in a 'synchronized' state is
mirrored to other simulation instances.
License:
Copyright 2013, 762 Studios
*/
#pragma once
#ifndef ZENTITY_HPP
#define ZENTITY_HPP
#include <ZUtil/ZRandomGenerator.hpp>
#include <ZUtil/ZName.hpp>
#include "ZSimulationDefs.hpp"
#include "ZPropertyBuffer.hpp"
#include "ZStringBuffer.hpp"
// forward decl
class ZSimulation;
struct ZMessage;
/*
Entity class.
*/
class ZEntity
{
public:
/*
This is the entity state typedef, which corresponds to 'AI State'. The current think state
is called once per simulation tick.
@param entity - the entity who is thinking
@param sim - the running simulation
@param dt - the time (expressed in seconds) since last call of this function
*/
typedef void (*StateFunc)(ZEntity& entity, const ZSimulation& sim, double dt);
/*
This is the entity message handler typedef, which corresponds to 'Behavior'. If there is no message
handler installed for a given message type, the default handler installed in the message queue
is used.
@param entity - the entity receiving the message
@param sim - the running simulation
@param sender - the sending entity
@param payload - the payload of the message
*/
typedef void (*MessageFunc)(ZEntity& entity, const ZSimulation& sim, eID sender, void* payload);
/*
This is the entity initialization function typedef, which can be passed to the simulation to initialize
the entity upon creation.
@param entity - the entity to initialize
@param sim - the running simulation
*/
typedef void (*InitFunc)(ZEntity& entity, const ZSimulation& sim);
/*
Entity property class. An ease of access class for reading and writing
property data.
This class does not handle type coercion on read. Types will change when
values are set.
*/
struct Property {
// we'll be using this a lot
typedef ZStringBuffer::StringKey StringKey;
enum {
BOOL, // boolean type
INT, // integer type
FLOAT, // floating point type
DOUBLE, // double precision floating point type
STRING, // string type (stored in string buffer or dynamic, limited size)
VECTOR, // vector type (4 floating point values)
MATRIX, // matrix type (16 floating point values)
NONE // untyped data
} Type; // the type of property
ZString LocalString; // local storage for strings
uint8_t Local[64]; // local storage for simple types
void* Data; // data for more complex types
size_t Size; // size of data (in bytes)
// default constructor, which initializes an empty string
Property();
// generic and array constructor, which takes data and size
Property(void* data, size_t size);
// value constructors
Property(const Property& other);
Property(const bool val);
Property(const int val);
Property(const float val);
Property(const double val);
Property(const char* val); // checks to see if this string has a string key, if not makes a dynamic string
Property(const StringKey val); // quicker method of directly creating a static string type
Property(const SST_Vec4f& val);
Property(const SST_Vec3f& val); // convenience for SST_Vec4f(x, y, z, 0.0)
Property(const SST_Vec2f& val); // convenience for SST_Vec4f(x, y, 0.0, 0.0)
Property(const SST_Mat44f& val);
// assignment operators
Property& operator = (const Property& other);
Property& operator = (const bool& val);
Property& operator = (const int& val);
Property& operator = (const float& val);
Property& operator = (const double& al);
Property& operator = (const char* val);
Property& operator = (const StringKey val); // quicker method of using a static string type
Property& operator = (const SST_Vec4f& val);
Property& operator = (const SST_Vec3f& val); // convenience for SST_Vec4f(x, y, z, 0.0)
Property& operator = (const SST_Vec2f& val); // convenience for SST_Vec4f(x, y, 0.0, 0.0)
Property& operator = (const SST_Mat44f& val);
// cast operators
operator bool () const;
operator int () const;
operator float () const;
operator double () const;
operator char* () const;
operator bool* () const;
operator int* () const;
operator float* () const;
operator double* () const;
operator SST_Vec4f () const;
operator SST_Vec3f () const; // gets [x, y, z] only
operator SST_Vec2f () const; // gets [x, y] only
operator SST_Mat44f () const;
};
// c'tor
ZEntity(eID entityId, ZSimulation& sim);
// parameterized c'tor (creates an actor)
ZEntity(eID entityId, ZSimulation& sim, StateFunc startState);
// d'tor
~ZEntity();
//////////////////////////////////////////////////////////////////////////
// Member Data Getter Functions
eID GetId() const;
ZRandomGenerator& GetPRNG();
//////////////////////////////////////////////////////////////////////////
// Property Manipulation Functions
/*
Property access functions. Used to create, remove, read, and write properties
and their values.
The 'HasProperty' function will return true if the named property exists on
this entity.
The 'CreateSyncProperty' functions is used to declare a synchronized property
on this entity. Synchronized properties are automatically mirrored across
the network system whenever a change is made. If the 'owner' boolean is
set to true, modifications to the entity on this simulation are synchronized
to other simulations. If the owner boolean is set to false, modifications
are not synchronized, and the property will be updated periodically from
other simulations. Only one simulation should be the owner of an entity,
or they will update over each other.
The 'GetProperty' function will fill out a Property instance that references
the read value of the named property. If the named property does not exist
on this entity, the function will return false.
The 'SetProperty' function will set the write value of the property to be
the value provided. If the named property does not exist, it will be created
as a non-synchronized property. Note that the read value is not updated until
the next simulation tick.
The 'EraseProperty' function will queue the property for removal at the end
of this tick, which allows 'get' operations to function as normal until the
next tick.
The template 'Get' function is a shortcut to directly get the value of
a specific property. Note that if the property does not exist, this will
return a default constructed property value.
*/
bool HasProperty(const ZName& name) const;
void CreateSyncProperty(const ZName& name, const Property& prop, bool owner);
bool GetProperty(const ZName& name, Property& val) const;
void SetProperty(const ZName& name, const Property& val);
void EraseProperty(const ZName& name);
template <typename T>
T Get(const ZName& name) const
{ Property prop; GetProperty(name, prop); return (T)prop; }
template <typename T>
void Set(const ZName& name, const T val)
{ Property prop(val); SetProperty(name, prop); }
/*************************************************************************/
/* State Management Functions */
/*
State functions. Used to get information about the current state and
manipulate the state stack, as well as call the current state function.
Push and Pop operate as you would expect for a state stack. The maximum
number of states is defined by ZENT_STACK_SIZE.
SetState sets the current state to the given state, replacing it.
ResetState clears the state stack and sets the current state to be the
provided one.
*/
StateFunc GetCurrentState() const;
double GetTimeInCurrentState() const;
void PushState(StateFunc state);
void PopState();
void SetState(StateFunc state);
void ResetState(StateFunc state);
/*
State property stack manipulation functions. Used to read and write information
to the state property stack, which can be used to pass transient information from
one state function to another.
*/
size_t GetStatePropertyCount() const;
Property PopStateProperty();
void PushStateProperty(const Property& prop);
void ClearStatePropertyStack();
/*************************************************************************/
/* Messaging Management Functions */
/*
Messages, once posted to the message queue, can be 'delivered' to entities for
sorting purposes to be later processed in a convenient fashion.
When processing delivered messages, order of delivery is maintained. Any
messages that do not have a corresponding handler type installed will be
handled by the handler installed in the message stream. If no corresponding
handler is installed there, the message is dropped, and the simulation is
notified.
*/
void DeliverMessage(ZMessage* msg);
void ProcessMessages();
void PushMessageHandler(mID type, MessageFunc handler);
void PopMessageHandler(mID type);
void ClearMessageHandlers();
protected:
/*
Typedef for the property map used by ZEntity. It has a bucket size of 32, local
storage for the buckets of 32, 10 local nodes for each hash-chain, 10 local storage
for each property array, and will not resize based on load factor. Uses a 64-bit
hash value.
*/
typedef ZHashMap<ZName, ZPropertyBuffer::Property, int64_t, ZHasher<ZName, int64_t>, 0> PropMap;
/*
Stack used to indicate which properties have been modified since last swap of properties.
*/
typedef ZArray<ZPair<ZName, ZPropertyBuffer::Property>> PropModifiedStack;
/*
Stack used to indicate which properties have been deleted since last swap of properties.
*/
typedef ZArray<ZName> PropDeletedStack;
/*
Stack used for state properties, which can be used to send data from one state to another.
*/
typedef ZArray<Property> StatePropStack;
/*
Buffer used as an entity mailbox - targeted messages are delivered here.
*/
typedef ZRingBuffer<ZMessage*> Mailbox;
/*
Typedef for the handler map used by ZEntity, which maps message type to a
stack of handlers. Only keeps two local nodes in the allocator, and will not
resize based on load factor.
*/
typedef ZHashMap<mID, ZArray<MessageFunc>, int64_t, ZHasher<ZName, int64_t>, 0,
ZListPooledAllocator<ZHashNode<mID, ZArray<MessageFunc>, int64_t>, 2 >> HandlerMap;
/* Member Data */
ZSimulation& Sim; // the simulation running this thing
const eID Id; // the id of the entity
ZRandomGenerator PRNG; // entity PRNG, kept so there are no thread conflicts with PRNG access
/* Property Management */
PropMap PropertyMap; // entity properties map
PropModifiedStack PropertyModifiedStack; // properties that need to be mirrored into the read properties map
PropDeletedStack PropertyDeletedStack; // properties have been deleted
/* State Management */
int StateIndex; // index to our current state (-1 indicates no state)
StateFunc StateStack[ZSIM_ENT_STATE_STACK_SIZE]; // stack of states (StateStack[StateIndex] is our current)
StatePropStack StatePropertyStack; // stack of properties so that states can pass data to each other
double StateTime; // amount of time we have spent in our current state
/* Message Management */
Mailbox MessageMailbox; // entity 'Mailbox', which is where messages get delivered to
HandlerMap MessageHandlers; // entity handlers, used to process received messages
private:
friend class ZSimulation;
friend class ActorTickTask;
DISABLE_COPY_AND_ASSIGN(ZEntity);
// allocates a new property
// TODO
// called by the simulation to tick the current state
void StateTick(double dt);
// called by the simulation to dealloc properties
void HandlePropertyDelete();
};
#endif

View File

@@ -0,0 +1,153 @@
/*
ZMessageStream.hpp
Author: James Russell <jcrussell@762studios.com>
Created: 2/4/2013
Purpose:
Messaging stream class, which acts as an event loop for the entire simulation. Processes both
untargeted (or 'simulation' targeted) messages and messages that are intended for a specific entity.
Handles organization and sorting so that the messages can be processed in a thread safe fashion at
a later time.
License:
Copyright 2013, 762 Studios
*/
#pragma once
#ifndef _ZMESSAGESTREAM_HPP
#define _ZMESSAGESTREAM_HPP
#include <ZUtil/ZConcurrency.hpp>
#include <ZUtil/ZSlabAllocator.hpp>
#include "ZSimulationDefs.hpp"
//Forward Declarations
class ZSimulation;
/*
Message structure. Messages are in the form of a POD struct of a set size
payload. Message layout definitions (see ZSimulationDefs.hpp for examples)
can be used to interpret the data.
*/
struct ZMessage {
mID Type; // type of the message
eID Sender; // the entity who sent the message (ZSIM_EID_SYSTEM means system or simulation)
eID Target; // the target of the message (ZSIM_EID_SYSTEM calls the default handler)
char Payload[ZSIM_MESSAGE_SIZE]; // the message payload, which can be interpreted via layout definitions
};
/*
Message handler function profile.
*/
typedef void (*ZMessageHandler)(eID _sender, eID _target, void* _payload, ZSimulation& _sim);
/*
Message streaming class, used to send messages from one entity to another or to the simulation.
*/
class ZMessageStream
{
public:
// c'tor
ZMessageStream();
// d'tor
~ZMessageStream();
// message 'Send' method, which will copy data from a struct into the message (MT must be a POD type)
template <typename MT>
void Send(mID _type, eID _sender, eID _target, MT _payload)
{
ZMessage* msg;
#if SST_COMPILER == SST_COMPILER_MSVC
#pragma warning(push)
#pragma warning(disable:4127)
#endif
ZASSERT(sizeof(MT) <= ZSIM_MESSAGE_SIZE, "Message sent with payload too large!");
#if SST_COMPILER == SST_COMPILER_MSVC
#pragma warning(pop)
#endif
// Synchronized Section
{
ZLock lock(AllocatorMutex);
msg = MessageAllocator.Allocate();
}
ZASSERT(msg != NULL, "Unable to allocate message!");
msg->Type = _type;
msg->Sender = _sender;
msg->Target = _target;
MemCopy(uint8_t, msg->Payload, &_payload, sizeof(MT));
// Synchronized Section
{
ZLock lock(BufferMutex);
MessageBuffers[CurrentBufferIndex].PushBack(msg);
}
}
/*
Pumps the message queue, processing the messages that have been sent since last time this method was
called. Returns true if additional messages were generated during the processing of the current set
of messages.
Messages that are targeted at a specific entity will be delivered to that entity. If that entity cannot
be found, the message is dropped. Messages that target the reserved eID of '0' will be handled by the
message stream handlers.
Only one thread should ever be calling this method. Multiple threads calling this method will result
in that thread waiting until the previous has finished.
Returns true if messages are still available for processing, false if the current queue is empty.
*/
bool ProcessMessages(ZSimulation& _sim);
/*
External message handlers. Used to return an entity targeted message or process a targeted
message that had no installed handler.
*/
void HandleMessage(ZMessage* _msg, ZSimulation& _sim);
void ReturnMessage(ZMessage* _msg);
/*
Push and Pop Methods for Handlers.
*/
void PushHandler(mID _type, ZMessageHandler _handler);
ZMessageHandler PopHandler(mID _type);
protected:
ZMutex AllocatorMutex; // lock for allocators
ZMutex BufferMutex; // lock for buffers
ZMutex HandlerMutex; // lock for handlers
ZMutex ProcessingMutex; // lock for processing
int CurrentBufferIndex; // index to our current message buffer
ZRingBuffer<ZMessage*> MessageBuffers[2]; // the current set of messages (one receives messages while the other is processed)
ZHashMap<mID,
ZArray<ZMessageHandler>> MessageHandlers; // set of handler functions
ZSlabAllocator<ZMessage,
ZSIM_MESSAGE_ALLOC> MessageAllocator; // slab allocator for messages
private:
DISABLE_COPY_AND_ASSIGN(ZMessageStream);
};
#endif

View File

@@ -0,0 +1,165 @@
/*
ZNetworkEvent.hpp
Author: James Russell <jcrussell@762studios.com>
Created: 9/13/2015
Purpose:
Network Event abstract class. Intended to be subclassed into various subclasses that will be
serialized / deserialized and processed on both client and server.
Note that events that originate on the server will generally not have their HandleServer method
called, as the server already processed the occurrence that originated the event. Events
that originate on the client will generally be pushed to the server, have HandleServer
called on them, which will generally also cause an event to be pushed to each client, who
will then have HandleClient called when the event gets deserialized.
From the above usage pattern, we can see that clients generally push events to the server but
do not process the local occurrence (the HandleClient) until after the server has confirmed
the event happens by pushing the event back to all clients, the original sender included.
This usage pattern is not required for client authoritative events, which are processed locally
and then handed to the server to notify other attached clients.
License:
TODO
*/
#pragma once
#ifndef _ZNETWORKEVENT_HPP
#define _ZNETWORKEVENT_HPP
#include "ZSimulationDefs.hpp"
// forward decl
class ZSimulation;
/*
ZNetworkEvent class.
*/
class ZNetworkEvent {
DISABLE_COPY_AND_ASSIGN(ZNetworkEvent);
public:
nID Type; // the type of event this is
cID NetTarget; // the connection this is targeting
cID NetIgnore; // if NetTarget is set to all, this target will be ignored
bool InQueue; // flag set during a handle method to indicate this has been queued up for sending
// handler for events on client side
virtual void HandleClient(ZSimulation& server_sim) = 0;
// handler for events on server side
virtual void HandleServer(ZSimulation& client_sim) = 0;
// called by the network system to deserialize this event from binary form (after header is read)
virtual bool Deserialize(ZBinaryBufferReader& reader) = 0;
// called by the network system to serialize this event into binary form
virtual void Serialize(ZBinaryBufferWriter& writer) = 0;
// called to get the serialized size of this event (including uint8_t message type flag)
virtual size_t GetSerializedSize() = 0;
// called by subclass to serialize the header (Type, PlayerSource, and PlayerTarget)
void SerializeHeader(ZBinaryBufferWriter& writer) {
writer.WriteU32(Type);
}
// called by subclass to get serialized header size
size_t GetHeaderSize() {
return sizeof(nID);
}
protected:
// c'tor
ZNetworkEvent(nID type) : Type(type) { }
};
/*
Network Event base implementation, which attempts to automatically handle serialization and
deserialization via template POD structs.
Note the template on the game event (DS), which indicates a POD struct. This is the data
that is serialized and sent as part of the event. Wrap the struct definition in pragma
pack statements to ensure correct behavior across systems and reduce bandwidth.
*/
template <typename DS>
class ZNetworkEventBase : public ZNetworkEvent
{
DISABLE_COPY_AND_ASSIGN(ZNetworkEventBase);
public:
// the data struct for this event
DS EventData;
// called by the network system to deserialize this event from binary form (after header is read)
virtual bool Deserialize(ZBinaryBufferReader& reader) {
if (reader.CanRead(sizeof(DS))) {
reader.ReadU8Array((uint8_t*)&EventData, sizeof(DS));
return true;
} else return false;
}
// called by the network system to serialize this event into binary form
virtual void Serialize(ZBinaryBufferWriter& writer) {
SerializeHeader(writer);
writer.WriteU8Array((uint8_t*)&EventData, sizeof(DS));
}
// called to get the serialized size of this event
virtual size_t GetSerializedSize() {
return GetHeaderSize() + sizeof(DS);
}
protected:
// c'tor
ZNetworkEventBase(nID type) : ZNetworkEvent(type) { }
};
#pragma pack (push, 1)
/*
Data struct used for message events, which will simply create the
given message on the other side and pass it to the simulation.
*/
template <typename M>
struct ZMessageEventData {
mID Type;
eID Sender;
eID Target;
M MessageData;
cID Connection;
};
#pragma pack (pop)
/*
Message network event. This is used to duplicate a message on other
connected simulations. The template parameter type is the message
data definition struct. Be sure to use pragma pack to reduce network
overhead.
*/
template <typename MT>
class ZMessageEvent : public ZNetworkEventBase<ZMessageEventData<MT>> {
public:
// c'tor
ZMessageEvent(mID msg_type) : ZNetworkEventBase(0) { }
ZMessageEvent(mID msg_type, eID sender, eID target, MT payload, cID connection)
: ZNetworkEventBase(0), EventData({msg_type, sender, target, connection, payload}) { }
// subclass implementations
virtual void HandleClient(ZSimulation& server_sim) {
server_sim.SendLocalMessage(Data.Type, Data.Sender, Data.Target, Data.MessageData);
}
virtual void HandleServer(ZSimulation& client_sim) {
client_sim.SendLocalMessage(Data.Type, Data.Sender, Data.Target, Data.MessageData);
}
};
#endif

View File

@@ -0,0 +1,82 @@
/*
NetworkRequest.hpp
Author: Patrick Baggett <ptbaggett@762studios.com>, James Russell <jcrussell@762studios.com>
Created: 12/03/2013
Purpose:
Cancel-able network thread request, similar to ZThreadRequest, but
with more cancellation points.
Used to communicate between any other thread and the NetworkService
thread. The "payload" field contains the message's data, and the event
is used to cancel and signal when the request has been handled. For some
requests, no response is required; these are called "responseless" and
have no SST_Event object associated. Obviously, they cannot be waited on
nor canceled -- they are simply "fire and forget".
License:
Copyright 2013, 762 Studios.
*/
#ifndef __ZNETWORKREQUEST_HPP
#define __ZNETWORKREQUEST_HPP
#include <SST/SST_Concurrency.h>
class ZNetworkRequest
{
public:
// result enumeration
enum RequestResult {
RESULT_CANCELED, // nope
RESULT_FINISHED, // yep
RESULT_INPROGRESS // go away
};
// type enumeration
enum RequestType {
REQUEST_CONNECT, // connect to another simulation
REQUEST_DISCONNECT, // disconnect from a server
REQUEST_RESET, // immediately disconnect (no notice)
REQUEST_STOP_NETWORK, // stop the network thread
};
// public member variables
uint8_t Payload[64]; // generic data payload, used to input data and read results back
RequestType Type; // request type
RequestResult Result; // in progress, success, or failure
// c'tor
ZNetworkRequest(RequestType type, bool needEvent = true)
: Type(type), Result(RESULT_INPROGRESS) {
FinishedEvent = needEvent ? SST_Concurrency_CreateEvent() : NULL;
SST_Atomic_StoreRelease(&Canceled, 0);
}
// d'tor
~ZNetworkRequest() {
if (FinishedEvent) {
SST_Concurrency_DestroyEvent(FinishedEvent);
}
}
// getters
bool IsResponseless() { return (FinishedEvent == NULL); }
bool WaitFinished() { return (SST_Concurrency_WaitEvent(FinishedEvent, SST_WAIT_INFINITE) != 0); }
bool CheckFinished() { return (SST_Concurrency_WaitEvent(FinishedEvent, 0) != 0); }
void Cancel() { Canceled = 1; }
bool IsCancelPending() { return (Canceled != 0); }
// used to signal the request should be finished or canceled
void MarkFinished() { Result = RESULT_FINISHED; SST_Concurrency_SignalEvent(FinishedEvent); }
void MarkCanceled() { Result = RESULT_CANCELED; SST_Concurrency_SignalEvent(FinishedEvent); }
private:
SST_Event FinishedEvent; // when signaled, the request has been completed
volatile int Canceled; // when non-zero, the network thread will not attempt to process it
};
#endif

View File

@@ -0,0 +1,181 @@
/*
ZNetworkService.hpp
Author: Patrick Baggett <ptbaggett@762studios.com>, James Russell <jcrussell@762studios.com>
Created: 12/03/2013
Purpose:
Network functionality for ZSimulation. Operates a thread that handles sending and
receiving of network data. Can be initialized as a server instance of a client
instance, with the real difference being that client instances connect to a single
server and servers listen for connections from multiple clients and other server
instances.
License:
Copyright 2013, 762 Studios.
*/
#pragma once
#ifndef __ZNETWORKSERVICE_HPP
#define __ZNETWORKSERVICE_HPP
#include <enet/enet.h>
#include <ZUtil/ZUtil.hpp>
#include "ZRemotePeer.hpp"
#include "ZNetworkRequest.hpp"
#include "ZNetworkUpdateStream.hpp"
// forward decl
class ZSimulation;
// class decl
class ZNetworkService
{
public:
// c'tor
ZNetworkService(ZSimulation& sim);
/*
Initializes the network system as client or server. The name is for display
purposes for both client and server.
If initialized as a server, the maximum number of simultaneous connections must
be specified.
*/
bool InitAsClient(const char* name);
bool InitAsServer(uint32_t maxConnections, uint16_t listenPort, const char* name);
/*
As a client, initializes a connection to the provided remote host. As a server,
links to another server instance to share workload. Delete this when done.
*/
ZNetworkRequest* InitiateConnection(const char* remoteHost, uint16_t port);
/*
As a client, disconnects from the server (cID for server is always zero). As
a server, disconnects the given connection. This is a soft disconnect that
notifies the other side and waits for a response. Delete this when done.
*/
ZNetworkRequest* Disconnect(cID id);
/*
Generates the disconnection event immediately, but gives no notice to the other
side. Delete this when done.
*/
ZNetworkRequest* ResetConnection(cID id);
/*
Given a connection id, gets the associated remote peer object.
*/
ZRemotePeer* GetPeer(cID id);
/*
Given the network name, gets the associated id. Returns cID(-1) if not connected
to this simulation instance.
*/
cID GetId(const char* netname);
/*
Gets a list of node and host pairings on the network. If the pairing exists in both
directions, then the connection is between two servers.
*/
void GetTopography(ZArray<ZPair<ZString, ZString>>& mappings);
// thread control methods
void SignalAndWaitShutdown();
void ThreadMain();
private:
// spawns the network thread
void SpawnThread();
// main thread = these four functions, in a loop
void PollNetEvents();
void ProcessRequests();
void PushBufferUpdates(uint64_t dt);
void PushNetworkEvents(uint64_t dt);
// handlers for requests (return value indicates 'done')
bool HandleRequest(ZNetworkRequest* req);
bool HandleConnectRequest(ZNetworkRequest* req);
bool HandleDisconnectRequest(ZNetworkRequest* req);
bool HandleResetRequest(ZNetworkRequest* req);
bool HandleStopNetworkRequest(ZNetworkRequest* req);
// handlers for ENet Events
void HandleEvent(ENetEvent* ev);
// server side
void ServerHandleConnectEvent(ENetEvent* ev); // initial connection event from ENet
void ServerHandleDisconnectEvent(ENetEvent* ev); // disconnect event from ENet
void ServerHandleReceiveEvent(ENetEvent* ev); // data received (any channel)
void ServerReceiveJoinMessage(ZRemotePeer* client, const void* data, uint32_t len); // process client (or server) join message
void ServerReceiveSystemChannel(ZRemotePeer* client, const void* data, uint32_t len); // data received
void ServerReceiveUpdateChannel(ZRemotePeer* client, const void* data, uint32_t len); // data received on update channel
void ServerReceiveEventChannel(ZRemotePeer* client, const void* data, uint32_t len); // data received on event channel
// client side
void ClientHandleConnectEvent(ENetEvent* ev); // initial connect event from ENet
void ClientHandleDisconnectEvent(ENetEvent* ev); // disconnect event from ENet
void ClientHandleReceiveEvent(ENetEvent* ev); // data received (any channel)
void ClientRecieveJoinResponse(const void* data, uint32_t len); // process server response from join message
void ClientReceiveSystemChannel(const void* data, uint32_t len); // data received on system channel
void ClientReceiveUpdateChannel(const void* data, uint32_t len); // data received from server on update channel
void ClientReceiveEventChannel(const void* data, uint32_t len); // data received from server on event channel
// adds and removes a network node mapping
void AddNode(const ZString& node, const ZString& host);
void RemoveNode(const ZString& node);
enum Netstate {
UNINITIALIZED, // not yet initialized as client or server
DISCONNECTED, // initialized as client and disconnected
LISTEN, // initialized as server and listening for connections
CONNECTING, // initiating low-level connection
JOINING, // connected, but not yet ready to play
CONNECTED // connected and ready to SIMULATE
};
ZSimulation& Sim; // simulation reference
ZString Name; // network name
Netstate State; // state of this service
SST_Thread Thread; // thread running this service
bool bIsServer; // true if initialized as server
bool bIsConnected; // true if connected
volatile int iShouldShutdown; // shutdown flag
ENetHost* Host; // enet host service (server and client)
ENetPeer* RemoteServer; // as a client, this is the server (otherwise NULL)
uint32_t NrConnectionsMax; // max number of connections
ZRemotePeer* Connections; // all connections (as client, only a single remote peer, the server)
ZMutex NodesMutex; // mutex for locking access to nodes listing
ZArray<ZPair<ZString, ZString>> Nodes; // node name and host name pairing for all simulations on the network
void* PacketBuffer; // packet data buffer, used as local copy
size_t PacketBufferSize; // size of the buffer
ZMutex QueueMutex; // mutex for locking access to request queue
ZArray<ZNetworkRequest*> SharedRequestQueue; // network request queue
ZNetworkRequest* CurrentRequest; // network request currently being processed
uint32_t WaitTime; // value used to modify polling frequency
ZArray<ZNetworkRequest*> PendingRequests; // queue used to process a single tick worth of requests
NetworkUpdateQueue DelayedUpdates; // updates that are being delayed for artificial lag simulation
NetworkEventQueue DelayedEvents; // events that are being delayed for artificial lag simulation
};
#endif

View File

@@ -0,0 +1,114 @@
/*
ZNetworkUpdateStream.hpp
Author: James Russell <jcrussell@762studios.com>
Created: 9/13/2015
Purpose:
Exists for the express purpose of holding onto network events and property updates
that the simulation wants to send out, and providing them to the network system when
the network system requires them.
In addition, holds onto network events and updates received until the simulation asks
for them.
License:
TODO
*/
#pragma once
#ifndef _ZNETWORKUPDATESTREAM_HPP
#define _ZNETWORKUPDATESTREAM_HPP
#include <ZSTL/ZArray.hpp>
#include <ZUtil/ZUtil.hpp>
#include "ZNetworkEvent.hpp"
#include "ZPropertyBufferUpdate.hpp"
// forward decl
class ZSimulation;
// typedefs
typedef ZArray<ZNetworkEvent*, ZArrayAllocator<ZNetworkEvent*, 128>> NetworkEventQueue;
typedef ZArray<ZPropertyBufferUpdate*, ZArrayAllocator<ZPropertyBufferUpdate*, 1024>> NetworkUpdateQueue;
// class decl
class ZNetworkUpdateStream {
public:
// event constructor and destructor function
typedef ZNetworkEvent* (*EventConstructor)();
typedef void (*EventDestructor)(ZNetworkEvent*);
// c'tor
ZNetworkUpdateStream();
// d'tor
~ZNetworkUpdateStream();
/*
Maps an event type to an event constructor and destructor. Events will not be properly recreated
unless the type is mapped to a constructor function.
*/
void MapEventAlloc(nID type, EventConstructor ctor, EventDestructor dtor);
/*
Allocator for events and updates. The version without id will be called by the network
system - the deserialize method will fill out the id.
*/
ZNetworkEvent* AllocEvent(nID type);
ZPropertyBufferUpdate* AllocUpdate(eID id);
ZPropertyBufferUpdate* AllocUpdate();
/*
Deallocation for events and updates.
*/
void DeallocEvent(ZNetworkEvent* e);
void DeallocUpdate(ZPropertyBufferUpdate* u);
/*
Enqueues an event or update to sync across the network.
*/
void EnqueueOutgoingEvent(ZNetworkEvent* e);
void EnqueueOutgoingUpdate(ZPropertyBufferUpdate* u);
void EnqueueOutgoingUpdates(NetworkUpdateQueue& q);
/*
Enqueues an event or update to be processed here.
*/
void EnqueueIncomingEvent(ZNetworkEvent* e);
void EnqueueIncomingEvents(NetworkEventQueue& q);
void EnqueueIncomingUpdate(ZPropertyBufferUpdate* u);
void EnqueueIncomingUpdates(NetworkUpdateQueue& q);
/*
Read outgoing events or updates to sync across the network.
*/
void ReadOutgoingEvents(NetworkEventQueue& out);
void ReadOutgoingUpdates(NetworkUpdateQueue& out);
/*
Processes events or updates that have been enqueued.
*/
void ProcessIncomingEvents(ZSimulation& sim);
void ProcessIncomingUpdates(ZSimulation& sim);
private:
DISABLE_COPY_AND_ASSIGN(ZNetworkUpdateStream);
ZMutex EventMutex[2]; // mutex for event queues (incoming / outgoing)
ZMutex UpdateMutex[2]; // mutex for update queues (incoming / outgoing)
NetworkEventQueue Events[2]; // buffered events (incoming / outgoing)
NetworkUpdateQueue Updates[2]; // buffered updates (incoming / outgoing)
ZHashMap<nID,
ZPair<EventConstructor, EventDestructor>> Constructors; // ctor and dtor for network events
};
#endif

View File

@@ -0,0 +1,454 @@
#include <ZSimulation/ZPropertyBuffer.hpp>
#include <ZSimulation/ZNetworkUpdateStream.hpp>
#include <SST/SST_SysMem.h>
#define BUFFER_SYNC 0
#define BUFFER_LOCAL 1
#define BUFFER_WRITE 0
#define BUFFER_READ 1
#define MIN_ALLOC 4
struct PropertyPage {
eID Id; // id of the entity that these properties are allocated for
void* Data[2]; // the data for this page (read and write)
size_t Size; // amount of data on this page
size_t Offset; // the current offset on this page
PropertyPage* Next; // the next page, or NULL
ZArray<ZPair<size_t, size_t>> Avail; // available data slots on this page
ZArray<ZPair<ZPropertyBuffer::PropertyKey, size_t>> Updates; // properties that have been updated since last update
// c'tor
PropertyPage(size_t alloc_size) : Next(NULL) {
Data[0] = SST_OS_AllocPages(alloc_size*2); // allocate 2x to account for read and write
Data[1] = (char*)Data[0] + alloc_size; // split in half, with first half being write and second for read
Size = alloc_size;
Offset = 0;
}
// d'tor
~PropertyPage() {
delete Next;
if (Size > 0) {
SST_OS_FreePages(Data[0], Size*2);
}
}
};
/*************************************************************************/
// Key is currently a 64 bit integer, which is partitioned as follows
//
// [ SYNC MOE (2) | OFFSET (14) | SUB PAGE (16) | PAGE (32) ]
static ZPropertyBuffer::PropertyKey GenerateKey(ZPropertyBuffer::SyncMode mode, uint32_t page, uint16_t sub_page, uint16_t offset) {
uint64_t key;
key = mode;
key = key << 14;
key = key + offset;
key = key << 16;
key = key + sub_page;
key = key << 32;
key = key + page;
// dumb assert
static_assert(sizeof(eID) == 4, "Cannot generate property key with sizeof(eID) not 32 bits, update property buffer");
return key;
}
static void ParseKey(ZPropertyBuffer::PropertyKey key, ZPropertyBuffer::SyncMode* mode, uint32_t* page, uint16_t* sub_page, uint16_t* offset) {
*page = (uint32_t)(key & 0xFFFFFFFF);
*sub_page = (uint16_t)((key >> 32) & 0xFFFF);
*offset = (uint16_t)((key >> 48) & 0x3FFF);
*mode = (ZPropertyBuffer::SyncMode)((key >> 62) & 0xFFFF);
}
void* ZPropertyBuffer::GetData( PropertyKey key, int rw, PropertyPage** page_out /*= NULL*/, SyncMode* mode_out /*= NULL*/ )
{
SyncMode mode;
uint32_t page_idx;
uint16_t sub_page_idx, offset;
ParseKey(key, &mode, &page_idx, &sub_page_idx, &offset); // parse data key
ZArray<PropertyPage*>& pages = mode != SYNC_NONE ? Pages[BUFFER_SYNC] :
Pages[BUFFER_LOCAL]; // determine if we are using sync or local data
if (pages.Size() >= page_idx) { // determine if we have this many pages
PropertyPage* page = pages[page_idx];
for (size_t i = 0; i < sub_page_idx; i++) { // see of we can find the sub page
page = page->Next;
if (page == NULL) { // check for bad sub page index
return NULL;
}
}
if (offset % MIN_ALLOC == 0 && offset <= page->Size - MIN_ALLOC) { // see if our offset is valid
if (page_out != NULL) *page_out = page; // get pointer to page
if (mode_out != NULL) *mode_out = mode; // get sync value
return (char*)page->Data[rw] + offset; // get read / write pointer
} else return NULL;
} else return NULL;
}
/*************************************************************************/
ZPropertyBuffer::Property ZPropertyBuffer::AllocData( PropertyPage* page, eID id, uint32_t page_idx, size_t size, SyncMode mode)
{
Property prop;
ZArray<PropertyPage*>& pages = mode ? Pages[BUFFER_SYNC] : Pages[BUFFER_LOCAL]; // determine if we are using sync or local data
size_t alloc_size = size > MIN_ALLOC ? (size + MIN_ALLOC - 1) & ~(MIN_ALLOC - 1) // ensure we allocate at least MIN_ALLOC, and align to MIN_ALLOC
: size;
uint16_t sub_page_idx;
for (sub_page_idx = 0; page != NULL; sub_page_idx++) { // loop pages seeing if we can drop the data in here
if (page->Offset + size <= page->Size) { // see if we have room
uint16_t offset = (uint16_t)page->Offset; // we do, so get current offset
page->Offset = page->Offset + alloc_size; // increment stored offset by alloc size, not property size
prop.Key = GenerateKey(mode, page_idx, sub_page_idx, offset); // key for this value
prop.Write = (char*)page->Data[BUFFER_WRITE] + offset; // write pointer for this value
prop.Read = (char*)page->Data[BUFFER_READ] + offset; // read pointer for this value
prop.Size = size; // size of this value
return prop;
} else {
for (size_t i = 0; i < page->Avail.Size(); i++) { // for each available slot from previous deallocs
if (page->Avail[i].Second >= size) { // see if we have room
uint16_t offset = (uint16_t)page->Avail[i].First; // we do, so store the offset
page->Avail.Erase(i); // no longer available
prop.Key = GenerateKey(mode, page_idx, sub_page_idx, offset); // key for this value
prop.Write = (char*)page->Data[BUFFER_WRITE] + offset; // write pointer for this value
prop.Read = (char*)page->Data[BUFFER_READ] + offset; // read pointer for this value
prop.Size = size; // size of this value
return prop;
}
}
if (page->Next != NULL) {
page = page->Next; // NEXT
} else {
sub_page_idx++; // sub page index will be next index
break;
}
}
}
// found nothing, time to allocate new
if (Pages[BUFFER_LOCAL].Size() <= page_idx) { // push NULL entry for local buffer if not present at page_idx
Pages[BUFFER_LOCAL].PushBack(NULL);
}
if (Pages[BUFFER_SYNC].Size() <= page_idx) { // push NULL entry for sync buffer if not present at page_idx
Pages[BUFFER_SYNC].PushBack(NULL);
}
const uint32_t page_size = SST_OS_GetPageSize(); // system page size
const uint32_t min_buffer_alloc = page_size / 2; // minimum amount we will allocate
uint32_t page_alloc_size = min_buffer_alloc; // starting page alloc
if (size > page_alloc_size) {
page_alloc_size =
((size+min_buffer_alloc-1) & ~(min_buffer_alloc-1)); // round up to nearest multiple of min_buffer_alloc
}
PropertyPage* npage = new PropertyPage(page_alloc_size); // new page
npage->Id = id; // set id for page
npage->Offset = alloc_size; // allocate enough space for the first property
if (page != NULL) { // link page into page list
page->Next = npage;
} else { // allocating new pages
pages[page_idx] = npage;
}
prop.Key = GenerateKey(mode, page_idx, sub_page_idx, 0); // generate a key for the correct page with sub-page and offset 0
prop.Write = (char*)npage->Data[BUFFER_WRITE]; // set the write pointer
prop.Read = (char*)npage->Data[BUFFER_READ]; // set the read pointer
prop.Size = size; // set the size of the data
return prop;
}
/*************************************************************************/
ZPropertyBuffer::ZPropertyBuffer()
{
}
/*************************************************************************/
ZPropertyBuffer::~ZPropertyBuffer()
{
for (size_t i = 0; i < Pages[BUFFER_LOCAL].Size(); i++) {
delete Pages[BUFFER_LOCAL][i];
}
for (size_t i = 0; i < Pages[BUFFER_SYNC].Size(); i++) {
delete Pages[BUFFER_SYNC][i];
}
}
/*************************************************************************/
ZPropertyBuffer::Property ZPropertyBuffer::AllocProperty(eID id, size_t size, SyncMode mode)
{
// sanity check - synchronized properties should be less than ZSIM_BUFFER_SYNC_MAX
ZASSERT_RUNTIME(!mode || size < ZSIM_SYNCBUFFER_MAX, "Synchronized property allocation too large!");
auto itr = IdPageMap.Find(id);
ZArray<PropertyPage*>& pages = mode != SYNC_NONE ? Pages[BUFFER_SYNC] :
Pages[BUFFER_LOCAL];
if (itr != IdPageMap.End()) {
uint32_t page_idx = itr.GetValue();
PropertyPage* page = pages[page_idx];
return AllocData(page, id, page_idx, size, mode);
}
if (Avail.Size() > 0) {
size_t page_idx = Avail.PopBack();
IdPageMap.Put(id, page_idx);
return AllocData(NULL, id, page_idx, size, mode);
} else {
IdPageMap.Put(id, pages.Size());
return AllocData(NULL, id, pages.Size(), size, mode);
}
}
/*************************************************************************/
void ZPropertyBuffer::DeallocEntity( eID id )
{
auto itr = IdPageMap.Find(id);
if (itr != IdPageMap.End()) {
uint32_t page_idx = itr.GetValue();
delete Pages[BUFFER_LOCAL][page_idx];
delete Pages[BUFFER_SYNC][page_idx];
Pages[BUFFER_LOCAL][page_idx] = NULL;
Pages[BUFFER_SYNC][page_idx] = NULL;
Avail.PushBack(page_idx);
IdPageMap.Erase(id);
}
}
/*************************************************************************/
void ZPropertyBuffer::DeallocProperty( PropertyKey key, size_t size )
{
SyncMode mode;
uint32_t page_idx;
uint16_t sub_page_idx, offset;
ParseKey(key, &mode, &page_idx, &sub_page_idx, &offset);
ZArray<PropertyPage*>& pages = mode != SYNC_NONE ? Pages[BUFFER_SYNC] :
Pages[BUFFER_LOCAL];
PropertyPage* page = pages[page_idx];
for (size_t i = 0; i < sub_page_idx; i++) {
page = page->Next;
}
page->Avail.PushBack(ZPair<size_t, size_t>(offset, size));
}
/*************************************************************************/
void ZPropertyBuffer::FlagModified( PropertyKey key, size_t size )
{
PropertyPage* page;
SyncMode mode;
void* data = GetData(key, BUFFER_WRITE, &page, &mode);
ZASSERT(data != NULL && page != NULL, "Attempt to flag modified on invalid property key");
page->Updates.PushBack(ZPair<PropertyKey, size_t>(key, size));
}
/*************************************************************************/
void ZPropertyBuffer::UpdateModified(bool sync, ZNetworkUpdateStream& stream)
{
// wherever we have an update, copy the write buffer
for (size_t b = 0; b < 2; b++) {
for (size_t i = 0; i < Pages[b].Size(); i++) {
PropertyPage* page = Pages[b][i];
ZPropertyBufferUpdate* buffer_update = NULL;
// synchronized buffers require a property buffer update call
if (sync && b == BUFFER_SYNC && page != NULL && page->Updates.Size() > 0) {
buffer_update = stream.AllocUpdate(page->Id);
}
// for all pages and sub-pages
while (page != NULL) {
for (size_t j = 0; page != NULL && j < page->Updates.Size(); j++) {
PropertyKey key = page->Updates[j].First;
size_t size = page->Updates[j].Second;
SyncMode mode;
uint32_t page_idx;
uint16_t sub_page_idx, offset;
ParseKey(key, &mode, &page_idx, &sub_page_idx, &offset);
void* data = (char*)page->Data[BUFFER_WRITE] + offset;
MemCopyChar((char*)page->Data[BUFFER_READ] + offset, data, size);
// if needed, make it happen
if (buffer_update != NULL && mode == SYNC_SEND) {
ZPropertyBufferUpdate::Update update;
update.SubPage = sub_page_idx;
update.Offset = offset;
update.Size = size;
update.Data = (char*)page->Data[BUFFER_READ] + offset;
buffer_update->Updates.PushBack(update);
}
}
page->Updates.Clear();
page = page->Next;
}
// push the entity update
if (buffer_update != NULL) {
stream.EnqueueOutgoingUpdate(buffer_update);
}
}
}
}
/*************************************************************************/
void ZPropertyBuffer::UpdateModified( eID id, bool sync, ZNetworkUpdateStream& stream )
{
auto itr = IdPageMap.Find(id);
if (itr != IdPageMap.End()) {
uint32_t page_idx = itr.GetValue();
// wherever we have an update, copy the write buffer
for (size_t b = BUFFER_LOCAL; b != BUFFER_SYNC; b = BUFFER_SYNC) {
PropertyPage* page = Pages[b][page_idx];
ZPropertyBufferUpdate* buffer_update = NULL;
// synchronized buffers require a property buffer update call
if (sync && b == BUFFER_SYNC && page != NULL) {
buffer_update = stream.AllocUpdate(id);
}
// for all pages and sub-pages
while (page != NULL) {
for (size_t j = 0; page != NULL && j < page->Updates.Size(); j++) {
PropertyKey key = page->Updates[j].First;
size_t size = page->Updates[j].Second;
SyncMode mode;
uint32_t page_idx;
uint16_t sub_page_idx, offset;
ParseKey(key, &mode, &page_idx, &sub_page_idx, &offset);
void* data = (char*)page->Data[BUFFER_WRITE] + offset;
MemCopyChar((char*)page->Data[BUFFER_READ] + offset, data, size);
if (buffer_update != NULL && mode == SYNC_SEND) {
ZPropertyBufferUpdate::Update update;
update.SubPage = sub_page_idx;
update.Offset = offset;
update.Size = size;
update.Data = (char*)page->Data[BUFFER_READ] + offset;
buffer_update->Updates.PushBack(update);
}
}
page->Updates.Clear();
page = page->Next;
}
// push the entity update
if (buffer_update != NULL) {
stream.EnqueueOutgoingUpdate(buffer_update);
}
}
} else {
SystemLogError("Unable to update entity property buffer - invalid id");
}
}
/*************************************************************************/
static void* GetSyncData(PropertyPage* page, uint16_t offset)
{
if (offset % MIN_ALLOC == 0 && offset <= page->Size - MIN_ALLOC) { // see if our offset is valid
return (char*)page->Data[BUFFER_READ] + offset; // return read
} else return NULL;
}
void ZPropertyBuffer::ApplyBufferUpdates( const ZArray<ZPropertyBufferUpdate*>& updates )
{
for (size_t i = 0; i < updates.Size(); i++) {
ZPropertyBufferUpdate& buffer_update = *updates[i];
eID id = buffer_update.Id;
ZArray<PropertyPage*>& pages = Pages[BUFFER_SYNC]; // always sync data
auto itr = IdPageMap.Find(id); // find page mapped for id
if (itr != IdPageMap.End()) { // make sure we have a valid one
uint32_t page_idx = itr.GetValue();
PropertyPage* base_page = pages[page_idx];
for (size_t j = 0; j < buffer_update.Updates.Size(); j++) {
ZPropertyBufferUpdate::Update& update = buffer_update.Updates[j];
uint16_t sub_page_idx = update.SubPage;
uint16_t offset = update.Offset;
size_t size = update.Size;
PropertyPage* sub_page = base_page;
for (uint16_t k = 0; k < sub_page_idx; k++) {
if (sub_page->Next != NULL) {
sub_page = sub_page->Next;
}
}
if (sub_page != NULL) {
void* rd_ptr = GetSyncData(sub_page, offset);
if (rd_ptr != NULL) {
MemCopyChar(rd_ptr, update.Data, size);
} else {
SystemLogError("Recieved invalid buffer update offset");
}
} else {
SystemLogError("Recieved invalid buffer update sub page");
}
}
}
}
}

View File

@@ -0,0 +1,134 @@
/*
ZPropertyBuffer.hpp
Author: author_name <author_email@762studios.com>
Created: 8/30/2015
Purpose:
The property buffer system is an allocator which holds entity property data in
contiguous blocks of memory that can then be synchronized across the network when
properties change, though properties can be kept in non-synchronized buffers for
local use.
The buffer keeps both a read and write copy of the properties which can be updated
on call.
This allocator does not lock, so any locking that would be required must be handled
by the caller.
In order to have proper synchronization, each entity that has properties allocated
should have it's synchronized variables created in the same order on all machines.
This will ensure that the data layouts are identical and proper synchronization can
occur.
License:
TODO
*/
#pragma once
#ifndef _ZPROPERTYBUFFER_HPP
#define _ZPROPERTYBUFFER_HPP
#include "ZSimulationDefs.hpp"
#include "ZPropertyBufferUpdate.hpp"
// forward decl
struct PropertyPage;
class ZNetworkUpdateStream;
// class decl
class ZPropertyBuffer
{
public:
// key used to look up allocated data in the property buffer
typedef uint64_t PropertyKey;
// used to flag synchronization type (none, send, or receive)
enum SyncMode {
SYNC_NONE = 0,
SYNC_SEND = 1,
SYNC_RECV = 2,
SYNC_ENUM_SIZE
};
// data struct for property data
struct Property {
void* Read; // pointer to the read property
void* Write; // pointer to the write property
size_t Size; // size of the property
PropertyKey Key; // the property key
};
// c'tor
ZPropertyBuffer();
// d'tor
~ZPropertyBuffer();
/*
Allocates a chunk of memory in the property buffer given the entity id, the entity size,
and whether or not the property should be synchronized. The read buffer for the property
is returned.
Allocations from these methods return static sized properties - they will not ever be able
to increase in size - alloc a new property and remove the old one in order to increase
size. The size limit on a synchronized property is 1024 bytes.
In order to have proper synchronization, each entity that has properties allocated
should have its synchronized variables created in the same order on all machines.
This will ensure that the data layouts are identical and proper synchronization can
occur.
*/
Property AllocProperty(eID id, size_t size, SyncMode mode = SYNC_NONE );
/*
Deallocation methods, which deallocate previously allocated property data. The version
which takes an entity id deallocates all data associated with that entity id. The
version which takes a property key merely deallocates that particular property.
Deallocating individual properties is not terribly fast, so avoid it where possible.
*/
void DeallocEntity(eID id);
void DeallocProperty(PropertyKey key, size_t size);
/*
Flags a previously allocated property as modified. The size (in bytes) of the property
is required.
*/
void FlagModified(PropertyKey key, size_t size);
/*
Updates the read properties from the write properties where modifications have happened.
The version which takes an entity id will update a single entities properties.
If told to synchronize, the buffer will queue up the network updates needed to sync
the buffer state.
*/
void UpdateModified(bool sync, ZNetworkUpdateStream& stream);
void UpdateModified(eID id, bool sync, ZNetworkUpdateStream& stream);
/*
Applies buffer updates that have been read from the network. These are applied directly
to the read value of the property.
*/
void ApplyBufferUpdates(const ZArray<ZPropertyBufferUpdate*>& updates);
private:
/* synchronized and non-synchronized data storage */
ZArray<PropertyPage*> Pages[2]; // the individual buffer pages (synchronized and local)
ZHashMap<eID, uint32_t> IdPageMap; // maps the entity id to its page number
ZArray<uint32_t> Avail; // available indices from dealloc'd entities
// given the property key will get the data, page, and synchronization flag
void* GetData(PropertyKey key, int rw, PropertyPage** page_out = NULL, SyncMode* mode_out = NULL);
// will alloc data for the property (searches page, otherwise makes new pages)
Property AllocData(PropertyPage* page, eID id, uint32_t page_idx, size_t size, SyncMode mode);
};
#endif

View File

@@ -0,0 +1,62 @@
/*
ZBufferUpdate.hpp
Author: James Russell <jcrussell@762studios.com>
Created: 9/13/2015
Purpose:
Buffer updates are created by the property buffer when synchronization
happens. They are then passed along to the network service to be serialized
and sent across the wire.
License:
TODO
*/
#pragma once
#ifndef _ZPROPERTYBUFFERUPDATE_HPP
#define _ZPROPERTYBUFFERUPDATE_HPP
#include <ZUtil/ZUtil.hpp>
#include "ZSimulationDefs.hpp"
class ZPropertyBufferUpdate {
public:
// update marker for a single bit of data in the buffer
struct Update {
uint16_t SubPage; // sub property page
uint16_t Offset; // offset into buffer (NOT into Data)
size_t Size; // size of the data
void* Data; // pointer to the data
uint8_t Local[ZSIM_SYNCBUFFER_MAX]; // local storage for Data (if copy is required)
};
eID Id; // entity we are updating in this packet
ZArray<Update> Updates; // the updates in the data array
int Delay; // simulated lag
// c'tor
ZPropertyBufferUpdate();
// parameterized c'tor
ZPropertyBufferUpdate(eID id);
// d'tor
~ZPropertyBufferUpdate();
// called by the network system to serialize this update into binary form
void Serialize(ZBinaryBufferWriter& writer);
// called by the network system to deserialize this update from binary form
bool Deserialize(ZBinaryBufferReader& reader);
// called to get the serialized size of this update
size_t GetSerializedSize();
};
#endif

View File

@@ -0,0 +1,33 @@
/*
ZPropertyBufferUpdateQueue.hpp
Author: James Russell <jcrussell@762studios.com>
Created: 9/17/2015
Purpose:
License:
Copyright 2015, 762 Studios.
*/
#pragma once
#ifndef _ZPROPERTYBUFFERUPDATEQUEUE_HPP
#define _ZPROPERTYBUFFERUPDATEQUEUE_HPP
#include "ZPropertyBufferUpdate.hpp"
class ZPropertyBufferUpdateQueue {
public:
// c'tor
ZPropertyBufferUpdateQueue();
// d'tor
~ZPropertyBufferUpdateQueue();
private:
};
#endif

View File

@@ -0,0 +1,74 @@
/*
RemoteClient.hpp
Author: Patrick Baggett <ptbaggett@762studios.com>, James Russell <jcrussell@762studios.com>
Created: 12/2/2013
Purpose:
Network peer as seen by a running simulation. May refer to either a
client or another server instance.
License:
Copyright 2013, 762 Studios.
*/
#ifndef __ZREMOTEPEER_HPP
#define __ZREMOTEPEER_HPP
#include <enet/enet.h>
class ZRemotePeer
{
public:
// the client state
enum PeerState {
AVAILABLE,
JOINING,
CONNECTED,
CONNECTED_LOCAL,
DISCONNECTED
};
// c'tor
ZRemotePeer() : bIsClient(true),
State(AVAILABLE),
Peer(NULL) { }
// getters
cID GetId() const { return Id; }
PeerState GetState() const { return State; }
bool IsClient() const { return bIsClient; }
const char* GetName() const { return Name; }
const char* GetHost() const { return Host; }
ENetPeer* GetPeer() { return Peer; }
// setters
void SetId(cID id) { Id = id; }
void SetState(PeerState cs) { State = cs; }
void SetClient(bool isClient) { bIsClient = isClient; }
void SetName(const char* name) { strcpy(Name, name); }
void SetHostIP(const char* host) { strcpy(Host, host); }
void SetPeer(ENetPeer* newPeer) { Peer = newPeer; }
// resets this peer object (must be called to reuse object and connection id)
void Reset() {
SetPeer(NULL);
SetState(AVAILABLE);
SetName("");
SetHostIP("");
SetClient(true);
}
private:
cID Id; // id of this connection assigned by network service
ENetPeer* Peer; // peer object
PeerState State; // overall state
bool bIsClient; // true if this connects to a client
char Host[ZSIM_NETWORK_MAX_HOST_LENGTH]; // host name in readable format
char Name[ZSIM_NETWORK_MAX_NAME_LENGTH]; // network 'name' (unique among network peers)
};
#endif

View File

@@ -0,0 +1,327 @@
/*
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

View File

@@ -0,0 +1,299 @@
/*
ZFrameworkDefs.hpp
Author: James Russell <jcrussell@762studios.com>
Created: 2/5/2013
Purpose:
This is our definitions and typedefs file for the ZFramework project.
License:
Copyright 2013, 762 Studios
*/
#pragma once
#ifndef _ZSIMULATIONDEFS_HPP
#define _ZSIMULATIONDEFS_HPP
#include "pstdint.h"
#include <SST/SST_Math.h> // For SST_Vec*, SST_Mat*
#include <SST/SST_NetResult.h> // For SST_NetResult
#include <ZUtil/ZUtil.hpp> // For Build Definitions
//////////////////////////////////////////////////////////////////////////
// Defines
#ifndef ZSIM_ENT_STATE_STACK_SIZE
#define ZSIM_ENT_STATE_STACK_SIZE (16) // size of the entity state stack (maximum number of pushed states)
#endif
#define ZSIM_DEFAULT_HZ (10) // Simulation default Hz
#define ZSIM_EID_MAX UINT16_MAX // Maximum eID that will ever be returned by the simulation
#define ZSIM_EID_SYSTEM (0) // eID that signifies target is 'system' or the running simulation
#define ZSIM_MSG_USER_BASE (128) // This is the base required Id for user messages (anything lower is reserved)
#define ZSIM_SYNCBUFFER_MAX (1024) // Maximum size of a buffer-synchronized property
#define ZSIM_STRINGBUFFER_DEFAULT (65535) // Default size of the string buffer
#define ZSIM_NETWORK_MAX_PEER_ID (32) // Maximum length of a peer id string
#define ZSIM_NETWORK_SLOT_LOCAL (0) // Array position for a local client
#define ZSIM_NETWORK_MAX_PACKETSIZE (1400) // Maximum network packet data size (coincidentally, size of the local buffer used to read packets)
#define ZSIM_NETWORK_REPLY_TIMEOUT (50) // Timeout on waiting for a reply from a connection
#define ZSIM_NETWORK_LISTEN_PORT (0x762C) // Listen port for the simulation (port 30252)
#define ZSIM_NETWORK_PROTOCOL_VERSION 0x1000000 // Protocol version
#define ZSIM_NETWORK_BYTEORDER SST_BIG_ENDIAN // Net system byteorder
#define ZSIM_NETWORK_DISCONNECT_TIME (1000) // Amount of time (in ms) we will send disconnection notices before giving up
#define ZSIM_NETWORK_MAX_CONNECTIONS (64) // Default number of maximum connections
#define ZSIM_NETWORK_MAX_CHANNELS (3) // Maximum number of network channels
#define ZSIM_NETWORK_CH_SYSTEM (0) // System Net Channel
#define ZSIM_NETWORK_CH_UPDATE (1) // Update Stream Net Channel
#define ZSIM_NETWORK_CH_EVENT (2) // Game Event Net Channel
#define ZSIM_NETWORK_SEND_RATE (10) // Number of buffer updates per second
#define ZSIM_NETWORK_MAX_NAME_LENGTH (128) // Maximum id length for a connection (steam max name length is 32, FYI)
#define ZSIM_NETWORK_MAX_HOST_LENGTH (1024) // Maximum length for readable host name
#define ZSIM_NETWORK_TARGET_ALL (UINT16_MAX) // Indicates the network event should target all connected
#ifndef ZSIM_MESSAGE_SIZE
#define ZSIM_MESSAGE_SIZE (1024) // The payload size (in bytes) for a message, which can be interpreted via a layout definition
#endif
#ifndef ZSIM_MESSAGE_ALLOC
#define ZSIM_MESSAGE_ALLOC (65535) //The number of messages the message stream holds locally
#endif
//////////////////////////////////////////////////////////////////////////
// Typedefs
// Equivalent to typedef T D;, but makes it a strong type
// ARE YOU NOT ENTERTAINED?!?!
#define strong_typedef(T, D) \
struct D { \
T t; \
explicit D(const T _t) : t(_t) {} \
D() {}; \
D(const D& _t) : t(_t) {} \
D& operator = (const D& other) { t = other.t; return *this; } \
D& operator = (const T& other) { t = other; return *this; } \
operator const T& () const { return t; } \
operator T& () { return t; } \
bool operator == (const D& other) { return t == other.t; } \
bool operator < (const D& other) { return t < other.t; } \
T value() { return t; } \
}
typedef uint32_t eID; // entity identifier
typedef uint16_t cID; // connection identifier
typedef uint32_t nID; // net event type identifier (0 reserved by simulation)
typedef uint32_t mID; // message type identifier (0 - 127 are reserved by the simulation)
//////////////////////////////////////////////////////////////////////////
// Enumerations
// base simulation message types
enum ZMessageType {
ZSIM_MSG_NETWORK_SHUTDOWN = 1, // network system has shutdown
ZSIM_MSG_CONNECTION_ESTABLISHED, // connection attempt successful
ZSIM_MSG_CONNECTION_FAILED, // connection attempt failed
ZSIM_MSG_CONNECTION_LOST, // connection lost
ZSIM_MSG_NODE_JOINED, // server node has joined the network
ZSIM_MSG_NODE_LOST, // server node has left the network
ZSIM_MSG_ENT_SPAWN, // spawns an entity
ZSIM_MSG_ENT_GAIN_STATE, // entity has gained state (becomes Actor)
ZSIM_MSG_ENT_LOSE_STATE, // actor has lost state (becomes Entity)
ZSIM_MSG_ENT_KILL, // kills an entity
ZSIM_MSG_SET_INT_PROPERTY, // sets an integer entity property
ZSIM_MSG_SET_REAL_PROPERTY, // sets a real number entity property
ZSIM_MSG_SET_STRING_PROPERTY, // sets a string entity property
ZSIM_MSG_SET_VEC_PROPERTY, // sets a vector entity property
ZSIM_MSG_SET_MAT_PROPERTY, // sets a matrix entity property
ZSIM_MSG_SHUTDOWN_SIMULATION = ZSIM_MSG_USER_BASE - 1, // stops the running simulation and cleans up (can restart)
};
//////////////////////////////////////////////////////////////////////////
// Message Layouts
#pragma pack (push, 1) // tightly compact data in message structs
#if SST_COMPILER == SST_COMPILER_MSVC
#pragma warning(push)
#pragma warning(disable:4201) // Disables the warning about nameless structs
#pragma warning(disable:4100) // Disables the warning about unreferenced formal parameters
#endif
/* Networking Messages */
/*
Sent to the simulation when the network system thread fails to initialize.
*/
struct ZMessage_NetworkSystemShutdown {
int Reason; // 0 - stop network request, 1 - failed to poll for net events
};
/*
Sent to the simulation when a connection has been established. Contains information
about the connection and a connection identifier.
*/
struct ZMessage_ConnectionEstablished {
cID ConnectionId; // id that has been assigned to this connection
int ConnectionType; // 0 - client, 1 - server, 2 - unknown
char NetworkName[ZSIM_NETWORK_MAX_NAME_LENGTH+1]; // network name given by the other simulation
};
/*
Sent to the simulation when a connection attempt has failed. Contains information
about the connection attempt.
*/
struct ZMessage_ConnectionFailed {
int Reason; // 0 - unable to resolve host, 1 - no slots available, 2 - malformed response
char RemoteHost[ZSIM_NETWORK_MAX_HOST_LENGTH+1]; // remote host address that connection failed
};
/*
Sent to the simulation when a connection has been lost due to error. Contains information
about the error and a connection identifier. Any handler for this message should call
'Reset' on the remote peer object - otherwise the connection id remains unused.
*/
struct ZMessage_ConnectionLost {
int Reason; // 0 - terminated here, 1 - terminated other side, 2 - connection unresponsive, 3 - malformed data
cID ConnectionId; // connection id for connection that was lost
};
/*
Sent to the simulation when another simulation instance is connected to the network.
Contains the name of the node and the name of its host node.
*/
struct ZMessage_NodeGained {
char NodeName[ZSIM_NETWORK_MAX_NAME_LENGTH+1]; // network node name of the other simulation
char HostName[ZSIM_NETWORK_MAX_NAME_LENGTH+1]; // network node name of the host the simulation is connected to
};
/*
Sent to the simulation when a simulation node connection is lost. Contains the name
as well as a reason flag.
*/
struct ZMessage_NodeLost {
int Reason; // 1 - connection terminated, 2 - connection unresponsive
char NodeName[ZSIM_NETWORK_MAX_NAME_LENGTH+1]; // network node name of the lost node
};
/* Simulation Messages */
/*
Spawns an entity into the simulation.
*/
struct ZMessage_SpawnEntity {
eID RequestedId; // id being requested for the entity
ZHashValue Type; // the entity type (name hash)
ZHashValue State; // the state to spawn with (zero for no state)
};
/*
Notifies the simulation that an entity has gained state, turning a base entity into an
actor entity that will have state ticked. The sender must be a valid entity that has
gained an initial state. This is not needed if the entity is created with state.
Message source is used to find the entity.
*/
struct ZMessage_EntityGainState {
};
/*
Notifies the simulation that an actor entity has lost all state and become a base entity.
Base entities do not have their state ticked by the simulation (as they have none).
Message source is used to find the entity.
*/
struct ZMessage_EntityLoseState {
};
/*
Kills off an entity from the simulation.
Message target is used to find the entity.
*/
struct ZMessage_KillEntity {
};
/*
Simulation Stop message layout. This will take a shutdown message and shut down the
simulation.
*/
struct ZMessage_ShutdownSimulation {
char ShutdownMessage[ZSIM_MESSAGE_SIZE]; // the shutdown message
};
/* Property Set Messages */
/*
All property set messages behave in a similar fashion to cope with the threaded processing
of values.
Provided the original value of the property, this will set it if that value remains the same at
the time this message is processed. If the value has changed from the original value, this will
call the provided callback function to recompute the value. If the provided callback function is
NULL, set messages will simply apply the value regardless of original value.
The function pointer always has the following signature:
T callback_function(const T& new_value);
Where T is the property type. Note that the behavior of set messages make them unsuitable
as network messages - rely on synchronized properties instead.
*/
/*
Templated set message. Contains the new value, the original value, and the name hash which
is used to look up the property. When this message is processed it will set the property
value regardless of the current read or write value.
If you wish to have the message take the current value into account, use a lambda message.
*/
template <typename T>
struct ZMessage_Set {
uint64_t NameHash;
T Value;
};
typedef ZMessage_Set<bool> ZMessage_SetBool;
typedef ZMessage_Set<int64_t> ZMessage_SetInt;
typedef ZMessage_Set<float> ZMessage_SetFloat;
typedef ZMessage_Set<double> ZMessage_SetDouble;
typedef ZMessage_Set<SST_Vec4f> ZMessage_SetVector;
typedef ZMessage_Set<SST_Mat44f> ZMessage_SetMatrix;
typedef ZMessage_Set<char*> ZMessage_SetString;
/*
Specialized set string message that holds the string in the message (up to ZSIM_MESSAGE_SIZE).
Useful when a pointer would leave scope befor the message can be processed.
*/
struct ZMessage_SetStringCopy {
uint64_t NameHash;
char Value[ZSIM_MESSAGE_SIZE-sizeof(uint64_t)];
};
/*
Templated lambda message. Simply carries a pointer to a lambda function which is run
on the property when this message is processed.
*/
template <typename T>
struct ZMessage_Lambda {
uint64_t NameHash;
T (*LambdaFnc)(const T&);
};
#if SST_COMPILER == SST_COMPILER_MSVC
#pragma warning(pop)
#endif
#pragma pack (pop)
#endif

View File

@@ -0,0 +1,72 @@
/*
ZStringBuffer.hpp
Author: James Russell <jcrussell@762studios.com>
Created: 10/19/2016
Purpose:
The string buffer is a specialized lookup table for strings, designed to hold
static strings for entity properties. In doing so, properties need merely keep
a reference key for a static string that is shared between entities and different
simulations, reducing memory usage and network traffic.
Dynamic strings are stored within the property buffer and are limited in size.
It is recommended that all static strings be declared during the simulation
initialization step, as no concurrency mechanisms are in place when looking up
strings from string keys. As such, there is no way of removing a string once
added.
This class is implemented as a singleton.
License:
Copyright 2016, 762 Studios.
*/
#pragma once
#ifndef _ZSTRINGBUFFER_HPP
#define _ZSTRINGBUFFER_HPP
#include "ZSimulationDefs.hpp"
class ZStringBuffer
{
public:
// typedef used to look up a static string
typedef uint32_t StringKey;
// singleton instance getter
static ZStringBuffer* Instance()
{ if (_Instance == NULL) _Instance = new ZStringBuffer(); return _Instance; }
// adds a string to the buffer
StringKey AddString(const char* str);
// given a string, finds the key (returns StringKey(-1) if not found)
StringKey GetKey(const char* str);
// given a key, finds the string (returns NULL if not found)
const char* GetString(StringKey key);
private:
static ZStringBuffer* _Instance; // singleton instance
char* Buffer; // the buffer all strings will be stored in
size_t BufferSize; // current size of the buffer
size_t BufferOffset; // current offset into the buffer
ZArray<size_t> Offsets; // string key as index into this array, which provides offset into buffer
ZHashMap<SST_HashValue64, ZArray<StringKey>> Lookup; // fast lookup for strings (narrows the search)
// c'tor
ZStringBuffer();
// d'tor
~ZStringBuffer();
};
#endif