Skip to content

Instantly share code, notes, and snippets.

@sppmacd
Last active December 3, 2020 11:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sppmacd/2c9c62341c2a00eed88b7a1783a46d92 to your computer and use it in GitHub Desktop.
Save sppmacd/2c9c62341c2a00eed88b7a1783a46d92 to your computer and use it in GitHub Desktop.
egeNetwork

egeNetwork Protocol and API

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.

EGE::EGEPacket

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.

Types of EGE::EGEPacket

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)

_Data (0x00) -> SResult

Arguments: user-specific (excluding uid)
Description: The packet can be used to send any data to second endpoint.

_Ping (0x01)

Arguments: none
Description: Requests endpoint to response with _Pong. Is used to measure ping (packet transfer time) and to check if endpoint is alive.

_Pong (0x02)

Arguments: none
Description: It's a reponse to _Ping packet.

_ProtocolVersion (0x03)

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.

SResult (0x04)

Arguments: {"uid": operation packet UID}, user-defined
Description: Informs client about result of last operation.

CLogin (0x05) -> SResult

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>}.

SLoginRequest (0x06)

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.

SDisconnectReason (0x07)

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.

SSceneObjectCreation (0x08)

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.

SSceneObjectUpdate (0x09)

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.

SSceneObjectDeletion (0x0a)

Arguments: {"id": object id <isInt>}
Description: Informs client that the SceneObject was removed on server.

SSceneCreation (0x0b)

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.

SSceneDeletion (0x0c)

Arguments: user-defined (usually none)
Description: Informs client that scene was deleted on server.

CSceneObjectControl (0x0d) -> SResult

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}

SDefaultControllerId (0x0e)

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.

SSceneObjectControl (0x0f)

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.

Scene serialization

Format

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.

SceneObject serialization

Format

The SceneObject are serialized in the following way:

{
	"m": { main data: (frequently changed, e.g position) }
	"x": { extended data: (not frequently changed) }
}

Deserialization

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.

Example

// 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);
	}
};

Examples

EGE::Controller

Graph

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

Example code

// 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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment