Note: All listings included in this documentation are for example purposes and should not be used in production code due to potential bugs and lack of security.
The EGE::EGEPacket has the following structure:
|B | 4 bytes | 4 bytes |
|---|----------------|----------------|
| 0 | [ size 4B ] | [ type ] |
|16 | [args...] |
|---|----------------|----------------|
size
- packet size, counting from byte 4 to end (omitting size
)
type
- packet type (see "Types of EGE::Packet
")
args
- packet specific arguments, presented in format described in egepacket.md.
The -> SResult
notation means that the packet has assigned uid
property to args
, which is an unique id generated by endpoint (random number based on system time). The SResult
packet is sent in response, which contains operation result (packet-specific) and the uid
assigned to request packet.
The uid
property cannot be used as user-specific in these packets.
Example:
C -> S CLogin {"username":"user","password":"^%!%&F$", "uid":1234}
S -> C SResult {"uid":1234,
"message":"Invalid username or password"}
Packet type prefixes:
_
- Double-side (sent by client and server)
S
- Client-bound (sent by server)
C
- Server-bound (sent by client)
Arguments: user-specific (excluding uid
)
Description: The packet can be used to send any data to second endpoint.
Arguments: none
Description: Requests endpoint to response with _Pong
. Is used to measure ping (packet transfer time) and to check if endpoint is alive.
Arguments: none
Description: It's a reponse to _Ping
packet.
Arguments: {"value": version ID <isInt>}
Description: The packet is used to inform second endpoint about protocol version. The second endpoint should check its version and break the connection if version numbers are not equal.
Arguments: {"uid": operation packet UID}
, user-defined
Description: Informs client about result of last operation.
Arguments: user-defined, usually username, often password
Description: Passes user information to Server. When this packet is received, the onLogin()
function is called in EGE::EGEServer
.
SResult response: {"success": success state <isBool>, "message": message from server <isBool>}
.
Arguments: user-defined, usually none
Description: Requests client to send login information. If client doesn't receive this packet, it assumes that login is not required.
Arguments: {"reason": message from server <isString>}
Description: Informs client, that it must be disconnected from server for any reason specified in reason
property. It should be the last packet that is sent to client before disconnection.
Arguments: {"object": { object... (see SceneObject serialization) }, "id": object id <isInt>, "typeId": object type ID <isString>}
Description: Informs client that the SceneObject of type specified by typeId
and unique ID specified by id
was created on server.
Arguments: {"object": { object... (see SceneObject serialization) }, "id": object id <isInt>}
Description: Informs client that the SceneObject was updated on server.
If the object
property has only m
property, only the m
(main) data are updated. If the object
has x
, the x
(extended) data are updated.
Arguments: {"id": object id <isInt>}
Description: Informs client that the SceneObject was removed on server.
Arguments: user-defined / scene-specific (see Scene serialization)
Description: Informs client that the scene was created or updated on the server. The client-side EGE::Scene
implementation must be derived from EGE::EGESerializableScene
to properly handle this packet.
Arguments: user-defined (usually none)
Description: Informs client that scene was deleted on server.
Arguments: {"id": object id <isInt>}, "data": {user-defined...}}
Description: Requests server to call Controller handler assigned to specified SceneObject. It's used for e.g use keyboard to move a player.
See EGE::Controller example.
SResult response: {"code": user-defined code list}
Arguments: {"id": object id <isInt>}
Description: Specifies the SceneObject ID that will be controlled by default. Id id
is 0, the default controlled object is null
.
Arguments: {"id": object id <isInt>, user-defined}
Description: Informs client about action done by SceneObject. It can be used to improve performance e.g. for animations.
See EGE::Controller example.
Format of Scene
serialization is user-defined. The developer must derive his EGE::Scene
from EGE::EGESerializableScene
and implement methods:
virtual std::shared_ptr<EGE::ObjectMap> serialize() = 0;
virtual void deserialize(std::shared_ptr<EGE::ObjectMap> object) = 0;
The use of EGE::ObjectMap::merge()
function is recommended to keep entries not specified by passed object
.
The SceneObject are serialized in the following way:
{
"m": { main data: (frequently changed, e.g position) }
"x": { extended data: (not frequently changed) }
}
The SceneObjects are deserialized basing on existence of m
and x
properties. If only m
is present, only m
is assigned to SceneObject (the same for x
). However, if both properties exists, the object is reinitialized (the serialize
function is called).
To be able to send over the network, the SceneObject must be derived from EGE::EGESerializableSceneObject
and have defined following methods:
// manage main data
virtual std::shared_ptr<EGE::ObjectMap> serializeMain() const = 0;
virtual bool deserializeMain(std::shared_ptr<EGE::ObjectMap> object) = 0;
// manage extended data
virtual std::shared_ptr<EGE::ObjectMap> serializeExtended() const = 0;
virtual bool deserializeExtended(std::shared_ptr<EGE::ObjectMap> object) = 0;
The use of EGE::ObjectMap::merge()
function is recommended to keep entries not specified by passed object
.
// needed modules: scene,egeNetwork
class MyObject : public EGE::EGESerializableSceneObject, EGE::TexturedObject2D
{
public:
sf::Color m_color;
float m_speed = 0.f;
float m_angle = 0.f;
MyObject(EGE::Scene* owner)
: EGE::TexturedObject2D(owner, "MyGame::MyObject") {}
// manage main data
virtual std::shared_ptr<EGE::ObjectMap> serializeMain() const
{
std::shared_ptr<EGE::ObjectMap> map = std::make_shared<EGE::ObjectMap>();
// save speed value
map->addFloat("speed", m_speed);
map->addFloat("angle", m_angle);
// save generic SceneObject data and merge with our data
return EGE::TexturedObject2D::serializeMain()->merge(map);
}
virtual bool deserializeMain(std::shared_ptr<EGE::ObjectMap> object)
{
// load generic SceneObject data
EGE::TexturedObject2D::deserializeMain(object);
// load speed value
m_speed = object->getObject("speed").as<EGE::Float>().valueOr(0);
m_angle = object->getObject("angle").as<EGE::Float>().valueOr(0);
return true;
}
// manage extended data
virtual std::shared_ptr<EGE::ObjectMap> serializeExtended() const
{
std::shared_ptr<EGE::ObjectMap> map = std::make_shared<EGE::ObjectMap>();
// load color
map->addObject("color", EGE::ObjectSerializers::fromColor(m_color));
// save generic SceneObject data and merge with our data
return EGE::TexturedObject2D::serializeExtended()->merge(object);
}
virtual bool serializeExtended(std::shared_ptr<EGE::ObjectMap> object)
{
// load generic SceneObject data
EGE::TexturedObject2D::serializeExtended(object);
// load color
m_color = EGE::ObjectSerializers::toColor(object->getObject("color").to<EGE::ObjectMap>());
return true;
}
virtual void onUpdate(long long tick)
{
EGE::SceneObject2D::onUpdate(tick);
setMotion(EGE::VectorOperations::fromPolar(m_speed, m_angle));
}
virtual void render(sf::RenderTarget& target, EGE::RenderStates& states)
{
// do some render basing on m_color
// NOTE: the shader and renderstates api is not implemented but probably will look like these:
states.custom["EGE::TexturedObject2D::spriteColorOverride"] = m_color;
states.shader = std::make_shared<EGE::MotionBlurShader>(m_speed, m_angle);
EGE::TexturedObject2D::render(target, states);
}
};
graph TB
subgraph Server side
NET2{network} --server OS--> EGES[EGE::EGEServer]
EGES --EGE::Controller API--> SCCtrl[Server-side Controller Output]
SCCtrl --EGE::SceneObject API--> SO((Gameplay Object / Logic))
SO --user-defined API--> SSCtrl
SSCtrl[Server-side Controller Input] --EGE::EGEServer API--> EGES
EGES[EGE::EGEServer] --server OS--> NET2{network}
end
NET1-->NET2
subgraph Client side
IO((Client I/O)) --user-defined API--> CCCtrl
CCCtrl[Client-side Controller Output] --EGE::EGEClient API --> EGEC[EGE::EGEClient]
EGEC --client OS--> NET1{network}
NET1{network} --client OS--> EGEC
EGEC[EGE::EGEClient] --user-defined API--> CSCtrl[Client-side Controller Input]
CSCtrl --Client output API--> IO
end
// needed modules: scene,egeNetwork,controller
// we are using MyObject from "SceneObject serialization example"
class MyObjectControlled : public MyObject
{
public:
MyObjectControlled(EGE::Scene* scene)
: MyObject(scene)
{
setController(std::make_shared<MyController>(this));
}
virtual EGE::ServerResult handleServerController(EGE::SceneObject& object, EGE::ObjectMap& controllerArgs)
{
MyObjectControlled& myObject = (MyObject&) object;
// speed is set by 'moving' flag send by client, it prevents clients
// from setting unreal speed (cheating)
myObject.m_speed = controllerArgs.getObject("moving").lock()->asBool() ? 1.f : 0.f;
// angle is sent by client
myObject.m_angle = controllerArgs.getObject("angle").lock()->asFloat();
return EGE::ServerResult::Success;
}
};
class MyClientController
{
public:
// returns map that will be sent to server in CSceneObjectControl packet
std::shared_ptr<EGE::ObjectMap> move(EGE::SceneObject& object, bool moving, float angle)
{
std::shared_ptr<EGE::ObjectMap> map = std::make_shared<EGE::ObjectMap>();
// create object that we want to send to server
map->addObject("moving", std::make_shared<EGE::ObjectBool>(moving));
map->addObject("angle", std::make_shared<EGE::ObjectFloat>(angle));
return map;
}
}
class MyServerController
{
public:
std::shared_ptr<EGE::ObjectMap> animate(EGE::SceneObject& object)
{
// No data are sent.
return nullptr;
}
};
class MyClient : public EGE::EGEClient
{
};
// somewhere in GUI...
MyClient m_client;
void mouseButtonPressed(int x, int y)
{
float angle = EGE::VectorConverters::angleTo(
// vector begin: client position on scene
sf::Vector2f(((EGE::SceneObject2D*)m_client.getClientObject().lock().get())->getPosition()),
// vector end: mouse position mapped to scene view
// it can be done only with Scene2D
sf::Vector2f(((EGE::Scene2D*)m_client.getScene().lock().get())->mapView(sf::Vector2i(x, y));
);
MyClientController* controller = (MyClientController*)m_client.getController().lock().get();
controller->move(*m_client.getClientObject().lock(), true, angle);
}
void mouseButtonReleased(int x, int y)
{
float angle = EGE::VectorConverters::angleTo(
// vector begin: client position on scene
sf::Vector2f(((EGE::SceneObject2D*)m_client.getClientObject().lock().get())->getPosition()),
// vector end: mouse position mapped to scene view
// it can be done only with Scene2D
sf::Vector2f(((EGE::Scene2D*)m_client.getScene().lock().get())->mapView(sf::Vector2i(x, y));
);
MyClientController controller = (MyClientController*)m_client.getController().lock();
controller->move(*m_client.getClientObject().lock(), false, angle);
}