Initial commit
This commit is contained in:
379
Include/ZSimulation/ZEntity.hpp
Normal file
379
Include/ZSimulation/ZEntity.hpp
Normal 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
|
||||
|
||||
153
Include/ZSimulation/ZMessageStream.hpp
Normal file
153
Include/ZSimulation/ZMessageStream.hpp
Normal 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
|
||||
|
||||
165
Include/ZSimulation/ZNetworkEvent.hpp
Normal file
165
Include/ZSimulation/ZNetworkEvent.hpp
Normal 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
|
||||
82
Include/ZSimulation/ZNetworkRequest.hpp
Normal file
82
Include/ZSimulation/ZNetworkRequest.hpp
Normal 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
|
||||
181
Include/ZSimulation/ZNetworkService.hpp
Normal file
181
Include/ZSimulation/ZNetworkService.hpp
Normal 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
|
||||
114
Include/ZSimulation/ZNetworkUpdateStream.hpp
Normal file
114
Include/ZSimulation/ZNetworkUpdateStream.hpp
Normal 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
|
||||
454
Include/ZSimulation/ZPropertyBuffer.cpp
Normal file
454
Include/ZSimulation/ZPropertyBuffer.cpp
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
134
Include/ZSimulation/ZPropertyBuffer.hpp
Normal file
134
Include/ZSimulation/ZPropertyBuffer.hpp
Normal 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
|
||||
62
Include/ZSimulation/ZPropertyBufferUpdate.hpp
Normal file
62
Include/ZSimulation/ZPropertyBufferUpdate.hpp
Normal 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
|
||||
33
Include/ZSimulation/ZPropertyBufferUpdateQueue.hpp
Normal file
33
Include/ZSimulation/ZPropertyBufferUpdateQueue.hpp
Normal 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
|
||||
74
Include/ZSimulation/ZRemotePeer.hpp
Normal file
74
Include/ZSimulation/ZRemotePeer.hpp
Normal 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
|
||||
327
Include/ZSimulation/ZSimulation.hpp
Normal file
327
Include/ZSimulation/ZSimulation.hpp
Normal 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
|
||||
299
Include/ZSimulation/ZSimulationDefs.hpp
Normal file
299
Include/ZSimulation/ZSimulationDefs.hpp
Normal 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
|
||||
72
Include/ZSimulation/ZStringBuffer.hpp
Normal file
72
Include/ZSimulation/ZStringBuffer.hpp
Normal 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
|
||||
Reference in New Issue
Block a user