Major networking refactoring to allow for snapshot filtering outside of netcode

This commit is contained in:
2016-02-09 13:22:21 +01:00
parent 2b4e00e4b0
commit d3bc48f5f7
28 changed files with 382 additions and 198 deletions
+1 -1
View File
@@ -11,9 +11,9 @@ struct ComponentInfo
{ {
std::string Annotation; std::string Annotation;
unsigned int Allocation = 0; unsigned int Allocation = 0;
bool NetworkReplicated = false;
std::map<std::string, std::string> FieldAnnotations; std::map<std::string, std::string> FieldAnnotations;
std::map<std::string, std::map<std::string, EnumType>> FieldEnumDefinitions; std::map<std::string, std::map<std::string, EnumType>> FieldEnumDefinitions;
bool NetworkReplicated = true;
}; };
struct Field_t struct Field_t
+13
View File
@@ -1,6 +1,7 @@
#ifndef ComponentWrapper_h__ #ifndef ComponentWrapper_h__
#define ComponentWrapper_h__ #define ComponentWrapper_h__
#include <boost/shared_array.hpp>
#include "../Common.h" #include "../Common.h"
#include "Entity.h" #include "Entity.h"
#include "ComponentInfo.h" #include "ComponentInfo.h"
@@ -76,6 +77,18 @@ struct ComponentWrapper
SubscriptProxy operator[](std::string propertyName) { return SubscriptProxy(this, propertyName); } SubscriptProxy operator[](std::string propertyName) { return SubscriptProxy(this, propertyName); }
}; };
// A component wrapper that "owns" its data through a shared pointer
struct SharedComponentWrapper : ComponentWrapper
{
SharedComponentWrapper(const ComponentInfo& componentInfo, boost::shared_array<char> data)
: ComponentWrapper(componentInfo, data.get())
, m_DataReference(data)
{ }
private:
boost::shared_array<char> m_DataReference;
};
// TODO: Move this to Tests once entity importing is finished // TODO: Move this to Tests once entity importing is finished
class ComponentWrapperFactory class ComponentWrapperFactory
{ {
+11 -6
View File
@@ -20,16 +20,22 @@
#include "Input/EInputCommand.h" #include "Input/EInputCommand.h"
#include "Core/EPlayerDamage.h" #include "Core/EPlayerDamage.h"
#include "Network/EInterpolate.h" #include "Network/EInterpolate.h"
#include "Network/SnapshotFilter.h"
#include "Core/EPlayerSpawned.h" #include "Core/EPlayerSpawned.h"
class Client : public Network class Client : public Network
{ {
public: public:
Client(ConfigFile* config); Client(World* world, EventBroker* eventBroker);
Client(World* world, EventBroker* eventBroker, std::unique_ptr<SnapshotFilter> snapshotFilter);
~Client(); ~Client();
void Start(World* world, EventBroker* eventBroker) override;
void Connect(std::string address, int port);
void Update() override; void Update() override;
private: private:
std::unique_ptr<SnapshotFilter> m_SnapshotFilter = nullptr;
// Assio UDP logic // Assio UDP logic
boost::asio::ip::udp::endpoint m_ReceiverEndpoint; boost::asio::ip::udp::endpoint m_ReceiverEndpoint;
boost::asio::io_service m_IOService; boost::asio::io_service m_IOService;
@@ -45,10 +51,8 @@ private:
PacketID m_SendPacketID = 0; PacketID m_SendPacketID = 0;
// Game logic // Game logic
World* m_World;
std::string m_PlayerName; std::string m_PlayerName;
PlayerID m_PlayerID = -1; PlayerID m_PlayerID = -1;
EntityID m_ServerEntityID = std::numeric_limits<EntityID>::max();
bool m_IsConnected = false; bool m_IsConnected = false;
EntityWrapper m_LocalPlayer = EntityWrapper::Invalid; EntityWrapper m_LocalPlayer = EntityWrapper::Invalid;
// Server Client Lookup map // Server Client Lookup map
@@ -74,7 +78,9 @@ private:
void connect(); void connect();
void disconnect(); void disconnect();
void parseMessageType(Packet& packet); void parseMessageType(Packet& packet);
void updateFields(Packet& packet, const ComponentInfo& componentInfo, const EntityID& entityID, const std::string& componentType); void updateFields(Packet& packet, const ComponentInfo& componentInfo, const EntityID& entityID);
SharedComponentWrapper createSharedComponent(Packet& packet, EntityID entityID, const ComponentInfo& componentInfo);
void ignoreFields(Packet& packet, const ComponentInfo& componentInfo);
void parseConnect(Packet& packet); void parseConnect(Packet& packet);
void parsePlayerConnected(Packet& packet); void parsePlayerConnected(Packet& packet);
void parsePing(); void parsePing();
@@ -99,7 +105,6 @@ private:
void deleteFromServerClientMaps(EntityID serverEntityID, EntityID clientEntityID); void deleteFromServerClientMaps(EntityID serverEntityID, EntityID clientEntityID);
// Events // Events
EventBroker* m_EventBroker;
EventRelay<Client, Events::InputCommand> m_EInputCommand; EventRelay<Client, Events::InputCommand> m_EInputCommand;
bool OnInputCommand(const Events::InputCommand& e); bool OnInputCommand(const Events::InputCommand& e);
EventRelay<Client, Events::PlayerDamage> m_EPlayerDamage; EventRelay<Client, Events::PlayerDamage> m_EPlayerDamage;
+7 -2
View File
@@ -11,8 +11,13 @@ namespace Events
struct Interpolate : Event struct Interpolate : Event
{ {
EntityID Entity; Interpolate(EntityWrapper Entity, SharedComponentWrapper Component)
boost::shared_array<char> DataArray; : Entity(Entity)
, Component(Component)
{ }
EntityWrapper Entity;
SharedComponentWrapper Component;
}; };
} }
+6 -2
View File
@@ -19,10 +19,15 @@ typedef unsigned int PacketID;
class Network class Network
{ {
public: public:
Network(World* world, EventBroker* eventBroker);
virtual ~Network() { }; virtual ~Network() { };
virtual void Start(World* m_world, EventBroker *eventBroker) = 0;
virtual void Update() = 0; virtual void Update() = 0;
protected: protected:
World* m_World;
EventBroker* m_EventBroker;
// For Debug // For Debug
bool isReadingData = false; bool isReadingData = false;
NetworkData m_NetworkData; NetworkData m_NetworkData;
@@ -32,7 +37,6 @@ protected:
double m_TimeoutMs; double m_TimeoutMs;
void saveToFile(); void saveToFile();
void updateNetworkData(); void updateNetworkData();
void initialize();
}; };
#endif #endif
+5 -7
View File
@@ -22,15 +22,17 @@
class Server : public Network class Server : public Network
{ {
public: public:
Server(); Server(World* world, EventBroker* eventBroker, int port);
~Server(); ~Server();
void Start(World* m_world, EventBroker *eventBroker) override;
void Update() override; void Update() override;
private: private:
int m_Port = 27666;
// UDP logic // UDP logic
boost::asio::ip::udp::endpoint m_ReceiverEndpoint; boost::asio::ip::udp::endpoint m_ReceiverEndpoint;
boost::asio::io_service m_IOService; boost::asio::io_service m_IOService;
boost::asio::ip::udp::socket m_Socket; std::unique_ptr<boost::asio::ip::udp::socket> m_Socket;
// Sending messages to client logic // Sending messages to client logic
std::map<PlayerID, PlayerDefinition> m_ConnectedPlayers; std::map<PlayerID, PlayerDefinition> m_ConnectedPlayers;
@@ -50,10 +52,6 @@ private:
//Timers //Timers
std::clock_t m_StartPingTime; std::clock_t m_StartPingTime;
// Game logic
World* m_World;
EventBroker* m_EventBroker;
// Packet loss logic // Packet loss logic
PacketID m_PacketID = 0; PacketID m_PacketID = 0;
PacketID m_PreviousPacketID = 0; PacketID m_PreviousPacketID = 0;
+19
View File
@@ -0,0 +1,19 @@
#ifndef SnapshotFilter_h__
#define SnapshotFilter_h__
#include "../Core/EntityWrapper.h"
#include "../Core/ComponentWrapper.h"
class SnapshotFilter
{
public:
// Filters an incoming snapshot.
// Modify the component and return true if the component snapshot should be applied.
// Otherwise return false and it will be ignored.
virtual bool FilterComponent(EntityWrapper entity, SharedComponentWrapper& component)
{
return true;
}
};
#endif
+3
View File
@@ -32,6 +32,8 @@ public:
virtual void SetFullscreen(bool fullscreen) { m_Fullscreen = fullscreen; } virtual void SetFullscreen(bool fullscreen) { m_Fullscreen = fullscreen; }
bool VSYNC() const { return m_VSYNC; } bool VSYNC() const { return m_VSYNC; }
virtual void SetVSYNC(bool vsync) { m_VSYNC = vsync; } virtual void SetVSYNC(bool vsync) { m_VSYNC = vsync; }
std::string WindowTitle() const { return m_WindowTitle; }
virtual void SetWindowTitle(const std::string& title) { glfwSetWindowTitle(m_Window, title.c_str()); m_WindowTitle = title; }
//Returns screen size excluding window border and header //Returns screen size excluding window border and header
Rectangle GetViewportSize() const { return m_ViewportSize; } Rectangle GetViewportSize() const { return m_ViewportSize; }
virtual void Initialize() = 0; virtual void Initialize() = 0;
@@ -47,6 +49,7 @@ protected:
int m_GLVersion[2]; int m_GLVersion[2];
std::string m_GLVendor; std::string m_GLVendor;
GLFWwindow* m_Window = nullptr; GLFWwindow* m_Window = nullptr;
std::string m_WindowTitle;
}; };
#endif // Renderer_h__ #endif // Renderer_h__
-24
View File
@@ -1,24 +0,0 @@
#include "Common.h"
#include "Core/System.h"
class ExplosionEffectSystem : public PureSystem
{
public:
ExplosionEffectSystem(SystemParams params)
: System(params)
, PureSystem("ExplosionEffect")
{ }
virtual void UpdateComponent(EntityWrapper& entity, ComponentWrapper& component, double dt) override
{
if ((double)component["TimeSinceDeath"] > (double)component["ExplosionDuration"]) {
(double)component["TimeSinceDeath"] = 0.f;
}
(double&)component["TimeSinceDeath"] += dt;
//if ((bool)Component["Gravity"] == true) {
// (bool)Component["ExponentialAccelaration"] = false;
//}
}
};
+11 -5
View File
@@ -1,6 +1,8 @@
#ifndef Game_h__ #ifndef Game_h__
#define Game_h__ #define Game_h__
#include <boost/program_options.hpp>
#include "Core/ResourceManager.h" #include "Core/ResourceManager.h"
#include "Core/ConfigFile.h" #include "Core/ConfigFile.h"
#include "Core/EventBroker.h" #include "Core/EventBroker.h"
@@ -14,7 +16,7 @@
#include "Core/EKeyDown.h" #include "Core/EKeyDown.h"
#include "Core/EntityFilePreprocessor.h" #include "Core/EntityFilePreprocessor.h"
#include "Core/SystemPipeline.h" #include "Core/SystemPipeline.h"
#include "ExplosionEffectSystem.h" #include "Systems/ExplosionEffectSystem.h"
#include "Editor/EditorSystem.h" #include "Editor/EditorSystem.h"
#include "Core/EntityFile.h" #include "Core/EntityFile.h"
#include "Rendering/RenderSystem.h" #include "Rendering/RenderSystem.h"
@@ -42,7 +44,9 @@ public:
void Tick(); void Tick();
private: private:
double m_LastTime; std::string m_NetworkAddress;
int m_NetworkPort;
ConfigFile* m_Config = nullptr; ConfigFile* m_Config = nullptr;
EventBroker* m_EventBroker; EventBroker* m_EventBroker;
IRenderer* m_Renderer; IRenderer* m_Renderer;
@@ -55,13 +59,15 @@ private:
Octree<EntityAABB>* m_OctreeFrustrumCulling; Octree<EntityAABB>* m_OctreeFrustrumCulling;
SystemPipeline* m_SystemPipeline; SystemPipeline* m_SystemPipeline;
RenderFrame* m_RenderFrame; RenderFrame* m_RenderFrame;
Network* m_Network = nullptr; Client* m_NetworkClient = nullptr;
Server* m_NetworkServer = nullptr;
SoundSystem* m_SoundSystem;
double m_LastTime;
bool m_IsClient = false; bool m_IsClient = false;
bool m_IsServer = false; bool m_IsServer = false;
// Sound int parseArgs(int argc, char* argv[]);
SoundSystem* m_SoundSystem;
}; };
#endif #endif
@@ -0,0 +1,25 @@
#ifndef MultiplayerSnapshotFilter_h__
#define MultiplayerSnapshotFilter_h__
#include "Core/EventBroker.h"
#include "Core/EPlayerSpawned.h"
#include "Network/SnapshotFilter.h"
#include "Network/EInterpolate.h"
class MultiplayerSnapshotFilter : public SnapshotFilter
{
public:
MultiplayerSnapshotFilter(EventBroker* eventBroker);
virtual bool FilterComponent(EntityWrapper entity, SharedComponentWrapper& component) override;
private:
EventBroker* m_EventBroker;
EntityWrapper m_LocalPlayer = EntityWrapper::Invalid;
EventRelay<MultiplayerSnapshotFilter, Events::PlayerSpawned> m_EPlayerSpawned;
bool OnPlayerSpawned(Events::PlayerSpawned ePlayerSpawned);
};
#endif
@@ -0,0 +1,18 @@
#ifndef ExplosionEffectSystem_h__
#define ExplosionEffectSystem_h__
#include "Common.h"
#include "Core/System.h"
class ExplosionEffectSystem : public PureSystem
{
public:
ExplosionEffectSystem(SystemParams params)
: System(params)
, PureSystem("ExplosionEffect")
{ }
virtual void UpdateComponent(EntityWrapper& entity, ComponentWrapper& component, double dt) override;
};
#endif
+10 -8
View File
@@ -18,6 +18,13 @@
class InterpolationSystem : public PureSystem class InterpolationSystem : public PureSystem
{ {
public:
InterpolationSystem(SystemParams params);
~InterpolationSystem() { }
virtual void UpdateComponent(EntityWrapper& entity, ComponentWrapper& transform, double dt) override;
private:
struct Transform struct Transform
{ {
glm::vec3 Position; glm::vec3 Position;
@@ -25,27 +32,22 @@ class InterpolationSystem : public PureSystem
glm::quat Orientation; glm::quat Orientation;
float interpolationTime; float interpolationTime;
}; };
public:
InterpolationSystem(SystemParams params);
~InterpolationSystem() { }
virtual void UpdateComponent(EntityWrapper& entity, ComponentWrapper& transform, double dt) override;
private:
std::unordered_map<EntityID, Transform> m_NextTransform; std::unordered_map<EntityID, Transform> m_NextTransform;
std::unordered_map<EntityID, Transform> m_LastReceivedTransform; std::unordered_map<EntityID, Transform> m_LastReceivedTransform;
EntityWrapper m_LocalPlayer = EntityWrapper::Invalid; EntityWrapper m_LocalPlayer = EntityWrapper::Invalid;
//glm::vec3 vectorInterpolation(glm::vec3 prev, glm::vec3 next, double currentTime);
template <typename T> template <typename T>
T vectorInterpolation(T prev, T next, double currentTime) T vectorInterpolation(T prev, T next, double currentTime)
{ {
T difference = next - prev; T difference = next - prev;
T vector = (difference / m_SnapshotInterval) * static_cast<float>(currentTime); T vector = difference * (static_cast<float>(currentTime) / m_SnapshotInterval);
return vector; return vector;
} }
float m_SnapshotInterval; float m_SnapshotInterval;
EventRelay<InterpolationSystem, Events::Interpolate> m_EInterpolate; EventRelay<InterpolationSystem, Events::Interpolate> m_EInterpolate;
bool InterpolationSystem::OnInterpolate(const Events::Interpolate& e); bool InterpolationSystem::OnInterpolate(Events::Interpolate& e);
EventRelay<InterpolationSystem, Events::PlayerSpawned> m_EPlayerSpawned; EventRelay<InterpolationSystem, Events::PlayerSpawned> m_EPlayerSpawned;
bool OnPlayerSpawned(Events::PlayerSpawned& e); bool OnPlayerSpawned(Events::PlayerSpawned& e);
}; };
@@ -11,6 +11,7 @@
<xs:element name="Speed" type="t:double" minOccurs="0"/> <xs:element name="Speed" type="t:double" minOccurs="0"/>
<xs:element name="Loop" type="t:bool" minOccurs="0"/> <xs:element name="Loop" type="t:bool" minOccurs="0"/>
</xs:all> </xs:all>
<xs:attribute name="replicated" type="xs:boolean" fixed="true"/>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
</xs:schema> </xs:schema>
+1 -1
View File
@@ -12,7 +12,7 @@
<xs:element name="Orientation" type="t:Vector" minOccurs="0"/> <xs:element name="Orientation" type="t:Vector" minOccurs="0"/>
<xs:element name="Scale" type="t:Vector" minOccurs="0"/> <xs:element name="Scale" type="t:Vector" minOccurs="0"/>
</xs:all> </xs:all>
<xs:attribute name="replicated" type="xs:boolean" fixed="true"/> <xs:attribute name="NetworkReplicated" type="xs:boolean" fixed="true"/>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
</xs:schema> </xs:schema>
+6 -12
View File
@@ -6,10 +6,10 @@
<Origin X="0" Y="0.772000015" Z="0"/> <Origin X="0" Y="0.772000015" Z="0"/>
<Size X="1" Y="1.60000002" Z="1"/> <Size X="1" Y="1.60000002" Z="1"/>
</c:AABB> </c:AABB>
<c:DashAbility>
<CoolDownMaxTimer>2.0</CoolDownMaxTimer>
</c:DashAbility>
<c:Collidable/> <c:Collidable/>
<c:DashAbility>
<CoolDownMaxTimer>2</CoolDownMaxTimer>
</c:DashAbility>
<c:Health/> <c:Health/>
<c:Physics> <c:Physics>
<Velocity X="2.30999646e-23" Y="0" Z="1.05272533e-23"/> <Velocity X="2.30999646e-23" Y="0" Z="1.05272533e-23"/>
@@ -23,7 +23,7 @@
</Team> </Team>
</c:Team> </c:Team>
<c:Transform> <c:Transform>
<Position X="9.56501799e-22" Y="-0.471999973" Z="4.35902473e-22"/> <Position X="1.28011036e-21" Y="0.0264999866" Z="1.82592022"/>
</c:Transform> </c:Transform>
</Components> </Components>
@@ -104,15 +104,9 @@
</Entity> </Entity>
<Entity name="Weapon"> <Entity name="Weapon">
<Components> <Components>
<c:ExplosionEffect>
<ColorByDistance>true</ColorByDistance>
<Velocity X="0.800000012" Y="0.0379999988" Z="0"/>
<ExplosionDuration>3.7999999523162842</ExplosionDuration>
<EndColor A="0" B="23.5294113" G="11.7647057" R="0"/>
<Randomness>true</Randomness>
</c:ExplosionEffect>
<c:Model> <c:Model>
<Resource>Models/AssaultWeaponRed.mesh</Resource> <Resource>Models/AssaultWeaponRed.mesh</Resource>
<Transparent>true</Transparent>
</c:Model> </c:Model>
<c:Transform> <c:Transform>
<Position X="0.180000007" Y="-0.183000013" Z="0"/> <Position X="0.180000007" Y="-0.183000013" Z="0"/>
@@ -151,7 +145,7 @@
<Components> <Components>
<c:Animation> <c:Animation>
<Name>Hold Pos</Name> <Name>Hold Pos</Name>
<Time>0.73506627647571587</Time> <Time>0.79606322555778508</Time>
<Speed>1</Speed> <Speed>1</Speed>
</c:Animation> </c:Animation>
<c:HiddenForLocalPlayer/> <c:HiddenForLocalPlayer/>
+1 -1
View File
@@ -3,7 +3,7 @@ project(TacticalZ-Engine)
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
find_package(GLEW REQUIRED) find_package(GLEW REQUIRED)
find_package(GLFW REQUIRED) find_package(GLFW REQUIRED)
find_package(Boost REQUIRED COMPONENTS system filesystem thread chrono) find_package(Boost REQUIRED COMPONENTS system filesystem thread chrono program_options)
find_package(assimp REQUIRED) find_package(assimp REQUIRED)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_package(PNG REQUIRED) find_package(PNG REQUIRED)
+9 -5
View File
@@ -96,14 +96,18 @@ void EntityFilePreprocessor::parseComponentInfo()
auto attributeDecl = attributeUse->getAttrDeclaration(); auto attributeDecl = attributeUse->getAttrDeclaration();
std::string name = XS::ToString(attributeDecl->getName()); std::string name = XS::ToString(attributeDecl->getName());
// Read network replication flag // HACK: This should never happen since patched Xerces. Run deploy to get the updated DLL.
if (name == "replicated") { static bool fff = false;
// HACK: This should never happen since patched Xerces. Run deploy to get the updated DLL. if (attributeDecl->getConstraintType() == XSConstants::VALUE_CONSTRAINT_NONE) {
if (attributeDecl->getConstraintType() == XSConstants::VALUE_CONSTRAINT_NONE) { if (!fff) {
system("explorer https://imon.nu/deploy.html"); system("explorer https://imon.nu/deploy.html");
continue; fff = true;
} }
continue;
}
// Read client interpolation flag
if (name == "NetworkReplicated") {
std::string value = XS::ToString(attributeDecl->getConstraintValue()); std::string value = XS::ToString(attributeDecl->getConstraintValue());
if (value == "true") { if (value == "true") {
compInfo.Meta->NetworkReplicated = true; compInfo.Meta->NetworkReplicated = true;
+3 -3
View File
@@ -100,9 +100,9 @@ void EditorSystem::Enable()
} }
// Pause the world we're editing // Pause the world we're editing
Events::Pause ePause; //Events::Pause ePause;
ePause.World = m_World; //ePause.World = m_World;
m_EventBroker->Publish(ePause); //m_EventBroker->Publish(ePause);
m_Enabled = true; m_Enabled = true;
} }
+78 -49
View File
@@ -2,40 +2,50 @@
using namespace boost::asio::ip; using namespace boost::asio::ip;
Client::Client(World* world, EventBroker* eventBroker)
Client::Client(ConfigFile* config) : m_Socket(m_IOService) : Network(world, eventBroker)
, m_Socket(m_IOService)
{ {
Network::initialize();
// Asumes root node is EntityID_Invalid // Asumes root node is EntityID_Invalid
insertIntoServerClientMaps(EntityID_Invalid, EntityID_Invalid); insertIntoServerClientMaps(EntityID_Invalid, EntityID_Invalid);
// Init timer // Init timer
m_TimeSinceSentInputs = std::clock(); m_TimeSinceSentInputs = std::clock();
// Default is local host
std::string address = config->Get<std::string>("Networking.Address", "127.0.0.1"); auto config = ResourceManager::Load<ConfigFile>("Config.ini");
int port = config->Get<int>("Networking.Port", 27666);
m_ReceiverEndpoint = udp::endpoint(boost::asio::ip::address::from_string(address), port);
// Set up network stream
m_PlayerName = config->Get<std::string>("Networking.Name", "Raptorcopter"); m_PlayerName = config->Get<std::string>("Networking.Name", "Raptorcopter");
m_SendInputIntervalMs = config->Get<int>("Networking.SendInputIntervalMs", 33); m_SendInputIntervalMs = config->Get<int>("Networking.SendInputIntervalMs", 33);
LOG_INFO("Client initialized");
}
Client::Client(World* world, EventBroker* eventBroker, std::unique_ptr<SnapshotFilter> snapshotFilter)
: Client(world, eventBroker)
{
m_SnapshotFilter = std::move(snapshotFilter);
} }
Client::~Client() Client::~Client()
{ } { }
void Client::Start(World* world, EventBroker* eventBroker) void Client::Connect(std::string address, int port)
{ {
m_EventBroker = eventBroker;
m_World = world;
// Subscribe to events // Subscribe to events
EVENT_SUBSCRIBE_MEMBER(m_EInputCommand, &Client::OnInputCommand); EVENT_SUBSCRIBE_MEMBER(m_EInputCommand, &Client::OnInputCommand);
EVENT_SUBSCRIBE_MEMBER(m_EPlayerDamage, &Client::OnPlayerDamage); EVENT_SUBSCRIBE_MEMBER(m_EPlayerDamage, &Client::OnPlayerDamage);
EVENT_SUBSCRIBE_MEMBER(m_EPlayerSpawned, &Client::OnPlayerSpawned); EVENT_SUBSCRIBE_MEMBER(m_EPlayerSpawned, &Client::OnPlayerSpawned);
auto config = ResourceManager::Load<ConfigFile>("Config.ini");
if (address.empty()) {
address = config->Get<std::string>("Networking.Address", "127.0.0.1");
}
if (port == 0) {
port = config->Get<int>("Networking.Port", 27666);
}
m_ReceiverEndpoint = udp::endpoint(boost::asio::ip::address::from_string(address), port);
LOG_INFO("Client connecting...");
m_Socket.connect(m_ReceiverEndpoint); m_Socket.connect(m_ReceiverEndpoint);
LOG_INFO("I am client. BIP BOP"); connect();
} }
void Client::Update() void Client::Update()
@@ -49,6 +59,7 @@ void Client::Update()
sendInputCommands(); sendInputCommands();
m_TimeSinceSentInputs = std::clock(); m_TimeSinceSentInputs = std::clock();
} }
// HACK: Send absolute player positions for now to avoid desync until we have reliable messages
sendLocalPlayerTransform(); sendLocalPlayerTransform();
} }
Network::Update(); Network::Update();
@@ -173,34 +184,46 @@ void Client::parseComponentDeletion(Packet & packet)
} }
} }
// Fields with strings will not work right now void Client::updateFields(Packet& packet, const ComponentInfo& componentInfo, const EntityID& entityID)
void Client::InterpolateFields(Packet& packet, const ComponentInfo& componentInfo, const EntityID& entityID, const std::string& componentType)
{
int sizeOfFields = 0;
for (auto field : componentInfo.FieldsInOrder) {
ComponentInfo::Field_t fieldInfo = componentInfo.Fields.at(field);
sizeOfFields += fieldInfo.Stride;
}
// Is the size correct?
boost::shared_array<char> eventData(new char[componentInfo.Stride]);
memcpy(eventData.get(), packet.ReadData(componentInfo.Stride), componentInfo.Stride);
//Send event to interpolat system
Events::Interpolate e;
e.Entity = entityID;
e.DataArray = eventData;
m_EventBroker->Publish(e);
}
void Client::updateFields(Packet& packet, const ComponentInfo& componentInfo, const EntityID& entityID, const std::string& componentType)
{ {
for (auto field : componentInfo.FieldsInOrder) { for (auto field : componentInfo.FieldsInOrder) {
ComponentInfo::Field_t fieldInfo = componentInfo.Fields.at(field); ComponentInfo::Field_t fieldInfo = componentInfo.Fields.at(field);
if (fieldInfo.Type == "string") { if (fieldInfo.Type == "string") {
std::string& value = packet.ReadString(); std::string& value = packet.ReadString();
m_World->GetComponent(entityID, componentType)[fieldInfo.Name] = value; m_World->GetComponent(entityID, componentInfo.Name)[fieldInfo.Name] = value;
} else { } else {
memcpy(m_World->GetComponent(entityID, componentType).Data + fieldInfo.Offset, packet.ReadData(fieldInfo.Stride), fieldInfo.Stride); memcpy(m_World->GetComponent(entityID, componentInfo.Name).Data + fieldInfo.Offset, packet.ReadData(fieldInfo.Stride), fieldInfo.Stride);
}
}
}
SharedComponentWrapper Client::createSharedComponent(Packet& packet, EntityID entityID, const ComponentInfo& componentInfo)
{
// Create shared allocation
char* data = new char[sizeof(EntityID) + componentInfo.Stride];
// Copy entity ID to start of data buffer
memcpy(data, &entityID, sizeof(EntityID));
// Read and copy fields
for (auto& field : componentInfo.FieldsInOrder) {
ComponentInfo::Field_t fieldInfo = componentInfo.Fields.at(field);
if (fieldInfo.Type == "string") {
new (data + sizeof(EntityID) + fieldInfo.Offset) std::string(packet.ReadString());
} else {
memcpy(data + sizeof(EntityID) + fieldInfo.Offset, packet.ReadData(fieldInfo.Stride), fieldInfo.Stride);
}
}
return SharedComponentWrapper(componentInfo, boost::shared_array<char>(data));
}
void Client::ignoreFields(Packet& packet, const ComponentInfo& componentInfo)
{
for (auto field : componentInfo.FieldsInOrder) {
ComponentInfo::Field_t fieldInfo = componentInfo.Fields.at(field);
if (fieldInfo.Type == "string") {
packet.ReadString();
} else {
packet.ReadData(fieldInfo.Stride);
} }
} }
} }
@@ -214,26 +237,32 @@ void Client::parseSnapshot(Packet& packet)
int ammountOfComponents = packet.ReadPrimitive<int>(); int ammountOfComponents = packet.ReadPrimitive<int>();
for (int i = 0; i < ammountOfComponents; i++) { for (int i = 0; i < ammountOfComponents; i++) {
std::string componentType = packet.ReadString(); std::string componentType = packet.ReadString();
ComponentInfo componentInfo = m_World->GetComponents(componentType)->ComponentInfo(); const ComponentInfo& componentInfo = m_World->GetComponents(componentType)->ComponentInfo();
if (serverClientMapsHasEntity(serverEntityID)) { if (serverClientMapsHasEntity(serverEntityID)) {
EntityID localEntityID = m_ServerIDToClientID.at(serverEntityID); EntityID localEntityID = m_ServerIDToClientID.at(serverEntityID);
EntityWrapper localEntity(m_World, localEntityID);
// Update entity // Update entity
if (m_World->HasComponent(localEntityID, componentType)) { if (m_World->HasComponent(localEntityID, componentType)) {
// Update component SharedComponentWrapper newComponent = createSharedComponent(packet, localEntityID, componentInfo);
if (componentType == "Transform") { bool shouldApply = true;
// Interpolate only transform components // Apply potential filter function
InterpolateFields(packet, componentInfo, localEntityID, componentType); if (m_SnapshotFilter != nullptr) {
} else if (componentType == "Physics" && m_World->HasComponent(localEntityID, "Player")) { shouldApply = m_SnapshotFilter->FilterComponent(localEntity, newComponent);
// HACK: Ignore velocity of physics
packet.ReadData(componentInfo.Stride);
} else {
// Set component values
updateFields(packet, componentInfo, localEntityID, componentType);
} }
if (shouldApply) {
ComponentWrapper currentComponent = m_World->GetComponent(localEntityID, componentType);
memcpy(currentComponent.Data, newComponent.Data, componentInfo.Stride);
}
//if (localEntity != m_LocalPlayer && !localEntity.IsChildOf(m_LocalPlayer)) {
// updateFields(packet, componentInfo, localEntityID);
//} else {
// ignoreFields(packet, componentInfo);
//}
} else { } else {
// Has entity but no component // Has entity but no component
m_World->AttachComponent(localEntityID, componentType); m_World->AttachComponent(localEntityID, componentType);
updateFields(packet, componentInfo, localEntityID, componentType); updateFields(packet, componentInfo, localEntityID);
} }
} else { } else {
// Create Entity and component // Create Entity and component
@@ -246,7 +275,7 @@ void Client::parseSnapshot(Packet& packet)
m_World->SetName(newLocalEntityID, serverEntityName); m_World->SetName(newLocalEntityID, serverEntityName);
insertIntoServerClientMaps(serverEntityID, newLocalEntityID); insertIntoServerClientMaps(serverEntityID, newLocalEntityID);
m_World->AttachComponent(newLocalEntityID, componentType); m_World->AttachComponent(newLocalEntityID, componentType);
updateFields(packet, componentInfo, newLocalEntityID, componentType); updateFields(packet, componentInfo, newLocalEntityID);
} }
} }
// Parent logic // Parent logic
+9 -7
View File
@@ -1,5 +1,14 @@
#include "Network/Network.h" #include "Network/Network.h"
Network::Network(World* world, EventBroker* eventBroker)
: m_World(world)
, m_EventBroker(eventBroker)
{
ConfigFile* config = ResourceManager::Load<ConfigFile>("Config.ini");
m_MaxConnections = config->Get<int>("Networking.MaxConnections", 8);
m_TimeoutMs = config->Get<int>("Networking.TimeoutMs", 20000);
}
void Network::Update() void Network::Update()
{ {
updateNetworkData(); updateNetworkData();
@@ -59,10 +68,3 @@ void Network::updateNetworkData()
m_NetworkData.DataReceivedThisInterval = 0; m_NetworkData.DataReceivedThisInterval = 0;
} }
} }
void Network::initialize()
{
ConfigFile* config = ResourceManager::Load<ConfigFile>("Config.ini");
m_MaxConnections = config->Get<int>("Networking.MaxConnections", 8);
m_TimeoutMs = config->Get<int>("Networking.TimeoutMs", 20000);
}
+20 -19
View File
@@ -1,29 +1,30 @@
#include "Network/Server.h" #include "Network/Server.h"
Server::Server() : m_Socket(m_IOService, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 27666)) Server::Server(World* world, EventBroker* eventBroker, int port)
: Network(world, eventBroker)
{ {
Network::initialize();
ConfigFile* config = ResourceManager::Load<ConfigFile>("Config.ini"); ConfigFile* config = ResourceManager::Load<ConfigFile>("Config.ini");
snapshotInterval = 1000 * config->Get<float>("Networking.SnapshotInterval", 0.05f); snapshotInterval = 1000 * config->Get<float>("Networking.SnapshotInterval", 0.05f);
pingIntervalMs = config->Get<float>("Networking.PingIntervalMs", 1000); pingIntervalMs = config->Get<float>("Networking.PingIntervalMs", 1000);
}
Server::~Server()
{
}
void Server::Start(World* world, EventBroker* eventBroker)
{
m_World = world;
m_EventBroker = eventBroker;
// Subscribe to events // Subscribe to events
EVENT_SUBSCRIBE_MEMBER(m_EInputCommand, &Server::OnInputCommand); EVENT_SUBSCRIBE_MEMBER(m_EInputCommand, &Server::OnInputCommand);
EVENT_SUBSCRIBE_MEMBER(m_EPlayerSpawned, &Server::OnPlayerSpawned); EVENT_SUBSCRIBE_MEMBER(m_EPlayerSpawned, &Server::OnPlayerSpawned);
EVENT_SUBSCRIBE_MEMBER(m_EEntityDeleted, &Server::OnEntityDeleted); EVENT_SUBSCRIBE_MEMBER(m_EEntityDeleted, &Server::OnEntityDeleted);
EVENT_SUBSCRIBE_MEMBER(m_EComponentDeleted, &Server::OnComponentDeleted); EVENT_SUBSCRIBE_MEMBER(m_EComponentDeleted, &Server::OnComponentDeleted);
LOG_INFO("I am Server. BIP BOP\n");
// Bind
if (port == 0) {
port = config->Get<float>("Networking.Port", 27666);
}
m_Port = port;
m_Socket = std::make_unique<boost::asio::ip::udp::socket>(m_IOService, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port));
LOG_INFO("Server initialized and bound to port %i", port);
}
Server::~Server()
{
} }
void Server::Update() void Server::Update()
@@ -38,7 +39,7 @@ void Server::Update()
void Server::readFromClients() void Server::readFromClients()
{ {
while (m_Socket.available()) { while (m_Socket->available()) {
try { try {
bytesRead = receive(readBuffer); bytesRead = receive(readBuffer);
Packet packet(readBuffer, bytesRead); Packet packet(readBuffer, bytesRead);
@@ -105,7 +106,7 @@ void Server::parseMessageType(Packet& packet)
size_t Server::receive(char * data) size_t Server::receive(char * data)
{ {
size_t length = m_Socket.receive_from( size_t length = m_Socket->receive_from(
boost::asio::buffer((void*)data boost::asio::buffer((void*)data
, INPUTSIZE) , INPUTSIZE)
, m_ReceiverEndpoint, 0); , m_ReceiverEndpoint, 0);
@@ -121,7 +122,7 @@ size_t Server::receive(char * data)
void Server::send(PlayerID player, Packet& packet) void Server::send(PlayerID player, Packet& packet)
{ {
try { try {
size_t bytesSent = m_Socket.send_to( size_t bytesSent = m_Socket->send_to(
boost::asio::buffer(packet.Data(), packet.Size()), boost::asio::buffer(packet.Data(), packet.Size()),
m_ConnectedPlayers[player].Endpoint, m_ConnectedPlayers[player].Endpoint,
0); 0);
@@ -139,7 +140,7 @@ void Server::send(PlayerID player, Packet& packet)
void Server::send(Packet & packet) void Server::send(Packet & packet)
{ {
m_Socket.send_to( m_Socket->send_to(
boost::asio::buffer( boost::asio::buffer(
packet.Data(), packet.Data(),
packet.Size()), packet.Size()),
@@ -240,7 +241,7 @@ void Server::checkForTimeOuts()
static_cast<double>(CLOCKS_PER_SEC); static_cast<double>(CLOCKS_PER_SEC);
if (startPing > stopPing + m_TimeoutMs) { if (startPing > stopPing + m_TimeoutMs) {
LOG_INFO("User %i timed out!", i); LOG_INFO("User %i timed out!", i);
disconnect(i); //disconnect(i);
} }
} }
} }
+1 -1
View File
@@ -51,7 +51,7 @@ void Renderer::InitializeWindow()
ss << " DEBUG"; ss << " DEBUG";
#endif #endif
LOG_INFO(ss.str().c_str()); LOG_INFO(ss.str().c_str());
glfwSetWindowTitle(m_Window, ss.str().c_str()); SetWindowTitle(ss.str());
// Initialize GLEW // Initialize GLEW
if (glewInit() != GLEW_OK) { if (glewInit() != GLEW_OK) {
+7 -3
View File
@@ -1,6 +1,6 @@
project(TacticalZ-Game) project(TacticalZ-Game)
find_package(Boost REQUIRED COMPONENTS system filesystem thread chrono) find_package(Boost REQUIRED COMPONENTS system filesystem thread chrono program_options)
set(INCLUDE_PATH ${CMAKE_SOURCE_DIR}/include/Game) set(INCLUDE_PATH ${CMAKE_SOURCE_DIR}/include/Game)
include_directories( include_directories(
@@ -22,13 +22,17 @@ file(GLOB SOURCE_FILES_Events
) )
source_group(Events FILES ${SOURCE_FILES_Events}) source_group(Events FILES ${SOURCE_FILES_Events})
file(GLOB SOURCE_FILES_Network
"${INCLUDE_PATH}/Network/*.h"
"Network/*.cpp"
)
source_group(Network FILES ${SOURCE_FILES_Network})
set(SOURCE_FILES set(SOURCE_FILES
${SOURCE_FILES} ${SOURCE_FILES}
"Game.cpp" "Game.cpp"
${SOURCE_FILES_Systems} ${SOURCE_FILES_Systems}
${SOURCE_FILES_Events} ${SOURCE_FILES_Events}
${SOURCE_FILES_Network}
) )
set(LIBRARIES set(LIBRARIES
+55 -13
View File
@@ -13,10 +13,13 @@
#include "Game/Systems/WeaponSystem.h" #include "Game/Systems/WeaponSystem.h"
#include "Game/Systems/PlayerHUDSystem.h" #include "Game/Systems/PlayerHUDSystem.h"
#include "Game/Systems/LifetimeSystem.h" #include "Game/Systems/LifetimeSystem.h"
#include "../Engine/Rendering/AnimationSystem.h" #include "Rendering/AnimationSystem.h"
#include "Network/MultiplayerSnapshotFilter.h"
Game::Game(int argc, char* argv[]) Game::Game(int argc, char* argv[])
{ {
parseArgs(argc, argv);
ResourceManager::RegisterType<ConfigFile>("ConfigFile"); ResourceManager::RegisterType<ConfigFile>("ConfigFile");
ResourceManager::RegisterType<Sound>("Sound"); ResourceManager::RegisterType<Sound>("Sound");
ResourceManager::RegisterType<Model>("Model"); ResourceManager::RegisterType<Model>("Model");
@@ -73,15 +76,14 @@ Game::Game(int argc, char* argv[])
// Initialize network // Initialize network
if (m_Config->Get<bool>("Networking.StartNetwork", false)) { if (m_Config->Get<bool>("Networking.StartNetwork", false)) {
bool isServer = m_Config->Get<bool>("Networking.IsServer", false); if (m_IsServer) {
if (isServer) { m_NetworkServer = new Server(m_World, m_EventBroker, m_NetworkPort);
m_Network = new Server(); m_Renderer->SetWindowTitle(m_Renderer->WindowTitle() + " SERVER");
m_IsServer = true; } else if (m_IsClient) {
} else { m_NetworkClient = new Client(m_World, m_EventBroker, std::make_unique<MultiplayerSnapshotFilter>(m_EventBroker));
m_Network = new Client(m_Config); m_NetworkClient->Connect(m_NetworkAddress, m_NetworkPort);
m_IsClient = true; m_Renderer->SetWindowTitle(m_Renderer->WindowTitle() + " CLIENT");
} }
m_Network->Start(m_World, m_EventBroker);
} }
// Create Octrees // Create Octrees
@@ -132,8 +134,11 @@ Game::~Game()
delete m_OctreeFrustrumCulling; delete m_OctreeFrustrumCulling;
delete m_OctreeCollision; delete m_OctreeCollision;
delete m_OctreeTrigger; delete m_OctreeTrigger;
if (m_Network != nullptr) { if (m_NetworkClient != nullptr) {
delete m_Network; delete m_NetworkClient;
}
if (m_NetworkServer != nullptr) {
delete m_NetworkServer;
} }
delete m_World; delete m_World;
delete m_FrameStack; delete m_FrameStack;
@@ -163,8 +168,12 @@ void Game::Tick()
m_EventBroker->Swap(); m_EventBroker->Swap();
// Update network // Update network
if (m_Network != nullptr) { m_EventBroker->Process<MultiplayerSnapshotFilter>();
m_Network->Update(); if (m_NetworkClient != nullptr) {
m_NetworkClient->Update();
}
if (m_NetworkServer != nullptr) {
m_NetworkServer->Update();
} }
// Iterate through systems and update world! // Iterate through systems and update world!
@@ -177,3 +186,36 @@ void Game::Tick()
m_EventBroker->Swap(); m_EventBroker->Swap();
m_EventBroker->Clear(); m_EventBroker->Clear();
} }
int Game::parseArgs(int argc, char* argv[])
{
namespace po = boost::program_options;
po::options_description desc("Options");
desc.add_options()
("help", "Help")
("server,s", po::bool_switch(&m_IsServer), "Launch game in server mode")
("connect", po::value<std::string>(&m_NetworkAddress)->default_value(""), "Connect to this address in client mode")
("port,p", po::value<int>(&m_NetworkPort), "Port to listen on or connect to");
;
po::variables_map vm;
try {
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
} catch (std::exception& e) {
LOG_ERROR(e.what());
return 1;
}
if (vm.count("help")) {
std::cout << desc << std::endl;
exit(1);
}
if (vm.count("connect")) {
m_IsClient = true;
}
return 0;
}
@@ -0,0 +1,27 @@
#include "Network/MultiplayerSnapshotFilter.h"
MultiplayerSnapshotFilter::MultiplayerSnapshotFilter(EventBroker* eventBroker)
: m_EventBroker(eventBroker)
{
EVENT_SUBSCRIBE_MEMBER(m_EPlayerSpawned, &MultiplayerSnapshotFilter::OnPlayerSpawned);
}
bool MultiplayerSnapshotFilter::FilterComponent(EntityWrapper entity, SharedComponentWrapper& component)
{
if (entity == m_LocalPlayer || entity.IsChildOf(m_LocalPlayer)) {
return false;
}
if (component.Info.Name == "Transform") {
m_EventBroker->Publish(Events::Interpolate(entity, component));
return false;
}
return true;
}
bool MultiplayerSnapshotFilter::OnPlayerSpawned(Events::PlayerSpawned ePlayerSpawned)
{
m_LocalPlayer = ePlayerSpawned.Player;
return true;
}
@@ -0,0 +1,14 @@
#include "Systems/ExplosionEffectSystem.h"
void ExplosionEffectSystem::UpdateComponent(EntityWrapper& entity, ComponentWrapper& component, double dt)
{
if ((double)component["TimeSinceDeath"] > (double)component["ExplosionDuration"]) {
(double)component["TimeSinceDeath"] = 0.f;
}
(double&)component["TimeSinceDeath"] += dt;
//if ((bool)Component["Gravity"] == true) {
// (bool)Component["ExponentialAccelaration"] = false;
//}
}
+20 -28
View File
@@ -17,6 +17,7 @@ void InterpolationSystem::UpdateComponent(EntityWrapper& entity, ComponentWrappe
return; return;
} }
//return;
if (m_NextTransform.find(transform.EntityID) != m_NextTransform.end()) { // Exists in map if (m_NextTransform.find(transform.EntityID) != m_NextTransform.end()) { // Exists in map
m_NextTransform[transform.EntityID].interpolationTime += static_cast<float>(dt); m_NextTransform[transform.EntityID].interpolationTime += static_cast<float>(dt);
Transform sTransform = m_NextTransform[transform.EntityID]; Transform sTransform = m_NextTransform[transform.EntityID];
@@ -32,21 +33,14 @@ void InterpolationSystem::UpdateComponent(EntityWrapper& entity, ComponentWrappe
} }
} }
if (transform.Info.Name == "Transform") { if (transform.Info.Name == "Transform") {
bool isLocalPlayer = entity == m_LocalPlayer || entity.IsChildOf(m_LocalPlayer);
// Position // Position
glm::vec3 nextPosition = sTransform.Position; glm::vec3 nextPosition = sTransform.Position;
glm::vec3 currentPosition = static_cast<glm::vec3>(transform["Position"]); glm::vec3 currentPosition = static_cast<glm::vec3>(transform["Position"]);
// HACK: Don't force position for players (glm::vec3&)transform["Position"] += vectorInterpolation<glm::vec3>(currentPosition, nextPosition, sTransform.interpolationTime);
if (!isLocalPlayer) {
(glm::vec3&)transform["Position"] += vectorInterpolation<glm::vec3>(currentPosition, nextPosition, sTransform.interpolationTime);
}
// Orientation // Orientation
// Don't force orientation for players glm::quat nextOrientation = sTransform.Orientation;
if (!isLocalPlayer) { glm::quat currentOrientation = glm::quat(static_cast<glm::vec3>(transform["Orientation"]));
glm::quat nextOrientation = sTransform.Orientation; (glm::vec3&)transform["Orientation"] = glm::eulerAngles(glm::slerp<float>(currentOrientation, nextOrientation, glm::max(sTransform.interpolationTime / m_SnapshotInterval, 1.f)));
glm::quat currentOrientation = glm::quat(static_cast<glm::vec3>(transform["Orientation"]));
(glm::vec3&)transform["Orientation"] = glm::eulerAngles(glm::slerp<float>(currentOrientation, nextOrientation, sTransform.interpolationTime / m_SnapshotInterval));
}
// Scale // Scale
glm::vec3 nextScale = sTransform.Scale; glm::vec3 nextScale = sTransform.Scale;
glm::vec3 currentScale = static_cast<glm::vec3>(transform["Scale"]); glm::vec3 currentScale = static_cast<glm::vec3>(transform["Scale"]);
@@ -61,24 +55,22 @@ bool InterpolationSystem::OnPlayerSpawned(Events::PlayerSpawned& e)
return true; return true;
} }
bool InterpolationSystem::OnInterpolate(const Events::Interpolate & e) bool InterpolationSystem::OnInterpolate(Events::Interpolate& e)
{ {
Transform transform; // TODO: Make this work for arbitrary component types
int offset = 0; if (e.Component.Info.Name == "Transform") {
// Read the data Transform transform;
memcpy(&transform.Position, e.DataArray.get() + offset, sizeof(glm::vec3)); transform.Position = e.Component["Position"];
offset += sizeof(glm::vec3); transform.Orientation = glm::quat((glm::vec3)e.Component["Orientation"]);
glm::vec3 tempOrientation; transform.Scale = e.Component["Scale"];
memcpy(&tempOrientation, e.DataArray.get() + offset, sizeof(glm::vec3)); transform.interpolationTime = 0.0f;
transform.Orientation = glm::quat(tempOrientation);
offset += sizeof(glm::vec3);
memcpy(&transform.Scale, e.DataArray.get() + offset, sizeof(glm::vec3));
transform.interpolationTime = 0.0f;
if (m_NextTransform.find(e.Entity) != m_NextTransform.end()) { // Did exist if (m_NextTransform.find(e.Entity.ID) != m_NextTransform.end()) { // Did exist
m_LastReceivedTransform[e.Entity] = transform; m_LastReceivedTransform[e.Entity.ID] = transform;
} else { // Did not } else { // Did not
m_NextTransform[e.Entity] = transform; m_NextTransform[e.Entity.ID] = transform;
}
} }
return false;
return true;
} }