Compare commits

...

48 Commits

Author SHA1 Message Date
edmand46 27db256902 🐛 scene entities not execute buffered events 2023-09-03 13:29:37 +03:00
edmand46 8705e93929 🐛 event size 2023-08-02 22:13:51 +03:00
edmand46 08e931d1bd 🚑 remove static entity spawn 2023-08-01 22:21:21 +03:00
edmand46 fb58dedfaf changed scene loading flow 2023-07-30 21:46:42 +03:00
edmand46 92062cd708 🎨 update naming of fields 2023-07-30 21:19:39 +03:00
edmand46 5fc55eaddc wip 2023-07-30 21:14:14 +03:00
edmand46 4a8aae11e3 🚑 fixed empty state values 2023-07-30 17:44:51 +03:00
edmand46 cd9304e63a Merge pull request #13 from edmand46/fix/payload
Fix/payload
2023-07-30 17:01:08 +03:00
edmand46 5199b5271b 🐛 Remove listener inside callback 2023-07-30 16:56:11 +03:00
edmand46 c01b748031 wip 2023-07-29 10:58:06 +03:00
edmand46 0a8d761cc1 🐛 empty payload 2023-07-23 15:56:08 +03:00
edmand46 f38c7e98de ⬆️ deps 2023-07-23 11:23:38 +03:00
edmand46 0479a21980 feat: added safe get entity by id 2023-07-09 07:40:06 +03:00
edmand46 1406b17d62 🚑 independent buffers in properties 2023-07-01 08:12:40 +03:00
edmand46 105457ffa0 added transfer ownership, limit buffered events 2023-07-01 07:47:57 +03:00
edmand46 20662ae24d wip 2023-06-27 23:41:30 +03:00
edmand46 6c441d9dee feat: Entity.OnEvent now support resubscribing 2023-05-25 11:28:18 +03:00
edmand46 907bd2611e added reason of disconnect at callback 2023-05-08 00:16:43 +03:00
edmand46 91d8516ac9 independent buffers for ragon properties, extended buffer functionality 2023-05-07 23:54:07 +03:00
edmand46 ecdafeab00 ♻️ naming changing 2023-05-07 18:16:46 +03:00
edmand46 88baff9fee compressor extensions for write/read data and reduce boilerplate 2023-05-07 18:07:56 +03:00
edmand46 fdb41649b2 ♻️ added checks in client sdk 2023-05-07 12:46:39 +03:00
edmand46 aa607a7eb9 Update README.md 2023-05-06 15:30:16 +04:00
edmand46 efebf4ceda 🐛 scene entities 2023-04-14 17:42:27 +04:00
edmand46 17d1b7307d ♻️ plugin api 2023-04-14 14:32:24 +04:00
edmand46 fc28f512ba ♻️ plugin api 2023-04-14 14:32:04 +04:00
edmand46 6c4a51534a Merge pull request #12 from edmand46/develop
Plugins
2023-04-13 20:49:31 +04:00
edmand46 c91551ae08 📝 update headers 2023-04-13 20:49:00 +04:00
edmand46 e1a9ad476c http-commands 2023-04-13 20:42:05 +04:00
edmand46 24c9aa2043 🎨 namespaces 2023-04-09 11:06:52 +04:00
edmand46 bfd6c1b54b 🚧 plugin system, webhook system 2023-04-09 10:52:18 +04:00
edmand46 f2edc94958 chore: move sources to Sources folder 2023-04-05 18:53:21 +04:00
edmand46 b8dfc4cf41 chore: removed unused project 2023-04-05 18:48:37 +04:00
edmand46 bd7713bfcb fixed: cache sending on disconnecting 2023-03-31 12:56:23 +04:00
edmand46 043523d712 fixed: checking owner 2023-03-25 20:52:36 +04:00
edmand46 0dc5307b92 fixed: tickrate 2023-03-23 19:17:54 +04:00
edmand46 7b581b9afe chore: added tickrate logging 2023-03-23 18:07:48 +04:00
edmand46 8c5e063ef0 fixed: initial dirty property not tracked 2023-03-23 14:05:03 +04:00
edmand46 1a5f72a815 fixed: property update 2023-03-23 05:28:47 +04:00
edmand46 828da0d3da fix: wrong initial capacity 2023-03-22 11:04:14 +04:00
edmand46 951174e491 feat: split interface IRagonListener 2023-03-07 13:12:48 +04:00
edmand46 10b85867af feat: split interface IRagonListener 2023-03-07 00:57:13 +04:00
edmand46 252a46a713 chore: naming 2023-03-06 10:46:02 +04:00
edmand46 273c167c87 chore: naming 2023-03-06 10:31:42 +04:00
edmand46 192fb9e8eb chore: grammarly mistake 2023-03-06 10:29:22 +04:00
edmand46 a8ddc40268 Merge pull request #11 from edmand46/next
Update readme
2023-03-06 10:23:13 +04:00
edmand46 1ae545b353 chore: update readme.md 2023-03-06 10:22:27 +04:00
edmand46 b2058d21ce Merge pull request #10 from edmand46/next
Major update
2023-03-06 10:11:32 +04:00
119 changed files with 2350 additions and 954 deletions
+2
View File
@@ -4,3 +4,5 @@
obj obj
bin bin
*.user *.user
*.dylib
*.dll
+1 -4
View File
@@ -6,7 +6,7 @@
Ragon is fully free, small and high perfomance room based game server with plugin based architecture. Ragon is fully free, small and high perfomance room based game server with plugin based architecture.
<a href="https://www.ragon-server.com/docs/installation">Documentation</a> <a href="https://www.ragon-server.com/docs/overview">Documentation</a>
### Features: ### Features:
- Effective - Effective
@@ -26,9 +26,6 @@ Ragon is fully free, small and high perfomance room based game server with plugi
### Dependencies ### Dependencies
* ENet-Sharp [v2.4.8] * ENet-Sharp [v2.4.8]
### License
MIT
### Tips ### Tips
\* Limited to 4095 CCU by library ENet-Sharp (1) \* Limited to 4095 CCU by library ENet-Sharp (1)
\* Non finally (2) \* Non finally (2)
+45 -39
View File
@@ -1,8 +1,10 @@
using Ragon.Protocol;
namespace Ragon.Client.Simulation; namespace Ragon.Client.Simulation;
public class Game : IRagonListener public class Game : IRagonListener, IRagonSceneRequestListener
{ {
private RagonFloat _health; private RagonFloat _health;
private RagonInt _points; private RagonInt _points;
@@ -18,7 +20,7 @@ public class Game : IRagonListener
public void OnConnected(RagonClient client) public void OnConnected(RagonClient client)
{ {
RagonLog.Trace("Connected"); RagonLog.Trace("Connected");
_client.Session.AuthorizeWithKey("defaultkey", "Player Eduard", Array.Empty<byte>()); _client.Session.AuthorizeWithKey("defaultkey", "Player Eduard");
} }
public void OnAuthorizationSuccess(RagonClient client, string playerId, string playerName) public void OnAuthorizationSuccess(RagonClient client, string playerId, string playerName)
@@ -33,6 +35,41 @@ public class Game : IRagonListener
} }
public void OnJoined(RagonClient client) public void OnJoined(RagonClient client)
{
}
public void OnFailed(RagonClient client, string message)
{
RagonLog.Trace("Failed to join");
}
public void OnLeft(RagonClient client)
{
RagonLog.Trace("Left");
}
public void OnDisconnected(RagonClient client, RagonDisconnect ragonDisconnect)
{
RagonLog.Trace("Disconnected");
}
public void OnPlayerJoined(RagonClient client, RagonPlayer player)
{
RagonLog.Trace("Player joined");
}
public void OnPlayerLeft(RagonClient client, RagonPlayer player)
{
RagonLog.Trace("Player left");
}
public void OnOwnershipChanged(RagonClient client, RagonPlayer player)
{
RagonLog.Trace("Owner ship changed");
}
public void OnSceneLoaded(RagonClient client)
{ {
RagonLog.Trace("Joined"); RagonLog.Trace("Joined");
@@ -53,43 +90,6 @@ public class Game : IRagonListener
client.Room.CreateEntity(_entity); client.Room.CreateEntity(_entity);
} }
public void OnFailed(RagonClient client, string message)
{
RagonLog.Trace("Failed to join");
}
public void OnLeft(RagonClient client)
{
RagonLog.Trace("Left");
}
public void OnDisconnected(RagonClient client)
{
RagonLog.Trace("Disconnected");
}
public void OnPlayerJoined(RagonClient client, RagonPlayer player)
{
RagonLog.Trace("Player joined");
}
public void OnPlayerLeft(RagonClient client, RagonPlayer player)
{
RagonLog.Trace("Player left");
}
public void OnOwnershipChanged(RagonClient client, RagonPlayer player)
{
RagonLog.Trace("Owner ship changed");
}
public void OnLevel(RagonClient client, string sceneName)
{
RagonLog.Trace($"New level: {sceneName}");
client.Room.SceneLoaded();
}
private float _timer = 0; private float _timer = 0;
public void Update() public void Update()
@@ -107,4 +107,10 @@ public class Game : IRagonListener
_timer = 0; _timer = 0;
} }
} }
public void OnRequestScene(RagonClient client, string sceneName)
{
client.Room.SceneLoaded();
}
} }
@@ -16,6 +16,7 @@
using ENet; using ENet;
using Ragon.Protocol;
using Event = ENet.Event; using Event = ENet.Event;
using EventType = ENet.EventType; using EventType = ENet.EventType;
@@ -31,7 +32,7 @@ namespace Ragon.Client
public Action<byte[]> OnData { get; set; } public Action<byte[]> OnData { get; set; }
public Action OnConnected { get; set; } public Action OnConnected { get; set; }
public Action<DisconnectReason> OnDisconnected { get; set; } public Action<RagonDisconnect> OnDisconnected { get; set; }
public ulong BytesSent { get; } public ulong BytesSent { get; }
public ulong BytesReceived { get; } public ulong BytesReceived { get; }
public int Ping { get; } public int Ping { get; }
@@ -98,10 +99,10 @@ namespace Ragon.Client
OnConnected?.Invoke(); OnConnected?.Invoke();
break; break;
case EventType.Disconnect: case EventType.Disconnect:
OnDisconnected?.Invoke(DisconnectReason.MANUAL); OnDisconnected?.Invoke(RagonDisconnect.SERVER);
break; break;
case EventType.Timeout: case EventType.Timeout:
OnDisconnected?.Invoke(DisconnectReason.TIMEOUT); OnDisconnected?.Invoke(RagonDisconnect.TIMEOUT);
break; break;
case EventType.Receive: case EventType.Receive:
var data = new byte[_netEvent.Packet.Length]; var data = new byte[_netEvent.Packet.Length];
@@ -16,6 +16,7 @@
using ENet; using ENet;
using Ragon.Protocol;
using Event = ENet.Event; using Event = ENet.Event;
using EventType = ENet.EventType; using EventType = ENet.EventType;
@@ -31,7 +32,7 @@ namespace Ragon.Client
public Action<byte[]> OnData { get; set; } public Action<byte[]> OnData { get; set; }
public Action OnConnected { get; set; } public Action OnConnected { get; set; }
public Action<DisconnectReason> OnDisconnected { get; set; } public Action<RagonDisconnect> OnDisconnected { get; set; }
public ulong BytesSent { get; } public ulong BytesSent { get; }
public ulong BytesReceived { get; } public ulong BytesReceived { get; }
public int Ping { get; } public int Ping { get; }
@@ -99,10 +100,10 @@ namespace Ragon.Client
OnConnected?.Invoke(); OnConnected?.Invoke();
break; break;
case EventType.Disconnect: case EventType.Disconnect:
OnDisconnected?.Invoke(DisconnectReason.MANUAL); OnDisconnected?.Invoke(RagonDisconnect.SERVER);
break; break;
case EventType.Timeout: case EventType.Timeout:
OnDisconnected?.Invoke(DisconnectReason.TIMEOUT); OnDisconnected?.Invoke(RagonDisconnect.TIMEOUT);
break; break;
case EventType.Receive: case EventType.Receive:
var data = new byte[_netEvent.Packet.Length]; var data = new byte[_netEvent.Packet.Length];
+3 -3
View File
@@ -1,22 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>10</LangVersion> <LangVersion>10</LangVersion>
<RootNamespace>Ragon.Client.Simulation</RootNamespace> <RootNamespace>Ragon.Client.Simulation</RootNamespace>
<Authors>Eduard Kargin (Edmand46)</Authors> <Authors>Eduard Kargin (Edmand46)</Authors>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>none</DebugType> <DebugType>none</DebugType>
<OutputPath>/Users/edmand46/RagonProjects/ragon-unity-sdk/Assets/Ragon/Runtime/Plugins</OutputPath> <OutputPath>/Users/edmand46/RagonProjects/ragon-oss-examples/Assets/Ragon/Plugins/</OutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>/Users/edmand46/RagonProjects/ragon-unity-sdk/Assets/Ragon/Runtime/Plugins</OutputPath> <OutputPath>/Users/edmand46/RagonProjects/ragon-oss-examples/Assets/Ragon/Plugins/</OutputPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+46 -23
View File
@@ -22,6 +22,7 @@ namespace Ragon.Client
public sealed class RagonEntity public sealed class RagonEntity
{ {
private delegate void OnEventDelegate(RagonPlayer player, RagonBuffer serializer); private delegate void OnEventDelegate(RagonPlayer player, RagonBuffer serializer);
private RagonClient _client; private RagonClient _client;
public ushort Id { get; private set; } public ushort Id { get; private set; }
@@ -48,18 +49,20 @@ namespace Ragon.Client
private RagonPayload _spawnPayload; private RagonPayload _spawnPayload;
private RagonPayload _destroyPayload; private RagonPayload _destroyPayload;
private Dictionary<int, OnEventDelegate> _events = new(); private readonly Dictionary<int, OnEventDelegate> _events = new Dictionary<int, OnEventDelegate>();
private Dictionary<int, Action<RagonPlayer, IRagonEvent>> _localEvents = new(); private readonly Dictionary<int, Action<RagonPlayer, IRagonEvent>> _localEvents = new Dictionary<int, Action<RagonPlayer, IRagonEvent>>();
public RagonEntity(ushort type = 0, ushort sceneId = 0) public RagonEntity(ushort type = 0, ushort sceneId = 0)
{ {
State = new RagonEntityState(this); State = new RagonEntityState(this);
Type = type; Type = type;
_spawnPayload = new RagonPayload(0);
_destroyPayload = new RagonPayload(0);
_sceneId = sceneId; _sceneId = sceneId;
} }
internal void Attach(RagonClient client, ushort entityId, ushort entityType, bool hasAuthority, RagonPlayer owner, RagonPayload payload) internal void Attach(RagonClient client, ushort entityId, ushort entityType, bool hasAuthority, RagonPlayer owner)
{ {
Type = entityType; Type = entityType;
Id = entityId; Id = entityId;
@@ -69,7 +72,6 @@ namespace Ragon.Client
HasAuthority = hasAuthority; HasAuthority = hasAuthority;
_client = client; _client = client;
_spawnPayload = payload;
Attached?.Invoke(this); Attached?.Invoke(this);
} }
@@ -83,21 +85,29 @@ namespace Ragon.Client
internal T GetPayload<T>(RagonPayload data) where T : IRagonPayload, new() internal T GetPayload<T>(RagonPayload data) where T : IRagonPayload, new()
{ {
var payload = new T();
if (data.Size <= 0) return payload;
var buffer = new RagonBuffer(); var buffer = new RagonBuffer();
data.Write(buffer); data.Write(buffer);
var payload = new T();
payload.Deserialize(buffer); payload.Deserialize(buffer);
return payload; return payload;
} }
public T GetSpawnPayload<T>() where T : IRagonPayload, new() public void AttachPayload(RagonPayload payload)
{
_spawnPayload = payload;
}
public T GetAttachPayload<T>() where T : IRagonPayload, new()
{ {
return GetPayload<T>(_spawnPayload); return GetPayload<T>(_spawnPayload);
} }
public T GetDestroyPayload<T>() where T : IRagonPayload, new() public T GetDetachPayload<T>() where T : IRagonPayload, new()
{ {
return GetPayload<T>(_destroyPayload); return GetPayload<T>(_destroyPayload);
} }
@@ -105,6 +115,12 @@ namespace Ragon.Client
public void ReplicateEvent<TEvent>(TEvent evnt, RagonPlayer target, RagonReplicationMode replicationMode) public void ReplicateEvent<TEvent>(TEvent evnt, RagonPlayer target, RagonReplicationMode replicationMode)
where TEvent : IRagonEvent, new() where TEvent : IRagonEvent, new()
{ {
if (!IsAttached)
{
RagonLog.Error("Entity not attached");
return;
}
var evntId = _client.Event.GetEventCode(evnt); var evntId = _client.Event.GetEventCode(evnt);
var buffer = _client.Buffer; var buffer = _client.Buffer;
@@ -112,9 +128,9 @@ namespace Ragon.Client
buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
buffer.WriteUShort(Id); buffer.WriteUShort(Id);
buffer.WriteUShort(evntId); buffer.WriteUShort(evntId);
buffer.WriteByte((byte) replicationMode); buffer.WriteByte((byte)replicationMode);
buffer.WriteByte((byte) RagonTarget.Player); buffer.WriteByte((byte)RagonTarget.Player);
buffer.WriteUShort((ushort) target.PeerId); buffer.WriteUShort(target.PeerId);
evnt.Serialize(buffer); evnt.Serialize(buffer);
@@ -128,31 +144,34 @@ namespace Ragon.Client
RagonReplicationMode replicationMode = RagonReplicationMode.Server) RagonReplicationMode replicationMode = RagonReplicationMode.Server)
where TEvent : IRagonEvent, new() where TEvent : IRagonEvent, new()
{ {
if (!IsAttached)
{
RagonLog.Error("Entity not attached");
return;
}
var eventCode = _client.Event.GetEventCode(evnt);
if (target != RagonTarget.ExceptOwner) if (target != RagonTarget.ExceptOwner)
{ {
if (replicationMode == RagonReplicationMode.Local) if (replicationMode == RagonReplicationMode.Local)
{ {
var eventCode = _client.Event.GetEventCode(evnt);
_localEvents[eventCode].Invoke(_client.Room.Local, evnt); _localEvents[eventCode].Invoke(_client.Room.Local, evnt);
return; return;
} }
if (replicationMode == RagonReplicationMode.LocalAndServer) if (replicationMode == RagonReplicationMode.LocalAndServer)
{ {
var eventCode = _client.Event.GetEventCode(evnt);
_localEvents[eventCode].Invoke(_client.Room.Local, evnt); _localEvents[eventCode].Invoke(_client.Room.Local, evnt);
} }
} }
var evntId = _client.Event.GetEventCode(evnt);
var buffer = _client.Buffer; var buffer = _client.Buffer;
buffer.Clear(); buffer.Clear();
buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT); buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
buffer.WriteUShort(Id); buffer.WriteUShort(Id);
buffer.WriteUShort(evntId); buffer.WriteUShort(eventCode);
buffer.WriteByte((byte) replicationMode); buffer.WriteByte((byte)replicationMode);
buffer.WriteByte((byte) target); buffer.WriteByte((byte)target);
evnt.Serialize(buffer); evnt.Serialize(buffer);
@@ -167,11 +186,13 @@ namespace Ragon.Client
if (_events.ContainsKey(eventCode)) if (_events.ContainsKey(eventCode))
{ {
RagonLog.Warn($"Event already {eventCode} subscribed"); _events.Remove(eventCode);
return; _localEvents.Remove(eventCode);
RagonLog.Warn($"Event already {eventCode} subscribed, removed old one!");
} }
_localEvents.Add(eventCode, (player, eventData) => { callback.Invoke(player, (TEvent) eventData); }); _localEvents.Add(eventCode, (player, eventData) => { callback.Invoke(player, (TEvent)eventData); });
_events.Add(eventCode, (player, serializer) => _events.Add(eventCode, (player, serializer) =>
{ {
t.Deserialize(serializer); t.Deserialize(serializer);
@@ -196,8 +217,10 @@ namespace Ragon.Client
internal void Event(ushort eventCode, RagonPlayer caller, RagonBuffer buffer) internal void Event(ushort eventCode, RagonPlayer caller, RagonBuffer buffer)
{ {
if (_events.ContainsKey(eventCode)) if (_events.TryGetValue(eventCode, out var evnt))
_events[eventCode]?.Invoke(caller, buffer); evnt?.Invoke(caller, buffer);
else
RagonLog.Warn($"Handler event on entity {Id} with eventCode {eventCode} not defined");
} }
internal void TrackChangedProperty(RagonProperty property) internal void TrackChangedProperty(RagonProperty property)
@@ -210,7 +233,7 @@ namespace Ragon.Client
var prevOwner = Owner; var prevOwner = Owner;
Owner = player; Owner = player;
HasAuthority = player.PeerId == _client.Room.Local.PeerId; HasAuthority = player.IsLocal;
OwnershipChanged?.Invoke(prevOwner, player); OwnershipChanged?.Invoke(prevOwner, player);
} }
@@ -53,7 +53,7 @@ public sealed class RagonEntityState
{ {
var changed = buffer.ReadBool(); var changed = buffer.ReadBool();
if (changed) if (changed)
property.Deserialize(buffer); property.Read(buffer);
} }
} }
+5 -9
View File
@@ -19,10 +19,10 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
public struct RagonPayload public class RagonPayload
{ {
private uint[] _data = new uint[128]; private readonly uint[] _data = new uint[128];
private int _size = 0; private readonly int _size = 0;
public RagonPayload(int capacity) public RagonPayload(int capacity)
{ {
@@ -32,16 +32,12 @@ public struct RagonPayload
public void Read(RagonBuffer buffer) public void Read(RagonBuffer buffer)
{ {
var readOnlySpan = _data.AsSpan(); buffer.ReadArray(_data, _size);
buffer.ReadSpan(ref readOnlySpan, _size);
} }
public void Write(RagonBuffer buffer) public void Write(RagonBuffer buffer)
{ {
ReadOnlySpan<uint> readOnlySpan = _data.AsSpan(); buffer.WriteArray(_data, _size);
buffer.WriteSpan(ref readOnlySpan, _size);
} }
public override string ToString() public override string ToString()
+40 -18
View File
@@ -29,22 +29,25 @@ namespace Ragon.Client
public bool IsFixed => _fixed; public bool IsFixed => _fixed;
public int Size => _size; public int Size => _size;
private bool _fixed; private RagonBuffer _propertyBuffer;
private string _name;
protected bool _invokeLocal;
private RagonEntity _entity; private RagonEntity _entity;
private bool _dirty; private bool _dirty;
private int _size; private int _size;
private int _ticks; private int _ticks;
private int _priority; private int _priority;
private bool _fixed;
private string _name;
protected bool InvokeLocal;
protected RagonProperty(int priority, bool invokeLocal) protected RagonProperty(int priority, bool invokeLocal)
{ {
_size = 0; _size = 0;
_priority = priority; _priority = priority;
_fixed = false; _fixed = false;
_invokeLocal = invokeLocal; _propertyBuffer = new RagonBuffer();
InvokeLocal = invokeLocal;
} }
public void SetName(string name) public void SetName(string name)
@@ -60,7 +63,7 @@ namespace Ragon.Client
protected void InvokeChanged() protected void InvokeChanged()
{ {
if (!_invokeLocal) if (!InvokeLocal)
return; return;
Changed?.Invoke(); Changed?.Invoke();
@@ -70,10 +73,11 @@ namespace Ragon.Client
{ {
InvokeChanged(); InvokeChanged();
if (_dirty) return; if (_dirty || _entity == null)
_dirty = true; return;
_entity?.TrackChangedProperty(this); _dirty = true;
_entity.TrackChangedProperty(this);
} }
internal void Flush() internal void Flush()
@@ -87,29 +91,47 @@ namespace Ragon.Client
_ticks++; _ticks++;
} }
internal void AssignEntity(RagonEntity obj) internal void AssignEntity(RagonEntity ent)
{ {
_entity = obj; _entity = ent;
Changed?.Invoke(); Changed?.Invoke();
} }
internal void Write(RagonBuffer buffer) internal void Write(RagonBuffer buffer)
{ {
_propertyBuffer.Clear();
if (_fixed) if (_fixed)
{ {
Serialize(buffer); Serialize(_propertyBuffer);
buffer.FromBuffer(_propertyBuffer, _size);
return; return;
} }
var sizeOffset = buffer.WriteOffset; Serialize(_propertyBuffer);
buffer.Write(0, 16);
var propOffset = buffer.WriteOffset;
Serialize(buffer); var propertySize = (ushort) _propertyBuffer.WriteOffset;
buffer.WriteUShort(propertySize);;
buffer.FromBuffer(_propertyBuffer, propertySize);
}
var propSize = (uint) (buffer.WriteOffset - propOffset); internal void Read(RagonBuffer buffer)
buffer.Write(propSize, 16, sizeOffset); {
_propertyBuffer.Clear();
if (_fixed)
{
buffer.ToBuffer(_propertyBuffer, _size);
Deserialize(_propertyBuffer);
return;
}
var propSize = buffer.ReadUShort();
buffer.ToBuffer(_propertyBuffer, propSize);
Deserialize(_propertyBuffer);
} }
public virtual void Serialize(RagonBuffer buffer) public virtual void Serialize(RagonBuffer buffer)
@@ -22,9 +22,13 @@ namespace Ragon.Client;
internal class AuthorizeSuccessHandler: Handler internal class AuthorizeSuccessHandler: Handler
{ {
private readonly RagonListenerList _listenerList; private readonly RagonListenerList _listenerList;
private readonly RagonClient _client;
public AuthorizeSuccessHandler(RagonListenerList listenerList) public AuthorizeSuccessHandler(
RagonClient client,
RagonListenerList listenerList)
{ {
_client = client;
_listenerList = listenerList; _listenerList = listenerList;
} }
@@ -32,7 +36,9 @@ internal class AuthorizeSuccessHandler: Handler
{ {
var playerId = buffer.ReadString(); var playerId = buffer.ReadString();
var playerName = buffer.ReadString(); var playerName = buffer.ReadString();
var playerPayload = buffer.ReadString();
_listenerList.OnAuthorizationSuccess(playerId, playerName); _client.SetStatus(RagonStatus.LOBBY);
_listenerList.OnAuthorizationSuccess(playerId, playerName, playerPayload);
} }
} }
@@ -23,16 +23,18 @@ internal class EntityCreateHandler : Handler
private readonly RagonClient _client; private readonly RagonClient _client;
private readonly RagonPlayerCache _playerCache; private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache; private readonly RagonEntityCache _entityCache;
private readonly IRagonEntityListener _entityListener;
public EntityCreateHandler( public EntityCreateHandler(
RagonClient client, RagonClient client,
RagonPlayerCache playerCache, RagonPlayerCache playerCache,
RagonEntityCache entityCache RagonEntityCache entityCache,
IRagonEntityListener entityListener
) )
{ {
_client = client; _client = client;
_entityCache = entityCache; _entityCache = entityCache;
_playerCache = playerCache; _playerCache = playerCache;
_entityListener = entityListener;
} }
public void Handle(RagonBuffer buffer) public void Handle(RagonBuffer buffer)
@@ -51,8 +53,14 @@ internal class EntityCreateHandler : Handler
return; return;
} }
var hasAuthority = _playerCache.LocalPlayer.Id == player.Id; var hasAuthority = _playerCache.Local.Id == player.Id;
var entity = _entityCache.OnCreate(attachId, entityType, 0, entityId, hasAuthority); var entity = _entityCache.TryGetEntity(attachId, entityType, 0, entityId, hasAuthority, out var hasCreated);
entity.Attach(_client, entityId, entityType, hasAuthority, player, payload);
entity.AttachPayload(payload);
if (hasCreated)
_entityListener.OnEntityCreated(entity);
entity.Attach(_client, entityId, entityType, hasAuthority, player);
} }
} }
@@ -49,7 +49,7 @@ internal class EntityEventHandler : Handler
return; return;
} }
if (player.IsMe && executionMode == RagonReplicationMode.LocalAndServer) if (player.IsLocal && executionMode == RagonReplicationMode.LocalAndServer)
return; return;
_entityCache.OnEvent(player, entityId, eventCode, buffer); _entityCache.OnEvent(player, entityId, eventCode, buffer);
@@ -19,13 +19,13 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
internal class OwnershipHandler: Handler internal class EntityOwnershipHandler: Handler
{ {
private readonly RagonListenerList _listenerList; private readonly RagonListenerList _listenerList;
private readonly RagonPlayerCache _playerCache; private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache; private readonly RagonEntityCache _entityCache;
public OwnershipHandler( public EntityOwnershipHandler(
RagonListenerList listenerList, RagonListenerList listenerList,
RagonPlayerCache playerCache, RagonPlayerCache playerCache,
RagonEntityCache entityCache) RagonEntityCache entityCache)
@@ -37,17 +37,16 @@ internal class OwnershipHandler: Handler
public void Handle(RagonBuffer buffer) public void Handle(RagonBuffer buffer)
{ {
var newOwnerId = buffer.ReadString(); var newOwnerId = buffer.ReadUShort();
var player = _playerCache.GetPlayerById(newOwnerId);
_playerCache.OnOwnershipChanged(newOwnerId);
_listenerList.OnOwnershipChanged(player);
var entities = buffer.ReadUShort(); var entities = buffer.ReadUShort();
var player = _playerCache.GetPlayerByPeer(newOwnerId);
for (var i = 0; i < entities; i++) for (var i = 0; i < entities; i++)
{ {
var entityId = buffer.ReadUShort(); var entityId = buffer.ReadUShort();
_entityCache.OnOwnershipChanged(player, entityId); _entityCache.OnOwnershipChanged(player, entityId);
RagonLog.Trace("Entity changed owner: " + entityId);
} }
} }
} }
@@ -19,11 +19,11 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
internal class EntityDestroyHandler: Handler internal class EntityRemoveHandler: Handler
{ {
private readonly RagonEntityCache _entityCache; private readonly RagonEntityCache _entityCache;
public EntityDestroyHandler(RagonEntityCache entityCache) public EntityRemoveHandler(RagonEntityCache entityCache)
{ {
_entityCache = entityCache; _entityCache = entityCache;
} }
@@ -43,7 +43,6 @@ internal class JoinSuccessHandler : Handler
private readonly RagonPlayerCache _playerCache; private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache; private readonly RagonEntityCache _entityCache;
private readonly RagonClient _client; private readonly RagonClient _client;
private readonly RagonBuffer _buffer;
public JoinSuccessHandler( public JoinSuccessHandler(
RagonClient client, RagonClient client,
@@ -53,7 +52,6 @@ internal class JoinSuccessHandler : Handler
RagonEntityCache entityCache RagonEntityCache entityCache
) )
{ {
_buffer = buffer;
_client = client; _client = client;
_listenerList = listenerList; _listenerList = listenerList;
_entityCache = entityCache; _entityCache = entityCache;
@@ -67,14 +65,15 @@ internal class JoinSuccessHandler : Handler
var ownerId = buffer.ReadString(); var ownerId = buffer.ReadString();
var min = buffer.ReadUShort(); var min = buffer.ReadUShort();
var max = buffer.ReadUShort(); var max = buffer.ReadUShort();
var map = buffer.ReadString(); var sceneName = buffer.ReadString();
var scene = new RagonScene(_client, _playerCache, _entityCache); var scene = new RagonScene(_client, _playerCache, _entityCache, sceneName);
var roomInfo = new RagonRoomInformation(roomId, localId, ownerId, min, max); var roomInfo = new RagonRoomInformation(roomId, localId, ownerId, min, max);
var room = new RagonRoom(_client, _entityCache, _playerCache, roomInfo, scene); var room = new RagonRoom(_client, _entityCache, _playerCache, roomInfo, scene);
_playerCache.SetOwnerAndLocal(ownerId, localId); _playerCache.SetOwnerAndLocal(ownerId, localId);
_client.AssignRoom(room); _client.AssignRoom(room);
_listenerList.OnLevel(map);
_listenerList.OnSceneRequest(sceneName);
} }
} }
@@ -38,7 +38,8 @@ internal class SceneLoadHandler: Handler
var room = _client.Room; var room = _client.Room;
room.Cleanup(); room.Cleanup();
room.Update(sceneName);
_listenerList.OnLevel(sceneName); _listenerList.OnSceneRequest(sceneName);
} }
} }
@@ -0,0 +1,46 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
internal class OwnershipRoomHandler: Handler
{
private readonly RagonListenerList _listenerList;
private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache;
public OwnershipRoomHandler(
RagonListenerList listenerList,
RagonPlayerCache playerCache,
RagonEntityCache entityCache)
{
_listenerList = listenerList;
_playerCache = playerCache;
_entityCache = entityCache;
}
public void Handle(RagonBuffer buffer)
{
var newOwnerId = buffer.ReadUShort();
var player = _playerCache.GetPlayerByPeer(newOwnerId);
_playerCache.OnOwnershipChanged(newOwnerId);
_listenerList.OnOwnershipChanged(player);
}
}
@@ -53,8 +53,9 @@ internal class PlayerLeftHandler : Handler
toDeleteIds[i] = entityId; toDeleteIds[i] = entityId;
} }
var emptyPayload = new RagonPayload(0);
foreach (var id in toDeleteIds) foreach (var id in toDeleteIds)
_entityCache.OnDestroy(id, new RagonPayload()); _entityCache.OnDestroy(id, emptyPayload);
} }
} }
} }
+46 -17
View File
@@ -15,25 +15,29 @@
*/ */
using System.Diagnostics;
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
internal class SnapshotHandler : Handler internal class SnapshotHandler : Handler
{ {
private RagonClient _client; private readonly IRagonEntityListener _entityListener;
private RagonListenerList _listenerList; private readonly RagonClient _client;
private RagonEntityCache _entityCache; private readonly RagonListenerList _listenerList;
private RagonPlayerCache _playerCache; private readonly RagonEntityCache _entityCache;
private readonly RagonPlayerCache _playerCache;
public SnapshotHandler( public SnapshotHandler(
RagonClient ragonClient, RagonClient ragonClient,
RagonListenerList listenerList, RagonListenerList listenerList,
RagonEntityCache entityCache, RagonEntityCache entityCache,
RagonPlayerCache playerCache RagonPlayerCache playerCache,
IRagonEntityListener entityListener
) )
{ {
_client = ragonClient; _client = ragonClient;
_entityListener = entityListener;
_listenerList = listenerList; _listenerList = listenerList;
_entityCache = entityCache; _entityCache = entityCache;
_playerCache = playerCache; _playerCache = playerCache;
@@ -50,7 +54,10 @@ internal class SnapshotHandler : Handler
var playerName = buffer.ReadString(); var playerName = buffer.ReadString();
_playerCache.AddPlayer(playerPeerId, playerId, playerName); _playerCache.AddPlayer(playerPeerId, playerId, playerName);
RagonLog.Trace($"Player {playerPeerId} - {playerId} - {playerName}");
} }
var dynamicEntities = buffer.ReadUShort(); var dynamicEntities = buffer.ReadUShort();
RagonLog.Trace("Dynamic Entities: " + dynamicEntities); RagonLog.Trace("Dynamic Entities: " + dynamicEntities);
for (var i = 0; i < dynamicEntities; i++) for (var i = 0; i < dynamicEntities; i++)
@@ -59,16 +66,29 @@ internal class SnapshotHandler : Handler
var entityId = buffer.ReadUShort(); var entityId = buffer.ReadUShort();
var ownerPeerId = buffer.ReadUShort(); var ownerPeerId = buffer.ReadUShort();
var payloadSize = buffer.ReadUShort(); var payloadSize = buffer.ReadUShort();
var player = _playerCache.GetPlayerByPeer(ownerPeerId); var player = _playerCache.GetPlayerByPeer(ownerPeerId);
if (player == null)
{
RagonLog.Error($"Player not found with peerId: ${ownerPeerId}");
return;
}
var payload = new RagonPayload(payloadSize); var hasAuthority = _playerCache.Local.Id == player.Id;
payload.Read(buffer); var entity = _entityCache.TryGetEntity(0, entityType, 0, entityId, hasAuthority, out _);
var hasAuthority = _playerCache.LocalPlayer.Id == player.Id; if (payloadSize > 0)
var entity = _entityCache.OnCreate(0, entityType, 0, entityId, hasAuthority); {
var payload = new RagonPayload(payloadSize);
payload.Read(buffer);
entity.AttachPayload(payload);
}
_entityListener.OnEntityCreated(entity);
entity.Read(buffer); entity.Read(buffer);
entity.Attach(_client, entityId, entityType, hasAuthority, player, payload); entity.Attach(_client, entityId, entityType, hasAuthority, player);
} }
var staticEntities = buffer.ReadUShort(); var staticEntities = buffer.ReadUShort();
@@ -80,18 +100,27 @@ internal class SnapshotHandler : Handler
var staticId = buffer.ReadUShort(); var staticId = buffer.ReadUShort();
var ownerPeerId = buffer.ReadUShort(); var ownerPeerId = buffer.ReadUShort();
var payloadSize = buffer.ReadUShort(); var payloadSize = buffer.ReadUShort();
var player = _playerCache.GetPlayerByPeer(ownerPeerId); var player = _playerCache.GetPlayerByPeer(ownerPeerId);
if (player == null)
{
RagonLog.Error($"Player not found with peerId: ${ownerPeerId}");
return;
}
var payload = new RagonPayload(payloadSize); var hasAuthority = _playerCache.Local.Id == player.Id;
payload.Read(buffer); var entity = _entityCache.TryGetEntity(0, entityType, staticId, entityId, hasAuthority, out _);
var hasAuthority = _playerCache.LocalPlayer.Id == player.Id;
var entity = _entityCache.OnCreate(0, entityType, staticId, entityId, hasAuthority);
entity.Read(buffer); entity.Read(buffer);
entity.Attach(_client, entityId, entityType, hasAuthority, player, payload); entity.Attach(_client, entityId, entityType, hasAuthority, player);
} }
_listenerList.OnJoined(); if (_client.Status == RagonStatus.LOBBY)
{
_client.SetStatus(RagonStatus.ROOM);
_listenerList.OnJoined();
}
_listenerList.OnSceneLoaded();
} }
} }
@@ -15,6 +15,8 @@
*/ */
using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
public interface INetworkConnection: IRagonConnection public interface INetworkConnection: IRagonConnection
@@ -23,7 +25,7 @@ public interface INetworkConnection: IRagonConnection
public INetworkChannel Unreliable { get; } public INetworkChannel Unreliable { get; }
public Action<byte[]> OnData { get; set; } public Action<byte[]> OnData { get; set; }
public Action OnConnected { get; set; } public Action OnConnected { get; set; }
public Action<DisconnectReason> OnDisconnected { get; set; } public Action<RagonDisconnect> OnDisconnected { get; set; }
public ulong BytesSent { get; } public ulong BytesSent { get; }
public ulong BytesReceived { get; } public ulong BytesReceived { get; }
public int Ping { get; } public int Ping { get; }
@@ -14,10 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
public interface IRagonConnectedListener public interface IRagonConnectionListener
{ {
void OnConnected(RagonClient client); void OnConnected(RagonClient client);
void OnDisconnected(RagonClient client); void OnDisconnected(RagonClient client, RagonDisconnect reason);
} }
@@ -18,14 +18,15 @@ namespace Ragon.Client
{ {
public interface IRagonListener : public interface IRagonListener :
IRagonAuthorizationListener, IRagonAuthorizationListener,
IRagonConnectedListener, IRagonConnectionListener,
IRagonFailedListener, IRagonFailedListener,
IRagonJoinListener, IRagonJoinListener,
IRagonLeftListener, IRagonLeftListener,
IRagonLevelListener, IRagonSceneListener,
IRagonOwnershipChangedListener, IRagonOwnershipChangedListener,
IRagonPlayerJoinListener, IRagonPlayerJoinListener,
IRagonPlayerLeftListener IRagonPlayerLeftListener
{ {
} }
} }
@@ -16,8 +16,7 @@
namespace Ragon.Client; namespace Ragon.Client;
public enum DisconnectReason public interface IRagonSceneListener
{ {
MANUAL, void OnSceneLoaded(RagonClient client);
TIMEOUT,
} }
@@ -0,0 +1,6 @@
namespace Ragon.Client;
public interface IRagonSceneRequestListener
{
void OnRequestScene(RagonClient client, string sceneName);
}
+91 -54
View File
@@ -21,22 +21,22 @@ namespace Ragon.Client
public sealed class RagonClient public sealed class RagonClient
{ {
private readonly INetworkConnection _connection; private readonly INetworkConnection _connection;
private readonly IRagonEntityListener _entityListener; private readonly NetworkStatistics _stats;
private readonly IRagonSceneCollector _sceneCollector; private IRagonEntityListener _entityListener;
private Handler[] _processors; private IRagonSceneCollector _sceneCollector;
private Handler[] _handlers;
private RagonBuffer _readBuffer; private RagonBuffer _readBuffer;
private RagonBuffer _writeBuffer; private RagonBuffer _writeBuffer;
private RagonRoom _room; private RagonRoom _room;
private RagonSession _session; private RagonSession _session;
private RagonListenerList _listenerList; private RagonListenerList listeners;
private RagonPlayerCache _playerCache; private RagonPlayerCache _playerCache;
private RagonEntityCache _entityCache; private RagonEntityCache _entityCache;
private RagonEventCache _eventCache; private RagonEventCache _eventCache;
private RagonStatus _status; private RagonStatus _status;
private NetworkStatistics _stats;
private float _replicatationRate = 0; private float _replicationRate = 0;
private float _replicatationTime = 0; private float _replicationTime = 0;
public IRagonConnection Connection => _connection; public IRagonConnection Connection => _connection;
public RagonStatus Status => _status; public RagonStatus Status => _status;
@@ -52,68 +52,71 @@ namespace Ragon.Client
#region PUBLIC #region PUBLIC
public RagonClient( public RagonClient(INetworkConnection connection, int rate)
INetworkConnection connection,
IRagonEntityListener entityListener,
IRagonSceneCollector sceneCollector,
int rate)
{ {
_listenerList = new RagonListenerList(this);
_entityListener = entityListener;
_sceneCollector = sceneCollector;
_connection = connection; _connection = connection;
_connection.OnData += OnData; _connection.OnData += OnData;
_connection.OnConnected += OnConnected; _connection.OnConnected += OnConnected;
_connection.OnDisconnected += OnDisconnected; _connection.OnDisconnected += OnDisconnected;
_replicatationRate = (1000.0f / rate) / 1000.0f; listeners = new RagonListenerList(this);
_replicatationTime = 0;
_replicationRate = (1000.0f / rate) / 1000.0f;
_replicationTime = 0;
_eventCache = new RagonEventCache(); _eventCache = new RagonEventCache();
_stats = new NetworkStatistics(); _stats = new NetworkStatistics();
_status = RagonStatus.DISCONNECTED; _status = RagonStatus.DISCONNECTED;
} }
public void AddListener(IRagonListener listener)
public void Configure(IRagonSceneCollector sceneCollector)
{ {
_listenerList.Add(listener); _sceneCollector = sceneCollector;
} }
public void RemoveListener(IRagonListener listener) public void Configure(IRagonEntityListener listener)
{ {
_listenerList.Remove(listener); _entityListener = listener;
} }
public void Connect(string address, ushort port, string protocol) public void Connect(string address, ushort port, string protocol)
{ {
if (_sceneCollector == null)
{
RagonLog.Error("Scene collector is not defined!");
return;
}
if (_entityListener == null)
{
RagonLog.Error("Entity Listener is not defined!");
return;
}
_writeBuffer = new RagonBuffer(); _writeBuffer = new RagonBuffer();
_readBuffer = new RagonBuffer(); _readBuffer = new RagonBuffer();
_session = new RagonSession(this, _readBuffer); _session = new RagonSession(this, _readBuffer);
_playerCache = new RagonPlayerCache(); _playerCache = new RagonPlayerCache();
_entityCache = new RagonEntityCache(this, _playerCache, _entityListener, _sceneCollector); _entityCache = new RagonEntityCache(this, _playerCache, _sceneCollector);
_processors = new Handler[byte.MaxValue]; _handlers = new Handler[byte.MaxValue];
_processors[(byte)RagonOperation.AUTHORIZED_SUCCESS] = new AuthorizeSuccessHandler(_listenerList); _handlers[(byte)RagonOperation.AUTHORIZED_SUCCESS] = new AuthorizeSuccessHandler(this, listeners);
_processors[(byte)RagonOperation.AUTHORIZED_FAILED] = new AuthorizeFailedHandler(_listenerList); _handlers[(byte)RagonOperation.AUTHORIZED_FAILED] = new AuthorizeFailedHandler(listeners);
_processors[(byte)RagonOperation.JOIN_SUCCESS] = _handlers[(byte)RagonOperation.JOIN_SUCCESS] = new JoinSuccessHandler(this, _readBuffer, listeners, _playerCache, _entityCache);
new JoinSuccessHandler(this, _readBuffer, _listenerList, _playerCache, _entityCache); _handlers[(byte)RagonOperation.JOIN_FAILED] = new JoinFailedHandler(listeners);
_processors[(byte)RagonOperation.JOIN_FAILED] = new JoinFailedHandler(_listenerList); _handlers[(byte)RagonOperation.LEAVE_ROOM] = new LeaveRoomHandler(this, listeners, _entityCache);
_processors[(byte)RagonOperation.LEAVE_ROOM] = new LeaveRoomHandler(this, _listenerList, _entityCache); _handlers[(byte)RagonOperation.OWNERSHIP_ROOM_CHANGED] = new OwnershipRoomHandler(listeners, _playerCache, _entityCache);
_processors[(byte)RagonOperation.OWNERSHIP_CHANGED] = _handlers[(byte)RagonOperation.OWNERSHIP_ENTITY_CHANGED] = new EntityOwnershipHandler(listeners, _playerCache, _entityCache);
new OwnershipHandler(_listenerList, _playerCache, _entityCache); _handlers[(byte)RagonOperation.PLAYER_JOINED] = new PlayerJoinHandler(_playerCache, listeners);
_processors[(byte)RagonOperation.PLAYER_JOINED] = new PlayerJoinHandler(_playerCache, _listenerList); _handlers[(byte)RagonOperation.PLAYER_LEAVED] = new PlayerLeftHandler(_entityCache, _playerCache, listeners);
_processors[(byte)RagonOperation.PLAYER_LEAVED] = _handlers[(byte)RagonOperation.LOAD_SCENE] = new SceneLoadHandler(this, listeners);
new PlayerLeftHandler(_entityCache, _playerCache, _listenerList); _handlers[(byte)RagonOperation.CREATE_ENTITY] = new EntityCreateHandler(this, _playerCache, _entityCache, _entityListener);
_processors[(byte)RagonOperation.LOAD_SCENE] = new SceneLoadHandler(this, _listenerList); _handlers[(byte)RagonOperation.REMOVE_ENTITY] = new EntityRemoveHandler(_entityCache);
_processors[(byte)RagonOperation.CREATE_ENTITY] = new EntityCreateHandler(this, _playerCache, _entityCache); _handlers[(byte)RagonOperation.REPLICATE_ENTITY_STATE] = new StateEntityHandler(_entityCache);
_processors[(byte)RagonOperation.DESTROY_ENTITY] = new EntityDestroyHandler(_entityCache); _handlers[(byte)RagonOperation.REPLICATE_ENTITY_EVENT] = new EntityEventHandler(this, _playerCache, _entityCache);
_processors[(byte)RagonOperation.REPLICATE_ENTITY_STATE] = new StateEntityHandler(_entityCache); _handlers[(byte)RagonOperation.SNAPSHOT] = new SnapshotHandler(this, listeners, _entityCache, _playerCache, _entityListener);
_processors[(byte)RagonOperation.REPLICATE_ENTITY_EVENT] =
new EntityEventHandler(this, _playerCache, _entityCache);
_processors[(byte)RagonOperation.SNAPSHOT] =
new SnapshotHandler(this, _listenerList, _entityCache, _playerCache);
var protocolRaw = RagonVersion.Parse(protocol); var protocolRaw = RagonVersion.Parse(protocol);
_connection.Connect(address, port, protocolRaw); _connection.Connect(address, port, protocolRaw);
@@ -124,19 +127,25 @@ namespace Ragon.Client
_status = RagonStatus.DISCONNECTED; _status = RagonStatus.DISCONNECTED;
_room.Cleanup(); _room.Cleanup();
_connection.Disconnect(); _connection.Disconnect();
OnDisconnected(DisconnectReason.MANUAL);
OnDisconnected(RagonDisconnect.MANUAL);
} }
public void Update(float dt) public void Update(float dt)
{ {
_replicatationTime += dt; if (_status != RagonStatus.DISCONNECTED)
if (_replicatationTime >= _replicatationRate)
{ {
_entityCache.WriteState(_readBuffer); _replicationTime += dt;
_replicatationTime = 0; if (_replicationTime >= _replicationRate)
{
_entityCache.WriteState(_readBuffer);
_replicationTime = 0;
}
_stats.Update(_connection.BytesSent, _connection.BytesReceived, _connection.Ping, dt);
} }
_stats.Update(_connection.BytesSent, _connection.BytesReceived, _connection.Ping, dt); listeners.Update();
_connection.Update(); _connection.Update();
} }
@@ -147,6 +156,30 @@ namespace Ragon.Client
_connection.Dispose(); _connection.Dispose();
} }
public void AddListener(IRagonListener listener) => listeners.Add(listener);
public void AddListener(IRagonAuthorizationListener listener) => listeners.Add(listener);
public void AddListener(IRagonConnectionListener listener) => listeners.Add(listener);
public void AddListener(IRagonFailedListener listener) => listeners.Add(listener);
public void AddListener(IRagonJoinListener listener) => listeners.Add(listener);
public void AddListener(IRagonLeftListener listener) => listeners.Add(listener);
public void AddListener(IRagonOwnershipChangedListener listener) => listeners.Add(listener);
public void AddListener(IRagonPlayerJoinListener listener) => listeners.Add(listener);
public void AddListener(IRagonPlayerLeftListener listener) => listeners.Add(listener);
public void AddListener(IRagonSceneListener listener) => listeners.Add(listener);
public void AddListener(IRagonSceneRequestListener listener) => listeners.Add(listener);
public void RemoveListener(IRagonListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonAuthorizationListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonConnectionListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonFailedListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonJoinListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonLeftListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonOwnershipChangedListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonPlayerJoinListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonPlayerLeftListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonSceneListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonSceneRequestListener listener) => listeners.Remove(listener);
#endregion #endregion
#region INTERNAL #region INTERNAL
@@ -154,7 +187,11 @@ namespace Ragon.Client
internal void AssignRoom(RagonRoom room) internal void AssignRoom(RagonRoom room)
{ {
_room = room; _room = room;
_status = RagonStatus.ROOM; }
internal void SetStatus(RagonStatus status)
{
_status = status;
} }
#endregion #endregion
@@ -165,15 +202,15 @@ namespace Ragon.Client
{ {
RagonLog.Trace("Connected"); RagonLog.Trace("Connected");
_listenerList.OnConnected(); listeners.OnConnected();
_status = RagonStatus.CONNECTED; _status = RagonStatus.CONNECTED;
} }
private void OnDisconnected(DisconnectReason reason) private void OnDisconnected(RagonDisconnect reason)
{ {
RagonLog.Trace($"Disconnected: {reason}"); RagonLog.Trace($"Disconnected: {reason}");
_listenerList.OnDisconnected(); listeners.OnDisconnected(reason);
_status = RagonStatus.DISCONNECTED; _status = RagonStatus.DISCONNECTED;
} }
@@ -183,7 +220,7 @@ namespace Ragon.Client
_readBuffer.FromArray(data); _readBuffer.FromArray(data);
var operation = _readBuffer.ReadByte(); var operation = _readBuffer.ReadByte();
_processors[operation].Handle(_readBuffer); _handlers[operation].Handle(_readBuffer);
} }
#endregion #endregion
+62 -37
View File
@@ -26,7 +26,6 @@ public sealed class RagonEntityCache
private readonly Dictionary<uint, RagonEntity> _sceneEntities = new(); private readonly Dictionary<uint, RagonEntity> _sceneEntities = new();
private readonly RagonClient _client; private readonly RagonClient _client;
private readonly IRagonEntityListener _entityListener;
private readonly IRagonSceneCollector _sceneCollector; private readonly IRagonSceneCollector _sceneCollector;
private readonly RagonPlayerCache _playerCache; private readonly RagonPlayerCache _playerCache;
@@ -35,35 +34,33 @@ public sealed class RagonEntityCache
public RagonEntityCache( public RagonEntityCache(
RagonClient client, RagonClient client,
RagonPlayerCache playerCache, RagonPlayerCache playerCache,
IRagonEntityListener listener,
IRagonSceneCollector sceneCollector IRagonSceneCollector sceneCollector
) )
{ {
_client = client; _client = client;
_entityListener = listener;
_sceneCollector = sceneCollector; _sceneCollector = sceneCollector;
_playerCache = playerCache; _playerCache = playerCache;
} }
public RagonEntity FindById(ushort id) public bool TryGetEntity(ushort id, out RagonEntity entity)
{ {
return _entityMap[id]; return _entityMap.TryGetValue(id, out entity);
} }
public void Create(RagonEntity entity, IRagonPayload? spawnPayload) public void Create(RagonEntity entity, RagonPayload spawnPayload)
{ {
var attachId = (ushort) (_playerCache.LocalPlayer.PeerId + _localEntitiesCounter++) ; var attachId = (ushort)(_playerCache.Local.PeerId + _localEntitiesCounter++);
var buffer = _client.Buffer; var buffer = _client.Buffer;
buffer.Clear(); buffer.Clear();
buffer.WriteOperation(RagonOperation.CREATE_ENTITY); buffer.WriteOperation(RagonOperation.CREATE_ENTITY);
buffer.WriteUShort(attachId); buffer.WriteUShort(attachId);
buffer.WriteUShort(entity.Type); buffer.WriteUShort(entity.Type);
buffer.WriteByte((byte) entity.Authority); buffer.WriteByte((byte)entity.Authority);
entity.State.WriteInfo(buffer); entity.State.WriteInfo(buffer);
spawnPayload?.Serialize(buffer); spawnPayload?.Write(buffer);
_pendingEntities.Add(attachId, entity); _pendingEntities.Add(attachId, entity);
@@ -71,20 +68,34 @@ public sealed class RagonEntityCache
_client.Reliable.Send(sendData); _client.Reliable.Send(sendData);
} }
public void Destroy(RagonEntity entity, IRagonPayload? destroyPayload) public void Transfer(RagonEntity entity, RagonPlayer player)
{
var buffer = _client.Buffer;
buffer.Clear();
buffer.WriteOperation(RagonOperation.TRANSFER_ENTITY_OWNERSHIP);
buffer.WriteUShort(entity.Id);
buffer.WriteUShort(player.PeerId);
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void Destroy(RagonEntity entity, RagonPayload destroyPayload)
{ {
if (!entity.IsAttached) if (!entity.IsAttached)
{ {
RagonLog.Warn("Can't destroy object, he is not created"); RagonLog.Warn("Can't destroy object, he is not created");
return; return;
} }
var buffer = _client.Buffer; var buffer = _client.Buffer;
buffer.Clear(); buffer.Clear();
buffer.WriteOperation(RagonOperation.DESTROY_ENTITY); buffer.WriteOperation(RagonOperation.REMOVE_ENTITY);
buffer.WriteUShort(entity.Id); buffer.WriteUShort(entity.Id);
destroyPayload?.Serialize(buffer); destroyPayload?.Write(buffer);
var sendData = buffer.ToArray(); var sendData = buffer.ToArray();
_client.Reliable.Send(sendData); _client.Reliable.Send(sendData);
@@ -124,11 +135,11 @@ public sealed class RagonEntityCache
_sceneEntities.Clear(); _sceneEntities.Clear();
var entities = _sceneCollector.Collect(); var entities = _sceneCollector.Collect();
buffer.WriteUShort((ushort) entities.Length); buffer.WriteUShort((ushort)entities.Length);
foreach (var entity in entities) foreach (var entity in entities)
{ {
buffer.WriteUShort(entity.Type); buffer.WriteUShort(entity.Type);
buffer.WriteByte((byte) entity.Authority); buffer.WriteByte((byte)entity.Authority);
buffer.WriteUShort(entity.SceneId); buffer.WriteUShort(entity.SceneId);
entity.State.WriteInfo(buffer); entity.State.WriteInfo(buffer);
@@ -148,7 +159,7 @@ public sealed class RagonEntityCache
internal void Cleanup() internal void Cleanup()
{ {
var payload = new RagonPayload(); var payload = new RagonPayload(0);
foreach (var ent in _entityList) foreach (var ent in _entityList)
ent.Detach(payload); ent.Detach(payload);
@@ -156,53 +167,58 @@ public sealed class RagonEntityCache
_entityList.Clear(); _entityList.Clear();
} }
internal RagonEntity OnCreate(ushort attachId, ushort entityType, ushort sceneId, ushort entityId, bool hasAuthority) internal RagonEntity TryGetEntity(ushort attachId, ushort entityType, ushort sceneId, ushort entityId, bool hasAuthority, out bool hasCreated)
{ {
if (sceneId > 0) if (sceneId > 0)
{ {
if (_sceneEntities.TryGetValue(sceneId, out var entity)) if (_sceneEntities.TryGetValue(sceneId, out var sceneEntity))
{ {
_entityMap.Add(entityId, entity); _entityMap.Add(entityId, sceneEntity);
if (hasAuthority) if (hasAuthority)
_entityList.Add(entity); _entityList.Add(sceneEntity);
return entity; hasCreated = false;
return sceneEntity;
} }
} }
if (_pendingEntities.Remove(attachId, out var existsEntity)) if (_pendingEntities.TryGetValue(attachId, out var pendingEntity))
{ {
_entityMap.Add(entityId, existsEntity); _pendingEntities.Remove(attachId);
_entityMap.Add(entityId, pendingEntity);
if (hasAuthority) if (hasAuthority)
_entityList.Add(existsEntity); _entityList.Add(pendingEntity);
return existsEntity; hasCreated = false;
return pendingEntity;
} }
else
{
var entity = new RagonEntity(entityType, sceneId);
_entityMap.Add(entityId, entity);
if (hasAuthority) var entity = new RagonEntity(entityType, sceneId);
_entityList.Add(entity);
_entityListener.OnEntityCreated(entity); _entityMap.Add(entityId, entity);
return entity; if (hasAuthority)
} _entityList.Add(entity);
hasCreated = true;
return entity;
} }
internal void OnDestroy(ushort entityId, RagonPayload payload) internal void OnDestroy(ushort entityId, RagonPayload payload)
{ {
if (_entityMap.Remove(entityId, out var ragonEntity)) if (_entityMap.TryGetValue(entityId, out var entity))
{ {
_entityList.Remove(ragonEntity); _entityMap.Remove(entityId);
_entityList.Remove(entity);
ragonEntity.Detach(payload); entity.Detach(payload);
} }
} }
@@ -225,8 +241,17 @@ public sealed class RagonEntityCache
internal void OnOwnershipChanged(RagonPlayer player, ushort entityId) internal void OnOwnershipChanged(RagonPlayer player, ushort entityId)
{ {
if (_entityMap.TryGetValue(entityId, out var entity)) if (_entityMap.TryGetValue(entityId, out var entity))
{
if (player.IsLocal)
_entityList.Add(entity);
else
_entityList.Remove(entity);
entity.OnOwnershipChanged(player); entity.OnOwnershipChanged(player);
}
else else
{
RagonLog.Warn($"Entity {entityId} not found!"); RagonLog.Warn($"Entity {entityId} not found!");
}
} }
} }
+6 -2
View File
@@ -26,8 +26,12 @@ public class RagonEventCache
public ushort GetEventCode<TEvent>(TEvent _) where TEvent : IRagonEvent public ushort GetEventCode<TEvent>(TEvent _) where TEvent : IRagonEvent
{ {
var type = typeof(TEvent); var type = typeof(TEvent);
var evntCode = _eventsRegistryByType[type]; if (!_eventsRegistryByType.TryGetValue(type, out var eventCode))
return evntCode; {
RagonLog.Error($"Event with type {type} not registered");
return 0;
}
return eventCode;
} }
public void Register<T>() where T : IRagonEvent, new() public void Register<T>() where T : IRagonEvent, new()
+167 -21
View File
@@ -14,13 +14,24 @@
* limitations under the License. * limitations under the License.
*/ */
using Ragon.Protocol;
namespace Ragon.Client namespace Ragon.Client
{ {
internal class RagonListenerList internal class RagonListenerList
{ {
public int Count => _listeners.Count; private readonly RagonClient _client;
private List<IRagonListener> _listeners = new(); private readonly List<IRagonAuthorizationListener> _authorizationListeners = new();
private RagonClient _client; private readonly List<IRagonConnectionListener> _connectionListeners = new();
private readonly List<IRagonFailedListener> _failedListeners = new();
private readonly List<IRagonJoinListener> _joinListeners = new();
private readonly List<IRagonLeftListener> _leftListeners = new();
private readonly List<IRagonSceneListener> _sceneListeners = new();
private readonly List<IRagonSceneRequestListener> _sceneRequestListeners = new();
private readonly List<IRagonOwnershipChangedListener> _ownershipChangedListeners = new();
private readonly List<IRagonPlayerJoinListener> _playerJoinListeners = new();
private readonly List<IRagonPlayerLeftListener> _playerLeftListeners = new();
private readonly List<Action> _delayedActions = new();
public RagonListenerList(RagonClient client) public RagonListenerList(RagonClient client)
{ {
@@ -29,78 +40,213 @@ namespace Ragon.Client
public void Add(IRagonListener listener) public void Add(IRagonListener listener)
{ {
_listeners.Add(listener); _authorizationListeners.Add(listener);
_connectionListeners.Add(listener);
_failedListeners.Add(listener);
_joinListeners.Add(listener);
_leftListeners.Add(listener);
_sceneListeners.Add(listener);
_ownershipChangedListeners.Add(listener);
_playerJoinListeners.Add(listener);
_playerLeftListeners.Add(listener);
} }
public void Remove(IRagonListener listener) public void Remove(IRagonListener listener)
{ {
_listeners.Remove(listener); _delayedActions.Add(() =>
{
_authorizationListeners.Remove(listener);
_connectionListeners.Remove(listener);
_failedListeners.Remove(listener);
_joinListeners.Remove(listener);
_leftListeners.Remove(listener);
_sceneListeners.Remove(listener);
_ownershipChangedListeners.Remove(listener);
_playerJoinListeners.Remove(listener);
_playerLeftListeners.Remove(listener);
});
} }
public void OnAuthorizationSuccess(string playerId, string playerName) public void Update()
{ {
foreach (var listener in _listeners) foreach (var action in _delayedActions)
action.Invoke();
_delayedActions.Clear();
}
public void Add(IRagonAuthorizationListener listener)
{
_authorizationListeners.Add(listener);
}
public void Add(IRagonSceneRequestListener listener)
{
_sceneRequestListeners.Add(listener);
}
public void Add(IRagonConnectionListener listener)
{
_connectionListeners.Add(listener);
}
public void Add(IRagonFailedListener listener)
{
_failedListeners.Add(listener);
}
public void Add(IRagonJoinListener listener)
{
_joinListeners.Add(listener);
}
public void Add(IRagonLeftListener listener)
{
_leftListeners.Add(listener);
}
public void Add(IRagonSceneListener listener)
{
_sceneListeners.Add(listener);
}
public void Add(IRagonOwnershipChangedListener listener)
{
_ownershipChangedListeners.Add(listener);
}
public void Add(IRagonPlayerJoinListener listener)
{
_playerJoinListeners.Add(listener);
}
public void Add(IRagonPlayerLeftListener listener)
{
_playerLeftListeners.Add(listener);
}
public void Remove(IRagonSceneRequestListener listener)
{
_delayedActions.Add(() => _sceneRequestListeners.Remove(listener));
}
public void Remove(IRagonAuthorizationListener listener)
{
_delayedActions.Add(() => _authorizationListeners.Remove(listener));
}
public void Remove(IRagonConnectionListener listener)
{
_delayedActions.Add(() => _connectionListeners.Remove(listener));
}
public void Remove(IRagonFailedListener listener)
{
_delayedActions.Add(() => _failedListeners.Remove(listener));
}
public void Remove(IRagonJoinListener listener)
{
_delayedActions.Add(() => _joinListeners.Remove(listener));
}
public void Remove(IRagonLeftListener listener)
{
_delayedActions.Add(() => _leftListeners.Remove(listener));
}
public void Remove(IRagonSceneListener listener)
{
_delayedActions.Add(() => _sceneListeners.Remove(listener));
}
public void Remove(IRagonOwnershipChangedListener listener)
{
_delayedActions.Add(() => _ownershipChangedListeners.Remove(listener));
}
public void Remove(IRagonPlayerJoinListener listener)
{
_delayedActions.Add(() => _playerJoinListeners.Remove(listener));
}
public void Remove(IRagonPlayerLeftListener listener)
{
_delayedActions.Add(() => _playerLeftListeners.Remove(listener));
}
public void OnAuthorizationSuccess(string playerId, string playerName, string payload)
{
foreach (var listener in _authorizationListeners)
listener.OnAuthorizationSuccess(_client, playerId, playerName); listener.OnAuthorizationSuccess(_client, playerId, playerName);
} }
public void OnAuthorizationFailed(string message) public void OnAuthorizationFailed(string message)
{ {
foreach (var listener in _listeners) foreach (var listener in _authorizationListeners)
listener.OnAuthorizationFailed(_client, message); listener.OnAuthorizationFailed(_client, message);
} }
public void OnLeft() public void OnLeft()
{ {
foreach (var listener in _listeners) foreach (var listener in _leftListeners)
listener.OnLeft(_client); listener.OnLeft(_client);
} }
public void OnFailed(string message) public void OnFailed(string message)
{ {
foreach (var listener in _listeners) foreach (var listener in _failedListeners)
listener.OnFailed(_client, message); listener.OnFailed(_client, message);
} }
public void OnOwnershipChanged(RagonPlayer player) public void OnOwnershipChanged(RagonPlayer player)
{ {
foreach (var listener in _listeners) foreach (var listener in _ownershipChangedListeners)
listener.OnOwnershipChanged(_client, player); listener.OnOwnershipChanged(_client, player);
} }
public void OnPlayerLeft(RagonPlayer player) public void OnPlayerLeft(RagonPlayer player)
{ {
foreach (var listener in _listeners) foreach (var listener in _playerLeftListeners)
listener.OnPlayerLeft(_client, player); listener.OnPlayerLeft(_client, player);
} }
public void OnPlayerJoined(RagonPlayer player) public void OnPlayerJoined(RagonPlayer player)
{ {
foreach (var listener in _listeners) foreach (var listener in _playerJoinListeners)
listener.OnPlayerJoined(_client, player); listener.OnPlayerJoined(_client, player);
} }
public void OnLevel(string sceneName) public void OnSceneLoaded()
{ {
foreach (var listener in _listeners) foreach (var listener in _sceneListeners)
listener.OnLevel(_client, sceneName); listener.OnSceneLoaded(_client);
}
public void OnSceneRequest(string sceneName)
{
foreach (var listener in _sceneRequestListeners)
listener.OnRequestScene(_client, sceneName);
} }
public void OnJoined() public void OnJoined()
{ {
foreach (var listener in _listeners) foreach (var listener in _joinListeners)
listener.OnJoined(_client); listener.OnJoined(_client);
} }
public void OnConnected() public void OnConnected()
{ {
foreach (var listener in _listeners) foreach (var listener in _connectionListeners)
listener.OnConnected(_client); listener.OnConnected(_client);
} }
public void OnDisconnected() public void OnDisconnected(RagonDisconnect disconnect)
{ {
foreach (var listener in _listeners) foreach (var listener in _connectionListeners)
listener.OnDisconnected(_client); listener.OnDisconnected(_client, disconnect);
} }
} }
} }
+3 -3
View File
@@ -23,13 +23,13 @@ namespace Ragon.Client
public string Name { get; set; } public string Name { get; set; }
public ushort PeerId { get; set; } public ushort PeerId { get; set; }
public bool IsRoomOwner { get; set; } public bool IsRoomOwner { get; set; }
public bool IsMe { get; set; } public bool IsLocal { get; set; }
public RagonPlayer(ushort peerId, string playerId, string name, bool isRoomOwner, bool isMe) public RagonPlayer(ushort peerId, string playerId, string name, bool isRoomOwner, bool isLocal)
{ {
PeerId = peerId; PeerId = peerId;
IsRoomOwner = isRoomOwner; IsRoomOwner = isRoomOwner;
IsMe = isMe; IsLocal = isLocal;
Name = name; Name = name;
Id = playerId; Id = playerId;
} }
+14 -10
View File
@@ -18,12 +18,13 @@ namespace Ragon.Client;
public sealed class RagonPlayerCache public sealed class RagonPlayerCache
{ {
private List<RagonPlayer> _players = new List<RagonPlayer>(); private readonly List<RagonPlayer> _players = new();
private Dictionary<string, RagonPlayer> _playersById = new(); private readonly Dictionary<string, RagonPlayer> _playersById = new();
private Dictionary<ushort, RagonPlayer> _playersByConnection = new(); private readonly Dictionary<ushort, RagonPlayer> _playersByConnection = new();
public IReadOnlyList<RagonPlayer> Players => _players;
public RagonPlayer Owner { get; private set; } public RagonPlayer Owner { get; private set; }
public RagonPlayer LocalPlayer { get; private set; } public RagonPlayer Local { get; private set; }
public bool IsRoomOwner => _ownerId == _localId; public bool IsRoomOwner => _ownerId == _localId;
public RagonPlayer? GetPlayerById(string playerId) => _playersById[playerId]; public RagonPlayer? GetPlayerById(string playerId) => _playersById[playerId];
@@ -50,8 +51,8 @@ public sealed class RagonPlayerCache
var player = new RagonPlayer(peerId, playerId, playerName, isOwner, isLocal); var player = new RagonPlayer(peerId, playerId, playerName, isOwner, isLocal);
if (player.IsMe) if (player.IsLocal)
LocalPlayer = player; Local = player;
if (player.IsRoomOwner) if (player.IsRoomOwner)
Owner = player; Owner = player;
@@ -63,20 +64,23 @@ public sealed class RagonPlayerCache
public void RemovePlayer(string playerId) public void RemovePlayer(string playerId)
{ {
if (_playersById.Remove(playerId, out var player)) if (_playersById.TryGetValue(playerId, out var player))
{ {
_players.Remove(player); _players.Remove(player);
_playersById.Remove(playerId);
_playersByConnection.Remove(player.PeerId); _playersByConnection.Remove(player.PeerId);
} }
} }
public void OnOwnershipChanged(string playerId) public void OnOwnershipChanged(ushort playerPeerId)
{ {
foreach (var player in _players) foreach (var player in _players)
{ {
if (player.Id == playerId) if (player.PeerId == playerPeerId)
{
Owner = player; Owner = player;
player.IsRoomOwner = player.Id == playerId; Owner.IsRoomOwner = true;
}
} }
} }
+12 -4
View File
@@ -27,8 +27,10 @@ namespace Ragon.Client
public string Id => _information.RoomId; public string Id => _information.RoomId;
public int MinPlayers => _information.Min; public int MinPlayers => _information.Min;
public int MaxPlayers => _information.Max; public int MaxPlayers => _information.Max;
public string Scene => _scene.Name;
public RagonPlayer Local => _playerCache.LocalPlayer; public IReadOnlyList<RagonPlayer> Players => _playerCache.Players;
public RagonPlayer Local => _playerCache.Local;
public RagonPlayer Owner => _playerCache.Owner; public RagonPlayer Owner => _playerCache.Owner;
public RagonRoom(RagonClient client, public RagonRoom(RagonClient client,
@@ -50,13 +52,19 @@ namespace Ragon.Client
_playerCache.Cleanup(); _playerCache.Cleanup();
} }
public void LoadScene(string map) => _scene.Load(map); internal void Update(string sceneName)
{
_scene.Update(sceneName);
}
public void LoadScene(string sceneName) => _scene.Load(sceneName);
public void SceneLoaded() => _scene.SceneLoaded(); public void SceneLoaded() => _scene.SceneLoaded();
public void CreateEntity(RagonEntity entity) => CreateEntity(entity, null); public void CreateEntity(RagonEntity entity) => CreateEntity(entity, null);
public void CreateEntity(RagonEntity entity, IRagonPayload? payload) => _entityCache.Create(entity, payload); public void CreateEntity(RagonEntity entity, RagonPayload payload) => _entityCache.Create(entity, payload);
public void TransferEntity(RagonEntity entity, RagonPlayer player) => _entityCache.Transfer(entity, player);
public void DestroyEntity(RagonEntity entityId) => DestroyEntity(entityId, null); public void DestroyEntity(RagonEntity entityId) => DestroyEntity(entityId, null);
public void DestroyEntity(RagonEntity entityId, IRagonPayload? payload) => _entityCache.Destroy(entityId, payload); public void DestroyEntity(RagonEntity entityId, RagonPayload payload) => _entityCache.Destroy(entityId, payload);
} }
} }
+12 -3
View File
@@ -20,24 +20,33 @@ namespace Ragon.Client;
public class RagonScene public class RagonScene
{ {
public string Name { get; private set; }
private readonly RagonClient _client; private readonly RagonClient _client;
private readonly RagonEntityCache _entityCache; private readonly RagonEntityCache _entityCache;
private readonly RagonPlayerCache _playerCache; private readonly RagonPlayerCache _playerCache;
public RagonScene(RagonClient client, RagonPlayerCache playerCache, RagonEntityCache entityCache) public RagonScene(RagonClient client, RagonPlayerCache playerCache, RagonEntityCache entityCache, string sceneName)
{ {
Name = sceneName;
_client = client; _client = client;
_playerCache = playerCache; _playerCache = playerCache;
_entityCache = entityCache; _entityCache = entityCache;
} }
internal void Load(string map) internal void Update(string scene)
{
Name = scene;
}
internal void Load(string sceneName)
{ {
var buffer = _client.Buffer; var buffer = _client.Buffer;
buffer.Clear(); buffer.Clear();
buffer.WriteOperation(RagonOperation.LOAD_SCENE); buffer.WriteOperation(RagonOperation.LOAD_SCENE);
buffer.WriteString(map); buffer.WriteString(sceneName);
var sendData = buffer.ToArray(); var sendData = buffer.ToArray();
_client.Reliable.Send(sendData); _client.Reliable.Send(sendData);
+8 -8
View File
@@ -29,9 +29,9 @@ namespace Ragon.Client
_buffer = buffer; _buffer = buffer;
} }
public void CreateOrJoin(string map, int minPlayers, int maxPlayers) public void CreateOrJoin(string sceneName, int minPlayers, int maxPlayers)
{ {
var parameters = new RagonRoomParameters() {Map = map, Min = minPlayers, Max = maxPlayers}; var parameters = new RagonRoomParameters() {Scene = sceneName, Min = minPlayers, Max = maxPlayers};
CreateOrJoin(parameters); CreateOrJoin(parameters);
} }
@@ -46,14 +46,14 @@ namespace Ragon.Client
_client.Reliable.Send(sendData); _client.Reliable.Send(sendData);
} }
public void Create(string map, int minPlayers, int maxPlayers) public void Create(string sceneName, int minPlayers, int maxPlayers)
{ {
Create(null, new RagonRoomParameters() {Map = map, Min = minPlayers, Max = maxPlayers}); Create(null, new RagonRoomParameters() {Scene = sceneName, Min = minPlayers, Max = maxPlayers});
} }
public void Create(string roomId, string map, int minPlayers, int maxPlayers) public void Create(string roomId, string sceneNa, int minPlayers, int maxPlayers)
{ {
Create(roomId, new RagonRoomParameters() {Map = map, Min = minPlayers, Max = maxPlayers}); Create(roomId, new RagonRoomParameters() {Scene = sceneNa, Min = minPlayers, Max = maxPlayers});
} }
public void Create(string roomId, RagonRoomParameters parameters) public void Create(string roomId, RagonRoomParameters parameters)
@@ -93,13 +93,13 @@ namespace Ragon.Client
_client.Reliable.Send(sendData); _client.Reliable.Send(sendData);
} }
public void AuthorizeWithKey(string key, string playerName, byte[] additonalData) public void AuthorizeWithKey(string key, string playerName, string payload = "")
{ {
_buffer.Clear(); _buffer.Clear();
_buffer.WriteOperation(RagonOperation.AUTHORIZE); _buffer.WriteOperation(RagonOperation.AUTHORIZE);
_buffer.WriteString(key); _buffer.WriteString(key);
_buffer.WriteString(playerName); _buffer.WriteString(playerName);
_buffer.WriteBytes(additonalData); _buffer.WriteString(payload);
var sendData = _buffer.ToArray(); var sendData = _buffer.ToArray();
_client.Reliable.Send(sendData); _client.Reliable.Send(sendData);
@@ -0,0 +1,27 @@
using Ragon.Client.Compressor;
using Ragon.Protocol;
namespace Ragon.Client.Utils;
public static class CompressorExtension
{
public static float Read(this FloatCompressor compressor, RagonBuffer buffer)
{
return compressor.Decompress(buffer.Read(compressor.RequiredBits));
}
public static void Write(this FloatCompressor compressor, RagonBuffer buffer, float value)
{
buffer.Write(compressor.Compress(value), compressor.RequiredBits);
}
public static float Read(this IntCompressor compressor, RagonBuffer buffer)
{
return compressor.Decompress(buffer.Read(compressor.RequiredBits));
}
public static void Write(this IntCompressor compressor, RagonBuffer buffer, int value)
{
buffer.Write(compressor.Compress(value), compressor.RequiredBits);
}
}
+11 -3
View File
@@ -4,8 +4,16 @@
<ImplicitUsings>disable</ImplicitUsings> <ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<LangVersion>8</LangVersion> <LangVersion>8</LangVersion>
<TargetFramework>netstandard2.1</TargetFramework>
<RootNamespace>Ragon.Common</RootNamespace> <RootNamespace>Ragon.Common</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.2.4-rc</Version>
<Title>Ragon.Protocol</Title>
<Copyright>Eduard Kargin</Copyright>
<PackageProjectUrl>https://ragon-server.com</PackageProjectUrl>
<RepositoryUrl>https://github.com/edmand46/Ragon</RepositoryUrl>
<RepositoryType>Source</RepositoryType>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
@@ -16,8 +24,8 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>C:\Users\edmand46\RagonProjects\ragon-unity-sdk\Assets\Ragon-Unity-SDK\Runtime\Plugins</OutputPath> <OutputPath></OutputPath>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants> <DefineConstants>TRACE;</DefineConstants>
<DebugType>none</DebugType> <DebugType>none</DebugType>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
+17 -4
View File
@@ -58,7 +58,6 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
@@ -292,7 +291,7 @@ namespace Ragon.Protocol
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadSpan(ref Span<uint> data, int size) public void ReadArray(uint[] data, int size)
{ {
var used = _read & 0x0000001F; var used = _read & 0x0000001F;
var index = _read >> 5; var index = _read >> 5;
@@ -321,7 +320,9 @@ namespace Ragon.Protocol
_read += size; _read += size;
} }
public void WriteSpan(ref ReadOnlySpan<uint> data, int size)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteArray(uint[] data, int size)
{ {
var used = _write & 0x0000001F; var used = _write & 0x0000001F;
var index = _write >> 5; var index = _write >> 5;
@@ -332,7 +333,7 @@ namespace Ragon.Protocol
for (var i = 0; i < limit; i += 1) for (var i = 0; i < limit; i += 1)
{ {
var prepared = (ulong) data[i] << used; var prepared = (ulong)data[i] << used;
var mask = (1UL << used) - 1; var mask = (1UL << used) - 1;
var scratch = _buckets[index] & mask; var scratch = _buckets[index] & mask;
var result = scratch | prepared; var result = scratch | prepared;
@@ -352,6 +353,18 @@ namespace Ragon.Protocol
_write = 0; _write = 0;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBuffer(RagonBuffer buffer, int size)
{
WriteArray(buffer._buckets, size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToBuffer(RagonBuffer buffer, int size)
{
ReadArray(buffer._buckets, size);
}
public void FromArray(byte[] data) public void FromArray(byte[] data)
{ {
var length = data.Length; var length = data.Length;
@@ -0,0 +1,9 @@
namespace Ragon.Protocol
{
public enum RagonDisconnect
{
MANUAL,
TIMEOUT,
SERVER,
}
}
+5 -2
View File
@@ -26,7 +26,8 @@ namespace Ragon.Protocol
CREATE_ROOM, CREATE_ROOM,
JOIN_ROOM, JOIN_ROOM,
LEAVE_ROOM, LEAVE_ROOM,
OWNERSHIP_CHANGED, OWNERSHIP_ENTITY_CHANGED,
OWNERSHIP_ROOM_CHANGED,
JOIN_SUCCESS, JOIN_SUCCESS,
JOIN_FAILED, JOIN_FAILED,
LOAD_SCENE, LOAD_SCENE,
@@ -34,9 +35,11 @@ namespace Ragon.Protocol
PLAYER_JOINED, PLAYER_JOINED,
PLAYER_LEAVED, PLAYER_LEAVED,
CREATE_ENTITY, CREATE_ENTITY,
DESTROY_ENTITY, REMOVE_ENTITY,
SNAPSHOT, SNAPSHOT,
REPLICATE_ENTITY_STATE, REPLICATE_ENTITY_STATE,
REPLICATE_ENTITY_EVENT, REPLICATE_ENTITY_EVENT,
TRANSFER_ROOM_OWNERSHIP,
TRANSFER_ENTITY_OWNERSHIP,
} }
} }
@@ -19,20 +19,20 @@ namespace Ragon.Protocol
{ {
public class RagonRoomParameters: IRagonSerializable public class RagonRoomParameters: IRagonSerializable
{ {
public string Map { get; set; } public string Scene { get; set; }
public int Min { get; set; } public int Min { get; set; }
public int Max { get; set; } public int Max { get; set; }
public void Serialize(RagonBuffer buffer) public void Serialize(RagonBuffer buffer)
{ {
buffer.WriteString(Map); buffer.WriteString(Scene);
buffer.WriteInt(Min, 1, 32); buffer.WriteInt(Min, 1, 32);
buffer.WriteInt(Max, 1, 32); buffer.WriteInt(Max, 1, 32);
} }
public void Deserialize(RagonBuffer buffer) public void Deserialize(RagonBuffer buffer)
{ {
Map = buffer.ReadString(); Scene = buffer.ReadString();
Min = buffer.ReadInt(1, 32); Min = buffer.ReadInt(1, 32);
Max = buffer.ReadInt(1, 32); Max = buffer.ReadInt(1, 32);
} }
+1 -1
View File
@@ -21,7 +21,7 @@ namespace Ragon.Protocol
{ {
public static uint Parse(string version) public static uint Parse(string version)
{ {
var strings = version.Split("."); var strings = version.Split('.');
if (strings.Length < 3) if (strings.Length < 3)
return 0; return 0;
+1 -1
View File
@@ -24,7 +24,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ragon.Server.ENet\Ragon.Server.ENet.csproj" /> <ProjectReference Include="..\Ragon.Server.ENet\Ragon.Server.ENet.csproj" />
<ProjectReference Include="..\Ragon.Server.DotNetWebSockets\Ragon.Server.DotNetWebSockets.csproj" /> <ProjectReference Include="..\Ragon.Server.WebSocketServer\Ragon.Server.WebSocketServer.csproj" />
<ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" /> <ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" />
</ItemGroup> </ItemGroup>
@@ -0,0 +1,6 @@
namespace Ragon.Relay;
public class KickPlayerCommand
{
public string Id;
}
@@ -17,8 +17,9 @@
using NLog; using NLog;
using Ragon.Server; using Ragon.Server;
using Ragon.Server.ENet; using Ragon.Server.ENet;
using Ragon.Server.DotNetWebsockets; using Ragon.Server.WebSocketServer;
using Ragon.Server.IO;
using Ragon.Server.Plugin;
namespace Ragon.Relay; namespace Ragon.Relay;
@@ -29,24 +30,22 @@ public class Relay
var logger = LogManager.GetLogger("Ragon.Relay"); var logger = LogManager.GetLogger("Ragon.Relay");
logger.Info("Relay Application"); logger.Info("Relay Application");
var configuration = Configuration.Load("relay.config.json"); var configuration = RagonServerConfiguration.Load("relay.config.json");
var serverType = Configuration.GetServerType(configuration.ServerType); var serverType = RagonServerConfiguration.GetServerType(configuration.ServerType);
INetworkServer server = null; INetworkServer networkServer = new ENetServer();
IServerPlugin plugin = new RelayServerPlugin();
switch (serverType) switch (serverType)
{ {
case ServerType.ENET: case ServerType.ENET:
server = new ENetServer(); networkServer = new ENetServer();
break; break;
case ServerType.WEBSOCKET: case ServerType.WEBSOCKET:
server = new DotNetWebSocketServer(); networkServer = new WebSocketServer();
break;
default:
server = new ENetServer();
break; break;
} }
var relay = new RagonServer(server, configuration); var relay = new RagonServer(networkServer, plugin, configuration);
logger.Info("Started"); logger.Info("Started");
relay.Start(); relay.Start();
} }
+37
View File
@@ -0,0 +1,37 @@
using System;
using Ragon.Server;
using Ragon.Server.Entity;
using Ragon.Server.Plugin;
using Ragon.Server.Room;
namespace Ragon.Relay;
public class RelayRoomPlugin: BaseRoomPlugin
{
public void Tick(float dt)
{
}
public void OnAttached()
{
Console.WriteLine("Room attached");
}
public void OnDetached()
{
Console.WriteLine("Room detached");
}
public bool OnEntityCreate(RagonRoomPlayer creator, RagonEntity entity)
{
Console.WriteLine($"Entity created: {entity.Id}");
return true;
}
public bool OnEntityRemove(RagonRoomPlayer destroyer, RagonEntity entity)
{
Console.WriteLine($"Entity destroyed: {entity.Id}");
return true;
}
}
+24
View File
@@ -0,0 +1,24 @@
using System;
using Newtonsoft.Json;
using Ragon.Server.Plugin;
namespace Ragon.Relay;
public class RelayServerPlugin: BaseServerPlugin
{
public override bool OnCommand(string command, string payload)
{
Console.WriteLine(command);
if (command == "kick-player")
{
var commandPayload = JsonConvert.DeserializeObject<KickPlayerCommand>(payload);
var player = Server.GetPlayerById(commandPayload.Id);
if (player != null)
player.Connection.Close();
else
Console.WriteLine($"Player not found with Id {commandPayload.Id}");
}
return true;
}
}
+12 -2
View File
@@ -3,8 +3,18 @@
"serverType": "enet", "serverType": "enet",
"serverTickRate": 30, "serverTickRate": 30,
"gameProtocol": "1.0.0", "gameProtocol": "1.0.0",
"port": 5001, "port": 5000,
"httpPort": 5001,
"httpKey": "defaultkey",
"limitConnections": 4095, "limitConnections": 4095,
"limitPlayersPerRoom": 20, "limitPlayersPerRoom": 20,
"limitRooms": 200 "limitRooms": 200,
"limitBufferedEvents": 50,
"webHooks":
{
"room-created": "http://127.0.0.1:3000/service/create-room",
"room-removed": "http://127.0.0.1:3000/service/remove-room",
"room-joined": "http://127.0.0.1:3000/service/join-room",
"room-leaved": "http://127.0.0.1:3000/service/leave-room"
}
} }
+2 -2
View File
@@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RootNamespace>Ragon.ENet</RootNamespace> <RootNamespace>Ragon.ENet</RootNamespace>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ENet-CSharp" Version="2.4.8" /> <PackageReference Include="ENet-CSharp" Version="2.4.8" />
<PackageReference Include="NLog" Version="5.1.1" /> <PackageReference Include="NLog" Version="5.2.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -15,6 +15,7 @@
*/ */
using ENet; using ENet;
using Ragon.Server.IO;
namespace Ragon.Server.ENet; namespace Ragon.Server.ENet;
@@ -23,11 +24,19 @@ public sealed class ENetConnection: INetworkConnection
public ushort Id { get; } public ushort Id { get; }
public INetworkChannel Reliable { get; private set; } public INetworkChannel Reliable { get; private set; }
public INetworkChannel Unreliable { get; private set; } public INetworkChannel Unreliable { get; private set; }
private Peer _peer;
public ENetConnection(Peer peer) public ENetConnection(Peer peer)
{ {
_peer = peer;
Id = (ushort) peer.ID; Id = (ushort) peer.ID;
Reliable = new ENetReliableChannel(peer, 0); Reliable = new ENetReliableChannel(peer, 0);
Unreliable = new ENetUnreliableChannel(peer, 1); Unreliable = new ENetUnreliableChannel(peer, 1);
} }
public void Close()
{
_peer.Disconnect(0);
}
} }
@@ -15,6 +15,7 @@
*/ */
using ENet; using ENet;
using Ragon.Server.IO;
namespace Ragon.Server.ENet; namespace Ragon.Server.ENet;
+1
View File
@@ -17,6 +17,7 @@
using ENet; using ENet;
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.IO;
namespace Ragon.Server.ENet namespace Ragon.Server.ENet
{ {
@@ -15,6 +15,7 @@
*/ */
using ENet; using ENet;
using Ragon.Server.IO;
namespace Ragon.Server.ENet; namespace Ragon.Server.ENet;
@@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RootNamespace>Ragon.WebSockets</RootNamespace> <RootNamespace>Ragon.WebSockets</RootNamespace>
<TargetFrameworks>net7.0;net6.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NLog" Version="5.1.1" /> <PackageReference Include="NLog" Version="5.2.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -14,10 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
using System.Net.WebSockets;
using NLog; using NLog;
using System.Net.WebSockets;
using Ragon.Server.IO;
namespace Ragon.Server.DotNetWebsockets; namespace Ragon.Server.WebSocketServer;
public sealed class WebSocketConnection : INetworkConnection public sealed class WebSocketConnection : INetworkConnection
{ {
@@ -43,6 +44,11 @@ public sealed class WebSocketConnection : INetworkConnection
Unreliable = unreliableChannel; Unreliable = unreliableChannel;
} }
public void Close()
{
Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
}
public async Task Flush() public async Task Flush()
{ {
foreach (var channel in _channels) foreach (var channel in _channels)
@@ -15,9 +15,9 @@
*/ */
using System.Net.WebSockets; using System.Net.WebSockets;
using Ragon.Server; using Ragon.Server.IO;
namespace Ragon.Server.DotNetWebsockets; namespace Ragon.Server.WebSocketServer;
public class WebSocketReliableChannel : INetworkChannel public class WebSocketReliableChannel : INetworkChannel
{ {
@@ -18,10 +18,11 @@ using System.Net;
using System.Net.WebSockets; using System.Net.WebSockets;
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.IO;
namespace Ragon.Server.DotNetWebsockets; namespace Ragon.Server.WebSocketServer;
public class DotNetWebSocketServer : INetworkServer public class WebSocketServer : INetworkServer
{ {
public Executor Executor => _executor; public Executor Executor => _executor;
@@ -34,7 +35,7 @@ public class DotNetWebSocketServer : INetworkServer
private List<WebSocketConnection> _activeConnections; private List<WebSocketConnection> _activeConnections;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
public DotNetWebSocketServer() public WebSocketServer()
{ {
_sequencer = new Stack<ushort>(); _sequencer = new Stack<ushort>();
_connections = Array.Empty<WebSocketConnection>(); _connections = Array.Empty<WebSocketConnection>();
+13 -4
View File
@@ -1,19 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RootNamespace>Ragon.Core</RootNamespace> <RootNamespace>Ragon.Core</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.2.4-rc</Version>
<Title>Ragon.Server</Title>
<Copyright>Eduard Kargin</Copyright>
<PackageProjectUrl>https://ragon-server.com</PackageProjectUrl>
<RepositoryUrl>https://github.com/edmand46/Ragon</RepositoryUrl>
<RepositoryType>Source</RepositoryType>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<LangVersion>10</LangVersion>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2-beta2" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.1.1" /> <PackageReference Include="NLog" Version="5.2.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ragon.Protocol\Ragon.Protocol.csproj" /> <ProjectReference Include="..\Ragon.Protocol\Ragon.Protocol.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -0,0 +1,16 @@
using Ragon.Protocol;
using Ragon.Server.Room;
namespace Ragon.Server.Entity;
public interface IRagonEntity
{
public ushort Id { get; }
public ushort Type { get; }
public ushort StaticId { get; }
public ushort AttachId { get; }
public RagonRoomPlayer Owner { get; }
public RagonAuthority Authority { get; }
public RagonPayload Payload { get; }
public IRagonEntityState State { get; }
}
@@ -0,0 +1,8 @@
using Ragon.Protocol;
namespace Ragon.Server.Entity;
public interface IRagonEntityState
{
}
+48 -17
View File
@@ -16,10 +16,11 @@
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Room;
namespace Ragon.Server; namespace Ragon.Server.Entity;
public class RagonEntity public class RagonEntity : IRagonEntity
{ {
private static ushort _idGenerator = 100; private static ushort _idGenerator = 100;
public ushort Id { get; private set; } public ushort Id { get; private set; }
@@ -29,30 +30,36 @@ public class RagonEntity
public RagonRoomPlayer Owner { get; private set; } public RagonRoomPlayer Owner { get; private set; }
public RagonAuthority Authority { get; private set; } public RagonAuthority Authority { get; private set; }
public RagonPayload Payload { get; private set; } public RagonPayload Payload { get; private set; }
public RagonEntityState State { get; private set; } public IRagonEntityState State => _state;
private readonly List<RagonEvent> _bufferedEvents; private readonly List<RagonEvent> _bufferedEvents;
private readonly int _limitBufferedEvents;
private readonly RagonEntityState _state;
public RagonEntity(RagonRoomPlayer owner, ushort type, ushort staticId, ushort attachId, RagonAuthority eventAuthority) public RagonEntity(RagonEntityParameters parameters)
{ {
Owner = owner;
StaticId = staticId;
Type = type;
AttachId = attachId;
Id = _idGenerator++; Id = _idGenerator++;
Authority = eventAuthority;
State = new RagonEntityState(this); StaticId = parameters.StaticId;
Type = parameters.Type;
AttachId = parameters.AttachId;
Authority = parameters.Authority;
Payload = new RagonPayload(); Payload = new RagonPayload();
_state = new RagonEntityState(this);
_bufferedEvents = new List<RagonEvent>(); _bufferedEvents = new List<RagonEvent>();
_limitBufferedEvents = parameters.BufferedEvents;
} }
public void Attach(RagonRoomPlayer owner)
public void SetOwner(RagonRoomPlayer owner)
{ {
Owner = owner; Owner = owner;
} }
public void Detach()
{
}
public void RestoreBufferedEvents(RagonRoomPlayer roomPlayer, RagonBuffer writer) public void RestoreBufferedEvents(RagonRoomPlayer roomPlayer, RagonBuffer writer)
{ {
foreach (var evnt in _bufferedEvents) foreach (var evnt in _bufferedEvents)
@@ -96,7 +103,7 @@ public class RagonEntity
var buffer = room.Writer; var buffer = room.Writer;
buffer.Clear(); buffer.Clear();
buffer.WriteOperation(RagonOperation.DESTROY_ENTITY); buffer.WriteOperation(RagonOperation.REMOVE_ENTITY);
buffer.WriteUShort(Id); buffer.WriteUShort(Id);
Payload.Write(buffer); Payload.Write(buffer);
@@ -110,13 +117,16 @@ public class RagonEntity
{ {
buffer.WriteUShort(Type); buffer.WriteUShort(Type);
buffer.WriteUShort(Id); buffer.WriteUShort(Id);
if (StaticId != 0) if (StaticId != 0)
buffer.WriteUShort(StaticId); buffer.WriteUShort(StaticId);
buffer.WriteUShort(Owner.Connection.Id);
buffer.WriteUShort(Owner.Connection.Id);
buffer.WriteUShort(Payload.Size); buffer.WriteUShort(Payload.Size);
Payload.Write(buffer); Payload.Write(buffer);
State.Snapshot(buffer);
_state.Snapshot(buffer);
} }
public void ReplicateEvent( public void ReplicateEvent(
@@ -152,11 +162,12 @@ public class RagonEntity
if (Authority == RagonAuthority.OwnerOnly && if (Authority == RagonAuthority.OwnerOnly &&
Owner.Connection.Id != caller.Connection.Id) Owner.Connection.Id != caller.Connection.Id)
{ {
Console.WriteLine($"Player have not enough authority for event with Id {evnt.EventCode}");
return; return;
} }
if (eventMode == RagonReplicationMode.Buffered && targetMode != RagonTarget.Owner) if (eventMode == RagonReplicationMode.Buffered &&
targetMode != RagonTarget.Owner &&
_bufferedEvents.Count < _limitBufferedEvents)
{ {
_bufferedEvents.Add(evnt); _bufferedEvents.Add(evnt);
} }
@@ -209,4 +220,24 @@ public class RagonEntity
} }
} }
} }
public void AddProperty(RagonProperty property)
{
_state.AddProperty(property);
}
public void WriteState(RagonBuffer writer)
{
_state.Write(writer);
}
public bool TryReadState(RagonRoomPlayer player, RagonBuffer reader)
{
if (Owner.Connection.Id != player.Connection.Id)
return false;
_state.Read(reader);
return true;
}
} }
@@ -0,0 +1,28 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Server.Entity;
public ref struct RagonEntityParameters
{
public ushort Type;
public ushort StaticId;
public ushort AttachId;
public RagonAuthority Authority;
public int BufferedEvents;
}
@@ -17,17 +17,17 @@
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Server; namespace Ragon.Server.Entity;
public class RagonEntityState public class RagonEntityState: IRagonEntityState
{ {
private List<RagonProperty> _properties; private readonly List<RagonProperty> _properties;
private RagonEntity _entity; private readonly RagonEntity _entity;
public RagonEntityState(RagonEntity entity, int capacity = 10) public RagonEntityState(RagonEntity entity, int capacity = 10)
{ {
_entity = entity; _entity = entity;
_properties = new List<RagonProperty>(10); _properties = new List<RagonProperty>(capacity);
} }
public void AddProperty(RagonProperty property) public void AddProperty(RagonProperty property)
@@ -65,8 +65,7 @@ public class RagonEntityState
{ {
foreach (var property in _properties) foreach (var property in _properties)
{ {
var hasPayload = property.IsFixed || !property.IsFixed && property.Size > 0; if (property.HasData)
if (hasPayload)
{ {
buffer.WriteBool(true); buffer.WriteBool(true);
property.Write(buffer); property.Write(buffer);
+4 -6
View File
@@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Room;
namespace Ragon.Server; namespace Ragon.Server.Entity;
public class RagonEvent public class RagonEvent
{ {
@@ -39,15 +39,13 @@ public class RagonEvent
public void Read(RagonBuffer buffer) public void Read(RagonBuffer buffer)
{ {
var readOnlySpan = _data.AsSpan();
_size = buffer.Capacity; _size = buffer.Capacity;
buffer.ReadSpan(ref readOnlySpan, _size); buffer.ReadArray(_data, _size);
} }
public void Write(RagonBuffer buffer) public void Write(RagonBuffer buffer)
{ {
if (_size == 0) return; if (_size == 0) return;
ReadOnlySpan<uint> readOnlySpan = _data.AsSpan(); buffer.WriteArray(_data, _size);
buffer.WriteSpan(ref readOnlySpan, _size);
} }
} }
+3 -9
View File
@@ -17,7 +17,7 @@
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Server; namespace Ragon.Server.Entity;
public class RagonPayload public class RagonPayload
{ {
@@ -28,19 +28,13 @@ public class RagonPayload
public void Read(RagonBuffer buffer) public void Read(RagonBuffer buffer)
{ {
var readOnlySpan = _data.AsSpan();
_size = buffer.Capacity; _size = buffer.Capacity;
buffer.ReadArray(_data, _size);
buffer.ReadSpan(ref readOnlySpan, _size);
} }
public void Write(RagonBuffer buffer) public void Write(RagonBuffer buffer)
{ {
if (_size == 0) return; if (_size == 0) return;
buffer.WriteArray(_data, _size);
ReadOnlySpan<uint> readOnlySpan = _data.AsSpan();
buffer.WriteSpan(ref readOnlySpan, _size);
} }
} }
+6 -8
View File
@@ -14,17 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
using System.ComponentModel;
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Server; namespace Ragon.Server.Entity;
public class RagonProperty : RagonPayload public class RagonProperty : RagonPayload
{ {
public int Size { get; set; } public int Size { get; set; }
public bool IsDirty { get; private set; } public bool IsDirty { get; private set; }
public bool IsFixed { get; private set; } public bool IsFixed { get; private set; }
public bool HasData { get; private set; }
private uint[] _data; private uint[] _data;
@@ -39,24 +38,23 @@ public class RagonProperty : RagonPayload
public void Read(RagonBuffer buffer) public void Read(RagonBuffer buffer)
{ {
var readOnlySpan = _data.AsSpan();
if (IsFixed) if (IsFixed)
{ {
buffer.ReadSpan(ref readOnlySpan, Size); buffer.ReadArray(_data, Size);
} }
else else
{ {
Size = (int) buffer.Read(); Size = (int) buffer.Read();
buffer.ReadSpan(ref readOnlySpan, Size); buffer.ReadArray(_data, Size);
} }
HasData = true;
IsDirty = true; IsDirty = true;
} }
public void Write(RagonBuffer buffer) public void Write(RagonBuffer buffer)
{ {
ReadOnlySpan<uint> readOnlySpan = _data.AsSpan(); buffer.WriteArray(_data, Size);
buffer.WriteSpan(ref readOnlySpan, Size);
} }
public void Clear() public void Clear()
@@ -16,40 +16,97 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Lobby;
using Ragon.Server.Plugin;
using Ragon.Server.Plugin.Web;
namespace Ragon.Server;
namespace Ragon.Server.Handler;
public sealed class AuthorizationOperation: IRagonOperation public sealed class AuthorizationOperation: IRagonOperation
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private Logger _logger = LogManager.GetCurrentClassLogger();
private readonly RagonWebHookPlugin _ragonWebHook;
private readonly RagonContextObserver _contextObserver;
private readonly RagonBuffer _writer;
public AuthorizationOperation(
RagonWebHookPlugin ragonWebHook,
RagonContextObserver contextObserver,
RagonBuffer writer)
{
_ragonWebHook = ragonWebHook;
_contextObserver = contextObserver;
_writer = writer;
}
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
if (context.LobbyPlayer.Status == LobbyPlayerStatus.Authorized) if (context.ConnectionStatus == ConnectionStatus.Authorized)
{ {
_logger.Warn("Player already authorized"); _logger.Warn("Player already authorized!");
return; return;
} }
var key = reader.ReadString(); if (context.ConnectionStatus == ConnectionStatus.InProcess)
var playerName = reader.ReadString(); {
var additionalPayload = new RagonPayload(); _logger.Warn("Player already request authorization!");
additionalPayload.Read(reader); return;
}
context.LobbyPlayer.Name = playerName; var configuration = context.Configuration;
context.LobbyPlayer.AdditionalData = Array.Empty<byte>(); var key = reader.ReadString();
context.LobbyPlayer.Status = LobbyPlayerStatus.Authorized; var name = reader.ReadString();
var payload = reader.ReadString();
if (key == configuration.ServerKey)
{
if (_ragonWebHook.RequestAuthorization(context, name, payload))
return;
var lobbyPlayer = new RagonLobbyPlayer(context.Connection, Guid.NewGuid().ToString(), name, payload);
context.SetPlayer(lobbyPlayer);
Approve(context);
}
else
{
Reject(context);
}
}
public void Approve(RagonContext context)
{
context.ConnectionStatus = ConnectionStatus.Authorized;
_contextObserver.OnAuthorized(context);
var playerId = context.LobbyPlayer.Id; var playerId = context.LobbyPlayer.Id;
var playerName = context.LobbyPlayer.Name;
var playerPayload = context.LobbyPlayer.Payload;
writer.Clear(); _writer.Clear();
writer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS); _writer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS);
writer.WriteString(playerId); _writer.WriteString(playerId);
writer.WriteString(playerName); _writer.WriteString(playerName);
_writer.WriteString(playerPayload);
var sendData = writer.ToArray(); var sendData = _writer.ToArray();
context.Connection.Reliable.Send(sendData); context.Connection.Reliable.Send(sendData);
_logger.Trace($"Connection {context.Connection.Id} as {playerId}|{context.LobbyPlayer.Name} authorized"); _logger.Trace($"Connection {context.Connection.Id} as {playerId}|{context.LobbyPlayer.Name} authorized");
} }
public void Reject(RagonContext context)
{
_writer.Clear();
_writer.WriteOperation(RagonOperation.AUTHORIZED_FAILED);
var sendData = _writer.ToArray();
context.Connection.Reliable.Send(sendData);
context.Connection.Close();
_logger.Trace($"Connection {context.Connection.Id}");
}
} }
@@ -16,12 +16,13 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Entity;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class EntityCreateOperation : IRagonOperation public sealed class EntityCreateOperation : IRagonOperation
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
@@ -32,18 +33,32 @@ public sealed class EntityCreateOperation : IRagonOperation
var eventAuthority = (RagonAuthority) reader.ReadByte(); var eventAuthority = (RagonAuthority) reader.ReadByte();
var propertiesCount = reader.ReadUShort(); var propertiesCount = reader.ReadUShort();
var entity = new RagonEntity(player, entityType, 0, attachId, eventAuthority); var entityParameters = new RagonEntityParameters()
{
Type = entityType,
Authority = eventAuthority,
AttachId = attachId,
StaticId = 0,
BufferedEvents = context.Configuration.LimitBufferedEvents,
};
var entity = new RagonEntity(entityParameters);
for (var i = 0; i < propertiesCount; i++) for (var i = 0; i < propertiesCount; i++)
{ {
var propertyType = reader.ReadBool(); var propertyType = reader.ReadBool();
var propertySize = reader.ReadUShort(); var propertySize = reader.ReadUShort();
entity.State.AddProperty(new RagonProperty(propertySize, propertyType)); entity.AddProperty(new RagonProperty(propertySize, propertyType));
} }
if (reader.Capacity > 0) if (reader.Capacity > 0)
entity.Payload.Read(reader); entity.Payload.Read(reader);
var plugin = room.Plugin;
if (!plugin.OnEntityCreate(player, entity))
return;
entity.Attach(player);
room.AttachEntity(entity); room.AttachEntity(entity);
player.AttachEntity(entity); player.AttachEntity(entity);
@@ -16,12 +16,13 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Entity;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class EntityEventOperation : IRagonOperation public sealed class EntityEventOperation : IRagonOperation
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
@@ -43,16 +44,15 @@ public sealed class EntityEventOperation : IRagonOperation
if (targetMode == RagonTarget.Player) if (targetMode == RagonTarget.Player)
targetPlayerPeerId = reader.ReadUShort(); targetPlayerPeerId = reader.ReadUShort();
var ragonEvent = new RagonEvent(player, eventId); var @event = new RagonEvent(player, eventId);
ragonEvent.Read(reader); @event.Read(reader);
if (targetMode == RagonTarget.Player && if (targetMode == RagonTarget.Player && room.Players.TryGetValue(targetPlayerPeerId, out var targetPlayer))
context.Room.Players.TryGetValue(targetPlayerPeerId, out var targetPlayer))
{ {
ent.ReplicateEvent(player, ragonEvent, eventMode, targetPlayer); ent.ReplicateEvent(player, @event, eventMode, targetPlayer);
return; return;
} }
ent.ReplicateEvent(player, ragonEvent, eventMode, targetMode); ent.ReplicateEvent(player, @event, eventMode, targetMode);
} }
} }
@@ -0,0 +1,53 @@
using NLog;
using Ragon.Protocol;
namespace Ragon.Server.Handler;
public sealed class EntityOwnershipOperation : IRagonOperation
{
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{
var currentOwner = context.RoomPlayer;
var room = context.Room;
var entityId = reader.ReadUShort();
var playerPeerId = reader.ReadUShort();
if (!room.Entities.TryGetValue(entityId, out var entity))
{
_logger.Error($"Entity not found with id {entityId}");
return;
}
if (entity.Owner.Connection.Id != currentOwner.Connection.Id)
{
_logger.Error($"Player not owner of entity with id {entityId}");
return;
}
if (!room.Players.TryGetValue(playerPeerId, out var nextOwner))
{
_logger.Error($"Player not found with id {entityId}");
return;
}
currentOwner.Entities.Remove(entity);
nextOwner.Entities.Add(entity);
entity.Attach(nextOwner);
_logger.Trace($"Entity {entity.Id} next owner {nextOwner.Connection.Id}");
writer.Clear();
writer.WriteOperation(RagonOperation.OWNERSHIP_ENTITY_CHANGED);
writer.WriteUShort(playerPeerId);
writer.WriteUShort(1);
writer.WriteUShort(entity.Id);
var sendData = writer.ToArray();
foreach (var player in room.PlayerList)
player.Connection.Reliable.Send(sendData);
}
}
@@ -16,12 +16,13 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Entity;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class EntityDestroyOperation: IRagonOperation public sealed class EntityDestroyOperation: IRagonOperation
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
@@ -17,23 +17,23 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class EntityStateOperation: IRagonOperation public sealed class EntityStateOperation: IRagonOperation
{ {
private ILogger _logger = LogManager.GetCurrentClassLogger(); private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
var room = context.Room; var room = context.Room;
var player = context.RoomPlayer;
var entitiesCount = reader.ReadUShort(); var entitiesCount = reader.ReadUShort();
for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++) for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++)
{ {
var entityId = reader.ReadUShort(); var entityId = reader.ReadUShort();
if (room.Entities.TryGetValue(entityId, out var entity)) if (room.Entities.TryGetValue(entityId, out var entity) && entity.TryReadState(player, reader))
{ {
entity.State.Read(reader);
room.Track(entity); room.Track(entity);
} }
else else
@@ -16,7 +16,7 @@
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public interface IRagonOperation public interface IRagonOperation
{ {
@@ -16,17 +16,29 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Lobby;
using Ragon.Server.Plugin;
using Ragon.Server.Plugin.Web;
using Ragon.Server.Room;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class RoomCreateOperation: IRagonOperation public sealed class RoomCreateOperation: IRagonOperation
{ {
private RagonRoomParameters _roomParameters = new(); private readonly RagonRoomParameters _roomParameters = new();
private Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly IServerPlugin _serverPlugin;
private readonly RagonWebHookPlugin _ragonWebHookPlugin;
public RoomCreateOperation(IServerPlugin serverPlugin, RagonWebHookPlugin ragonWebHook)
{
_serverPlugin = serverPlugin;
_ragonWebHookPlugin = ragonWebHook;
}
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized) if (context.ConnectionStatus == ConnectionStatus.Unauthorized)
{ {
_logger.Warn($"Player {context.Connection.Id} not authorized for this request"); _logger.Warn($"Player {context.Connection.Id} not authorized for this request");
return; return;
@@ -56,23 +68,28 @@ public sealed class RoomCreateOperation: IRagonOperation
var information = new RoomInformation() var information = new RoomInformation()
{ {
Map = _roomParameters.Map, Scene = _roomParameters.Scene,
Max = _roomParameters.Max, Max = _roomParameters.Max,
Min = _roomParameters.Min, Min = _roomParameters.Min,
}; };
var lobbyPlayer = context.LobbyPlayer; var lobbyPlayer = context.LobbyPlayer;
var roomPlayer = new RagonRoomPlayer(context.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
var roomPlugin = _serverPlugin.CreateRoomPlugin(information);
var room = new RagonRoom(roomId, information, roomPlugin);
roomPlayer.OnAttached(room);
var room = new RagonRoom(roomId, information);
context.Scheduler.Run(room); context.Scheduler.Run(room);
context.Lobby.Persist(room); context.Lobby.Persist(room);
context.SetRoom(room, roomPlayer);
var player = new RagonRoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name); _ragonWebHookPlugin.RoomCreated(context, room, roomPlayer);
context.SetRoom(room, player);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} {information}"); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} with scene {information.Scene}");
JoinSuccess(player, room, writer); JoinSuccess(roomPlayer, room, writer);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} joined to room {room.Id}"); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} joined to room {room.Id}");
} }
@@ -84,9 +101,9 @@ public sealed class RoomCreateOperation: IRagonOperation
writer.WriteString(room.Id); writer.WriteString(room.Id);
writer.WriteString(player.Id); writer.WriteString(player.Id);
writer.WriteString(room.Owner.Id); writer.WriteString(room.Owner.Id);
writer.WriteUShort((ushort) room.Info.Min); writer.WriteUShort((ushort) room.PlayerMin);
writer.WriteUShort((ushort) room.Info.Max); writer.WriteUShort((ushort) room.PlayerMax);
writer.WriteString(room.Info.Map); writer.WriteString(room.Scene);
var sendData = writer.ToArray(); var sendData = writer.ToArray();
player.Connection.Reliable.Send(sendData); player.Connection.Reliable.Send(sendData);
@@ -16,12 +16,22 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Plugin;
using Ragon.Server.Plugin.Web;
using Ragon.Server.Room;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class RoomJoinOperation : IRagonOperation public sealed class RoomJoinOperation : IRagonOperation
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly RagonWebHookPlugin _webHook;
public RoomJoinOperation(RagonWebHookPlugin plugin)
{
_webHook = plugin;
}
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
@@ -30,42 +40,47 @@ public sealed class RoomJoinOperation : IRagonOperation
if (!context.Lobby.FindRoomById(roomId, out var existsRoom)) if (!context.Lobby.FindRoomById(roomId, out var existsRoom))
{ {
JoinFailed(lobbyPlayer, writer); JoinFailed(context, writer);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} failed to join room {roomId}"); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} failed to join room {roomId}");
return; return;
} }
var player = new RagonRoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name); var player = new RagonRoomPlayer(context.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
context.SetRoom(existsRoom, player); context.SetRoom(existsRoom, player);
JoinSuccess(player, existsRoom, writer); if (!existsRoom.Plugin.OnPlayerJoined(player))
return;
_webHook.RoomJoined(context, existsRoom, player);
JoinSuccess(context, existsRoom, writer);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} joined to {existsRoom.Id}"); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} joined to {existsRoom.Id}");
} }
private void JoinSuccess(RagonRoomPlayer player, RagonRoom room, RagonBuffer writer) private void JoinSuccess(RagonContext context, RagonRoom room, RagonBuffer writer)
{ {
writer.Clear(); writer.Clear();
writer.WriteOperation(RagonOperation.JOIN_SUCCESS); writer.WriteOperation(RagonOperation.JOIN_SUCCESS);
writer.WriteString(room.Id); writer.WriteString(room.Id);
writer.WriteString(player.Id); writer.WriteString(context.RoomPlayer.Id);
writer.WriteString(room.Owner.Id); writer.WriteString(room.Owner.Id);
writer.WriteUShort((ushort) room.Info.Min); writer.WriteUShort((ushort) room.PlayerMin);
writer.WriteUShort((ushort) room.Info.Max); writer.WriteUShort((ushort) room.PlayerMax);
writer.WriteString(room.Info.Map); writer.WriteString(room.Scene);
var sendData = writer.ToArray(); var sendData = writer.ToArray();
player.Connection.Reliable.Send(sendData); context.Connection.Reliable.Send(sendData);
} }
private void JoinFailed(RagonLobbyPlayer player, RagonBuffer writer) private void JoinFailed(RagonContext context, RagonBuffer writer)
{ {
writer.Clear(); writer.Clear();
writer.WriteOperation(RagonOperation.JOIN_FAILED); writer.WriteOperation(RagonOperation.JOIN_FAILED);
writer.WriteString($"Room not exists"); writer.WriteString($"Room not exists");
var sendData = writer.ToArray(); var sendData = writer.ToArray();
player.Connection.Reliable.Send(sendData); context.Connection.Reliable.Send(sendData);
} }
} }
@@ -16,17 +16,29 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Lobby;
using Ragon.Server.Plugin;
using Ragon.Server.Plugin.Web;
using Ragon.Server.Room;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class RoomJoinOrCreateOperation : IRagonOperation public sealed class RoomJoinOrCreateOperation : IRagonOperation
{ {
private RagonRoomParameters _roomParameters = new(); private readonly RagonRoomParameters _roomParameters = new();
private Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly IServerPlugin _serverPlugin;
private readonly RagonWebHookPlugin _ragonWebHookPlugin;
public RoomJoinOrCreateOperation(IServerPlugin serverPlugin, RagonWebHookPlugin plugin)
{
_serverPlugin = serverPlugin;
_ragonWebHookPlugin = plugin;
}
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized) if (context.ConnectionStatus == ConnectionStatus.Unauthorized)
{ {
_logger.Warn("Player not authorized for this request"); _logger.Warn("Player not authorized for this request");
return; return;
@@ -37,30 +49,35 @@ public sealed class RoomJoinOrCreateOperation : IRagonOperation
_roomParameters.Deserialize(reader); _roomParameters.Deserialize(reader);
if (context.Lobby.FindRoomByMap(_roomParameters.Map, out var existsRoom)) if (context.Lobby.FindRoomByScene(_roomParameters.Scene, out var existsRoom))
{ {
var player = new RagonRoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name); var player = new RagonRoomPlayer(context.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
context.SetRoom(existsRoom, player); context.SetRoom(existsRoom, player);
_ragonWebHookPlugin.RoomJoined(context, existsRoom, player);
JoinSuccess(player, existsRoom, writer); JoinSuccess(player, existsRoom, writer);
} }
else else
{ {
var information = new RoomInformation() var information = new RoomInformation()
{ {
Map = _roomParameters.Map, Scene = _roomParameters.Scene,
Max = _roomParameters.Max, Max = _roomParameters.Max,
Min = _roomParameters.Min, Min = _roomParameters.Min,
}; };
var room = new RagonRoom(roomId, information); var roomPlayer = new RagonRoomPlayer(context.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
var roomPlugin = _serverPlugin.CreateRoomPlugin(information);
var room = new RagonRoom(roomId, information, roomPlugin);
_ragonWebHookPlugin.RoomCreated(context, room, roomPlayer);
context.Lobby.Persist(room); context.Lobby.Persist(room);
context.Scheduler.Run(room); context.Scheduler.Run(room);
var roomPlayer = new RagonRoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
context.SetRoom(room, roomPlayer); context.SetRoom(room, roomPlayer);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} {information}"); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} with scene {information.Scene}");
JoinSuccess(roomPlayer, room, writer); JoinSuccess(roomPlayer, room, writer);
} }
@@ -73,9 +90,9 @@ public sealed class RoomJoinOrCreateOperation : IRagonOperation
writer.WriteString(room.Id); writer.WriteString(room.Id);
writer.WriteString(player.Id); writer.WriteString(player.Id);
writer.WriteString(room.Owner.Id); writer.WriteString(room.Owner.Id);
writer.WriteUShort((ushort) room.Info.Min); writer.WriteUShort((ushort) room.PlayerMin);
writer.WriteUShort((ushort) room.Info.Max); writer.WriteUShort((ushort) room.PlayerMax);
writer.WriteString(room.Info.Map); writer.WriteString(room.Scene);
var sendData = writer.ToArray(); var sendData = writer.ToArray();
player.Connection.Reliable.Send(sendData); player.Connection.Reliable.Send(sendData);
@@ -16,19 +16,34 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Plugin;
using Ragon.Server.Plugin.Web;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class RoomLeaveOperation: IRagonOperation public sealed class RoomLeaveOperation: IRagonOperation
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly RagonWebHookPlugin _webHook;
public RoomLeaveOperation(RagonWebHookPlugin plugin)
{
_webHook = plugin;
}
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
var room = context.Room; var room = context.Room;
var roomPlayer = context.RoomPlayer; var roomPlayer = context.RoomPlayer;
if (room != null) if (room != null)
{ {
context.Room?.DetachPlayer(roomPlayer); var plugin = room.Plugin;
plugin.OnPlayerLeaved(roomPlayer);
room.DetachPlayer(roomPlayer);
_webHook.RoomLeaved(context, room, roomPlayer);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} leaved from {room.Id}"); _logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} leaved from {room.Id}");
} }
} }
@@ -0,0 +1,14 @@
using NLog;
using Ragon.Protocol;
using Ragon.Server.Entity;
namespace Ragon.Server.Handler;
public sealed class RoomOwnershipOperation : IRagonOperation
{
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{
}
}
@@ -18,11 +18,11 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public class SceneLoadOperation: IRagonOperation public class SceneLoadOperation: IRagonOperation
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
@@ -33,7 +33,7 @@ public class SceneLoadOperation: IRagonOperation
if (roomOwner.Connection.Id != currentPlayer.Connection.Id) if (roomOwner.Connection.Id != currentPlayer.Connection.Id)
{ {
_logger.Warn("Only owner can change map!"); _logger.Warn("Only owner can change scene!");
return; return;
} }
@@ -16,16 +16,24 @@
using NLog; using NLog;
using Ragon.Protocol; using Ragon.Protocol;
using Ragon.Server.Entity;
using Ragon.Server.Lobby;
using Ragon.Server.Room;
namespace Ragon.Server; namespace Ragon.Server.Handler;
public sealed class SceneLoadedOperation : IRagonOperation public sealed class SceneLoadedOperation : IRagonOperation
{ {
private Logger _logger = LogManager.GetCurrentClassLogger(); private readonly Logger _logger = LogManager.GetCurrentClassLogger();
public SceneLoadedOperation()
{
}
public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer) public void Handle(RagonContext context, RagonBuffer reader, RagonBuffer writer)
{ {
if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized) if (context.ConnectionStatus == ConnectionStatus.Unauthorized)
return; return;
var owner = context.Room.Owner; var owner = context.Room.Owner;
@@ -42,19 +50,32 @@ public sealed class SceneLoadedOperation : IRagonOperation
var staticId = reader.ReadUShort(); var staticId = reader.ReadUShort();
var propertiesCount = reader.ReadUShort(); var propertiesCount = reader.ReadUShort();
var entity = new RagonEntity(player, entityType, staticId, 0, eventAuthority); var entityParameters = new RagonEntityParameters()
{
Type = entityType,
Authority = eventAuthority,
AttachId = 0,
StaticId = staticId,
BufferedEvents = context.Configuration.LimitBufferedEvents,
};
var entity = new RagonEntity(entityParameters);
for (var propertyIndex = 0; propertyIndex < propertiesCount; propertyIndex++) for (var propertyIndex = 0; propertyIndex < propertiesCount; propertyIndex++)
{ {
var propertyType = reader.ReadBool(); var propertyType = reader.ReadBool();
var propertySize = reader.ReadUShort(); var propertySize = reader.ReadUShort();
entity.State.AddProperty(new RagonProperty(propertySize, propertyType)); entity.AddProperty(new RagonProperty(propertySize, propertyType));
} }
var roomPlugin = room.Plugin;
if (!roomPlugin.OnEntityCreate(player, entity)) continue;
var playerInfo = $"Player {context.Connection.Id}|{context.LobbyPlayer.Name}"; var playerInfo = $"Player {context.Connection.Id}|{context.LobbyPlayer.Name}";
var entityInfo = $"{entity.Id}:{entity.Type}"; var entityInfo = $"{entity.Id}:{entity.Type}";
_logger.Trace($"{playerInfo} created entity {entityInfo}"); _logger.Trace($"{playerInfo} created static entity {entityInfo}");
entity.Attach(player);
room.AttachEntity(entity); room.AttachEntity(entity);
player.AttachEntity(entity); player.AttachEntity(entity);
} }
@@ -0,0 +1,111 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using NLog;
using System.Net;
using System.Text.Json;
using Ragon.Server.IO;
using Ragon.Server.Plugin;
namespace Ragon.Server.Http;
public class RagonHttpServer
{
private readonly ILogger _logger = LogManager.GetCurrentClassLogger();
private readonly IExecutor _executor;
private readonly IServerPlugin _serverPlugin;
private HttpListener _httpListener;
private CancellationTokenSource _cancellationTokenSource;
public RagonHttpServer(IExecutor executor, IServerPlugin serverPlugin)
{
_serverPlugin = serverPlugin;
_executor = executor;
}
public async void StartAccept(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var context = await _httpListener.GetContextAsync();
if (context.Request.HttpMethod != "POST")
{
context.Response.StatusCode = 404;
context.Response.ContentLength64 = 0;
context.Response.Close();
}
var request = context.Request;
var reader = new StreamReader(request.InputStream, request.ContentEncoding);
var rawJson = await reader.ReadToEndAsync();
var httpCommand = JsonDocument.Parse(rawJson);
if (httpCommand != null)
{
try
{
var command = httpCommand.RootElement.GetProperty("command");
var payload = httpCommand.RootElement.GetProperty("payload");
if (_serverPlugin.OnCommand(command.GetString() ?? "none", payload.GetRawText()))
{
context.Response.StatusCode = 200;
context.Response.ContentLength64 = 0;
context.Response.Close();
}
else
{
context.Response.StatusCode = 403;
context.Response.ContentLength64 = 0;
context.Response.Close();
}
}
catch (Exception ex)
{
_logger.Error(ex);
context.Response.StatusCode = 505;
context.Response.ContentLength64 = 0;
context.Response.Close();
}
continue;
}
context.Response.StatusCode = 403;
context.Response.ContentLength64 = 0;
context.Response.Close();
}
}
public void Start(RagonServerConfiguration configuration)
{
_cancellationTokenSource = new CancellationTokenSource();
_logger.Info($"Listen at http://0.0.0.0:{configuration.HttpPort}/");
_httpListener = new HttpListener();
_httpListener.Prefixes.Add($"http://127.0.0.1:{configuration.HttpPort}/");
_httpListener.Start();
_executor.Run(() => StartAccept(_cancellationTokenSource.Token), TaskCreationOptions.LongRunning);
}
public void Stop()
{
_cancellationTokenSource.Cancel();
_httpListener.Stop();
}
}
+14 -9
View File
@@ -16,23 +16,28 @@
using System.Threading.Channels; using System.Threading.Channels;
namespace Ragon.Server; namespace Ragon.Server.IO;
public class Executor: TaskScheduler, IExecutor public class Executor : TaskScheduler, IExecutor
{ {
private ChannelReader<Task> _reader; private readonly ChannelReader<Task> _reader;
private ChannelWriter<Task> _writer; private readonly ChannelWriter<Task> _writer;
private Queue<Task> _pendingTasks; private readonly Queue<Task> _pendingTasks;
private TaskFactory _taskFactory; private readonly TaskFactory _taskFactory;
public void Run(Action action) public Task Run(Action action, TaskCreationOptions task = TaskCreationOptions.None)
{ {
_taskFactory.StartNew(action); return _taskFactory.StartNew(action, task);
} }
public Executor() public Executor()
{ {
var channel = Channel.CreateUnbounded<Task>(); var channel = Channel.CreateUnbounded<Task>(new UnboundedChannelOptions()
{
SingleReader = true,
SingleWriter = true,
});
_reader = channel.Reader; _reader = channel.Reader;
_writer = channel.Writer; _writer = channel.Writer;
+2 -2
View File
@@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; namespace Ragon.Server.IO;
public interface IExecutor public interface IExecutor
{ {
public void Run(Action action); public Task Run(Action action, TaskCreationOptions task = TaskCreationOptions.None);
} }
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; namespace Ragon.Server.IO;
public interface INetworkChannel public interface INetworkChannel
{ {
@@ -14,11 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; namespace Ragon.Server.IO;
public interface INetworkConnection public interface INetworkConnection
{ {
public ushort Id { get; } public ushort Id { get; }
public INetworkChannel Reliable { get; } public INetworkChannel Reliable { get; }
public INetworkChannel Unreliable { get; } public INetworkChannel Unreliable { get; }
public void Close();
} }
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; namespace Ragon.Server.IO;
public interface INetworkListener public interface INetworkListener
{ {
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; namespace Ragon.Server.IO;
public interface INetworkServer public interface INetworkServer
{ {
@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; namespace Ragon.Server.IO;
public struct NetworkConfiguration public struct NetworkConfiguration
{ {
@@ -0,0 +1,31 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Server;
public class RagonContextObserver
{
private Dictionary<string, RagonContext> _contexts;
public RagonContextObserver(Dictionary<string, RagonContext> contexts)
{
_contexts = contexts;
}
public void OnAuthorized(RagonContext context)
{
_contexts.Add(context.LobbyPlayer.Id, context);
}
}
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Server.IO;
using Ragon.Server.Lobby;
namespace Ragon.Server;
public interface IRagonServer
{
RagonLobbyPlayer? GetPlayerByConnection(INetworkConnection connection);
RagonLobbyPlayer? GetPlayerById(string id);
}
+4 -3
View File
@@ -15,13 +15,14 @@
*/ */
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Ragon.Server.Room;
namespace Ragon.Server; namespace Ragon.Server.Lobby;
public interface IRagonLobby public interface IRagonLobby
{ {
public bool FindRoomById(string roomId, [MaybeNullWhen(false)] out RagonRoom room); public bool FindRoomById(string roomId, [MaybeNullWhen(false)] out RagonRoom room);
public bool FindRoomByMap(string map, [MaybeNullWhen(false)] out RagonRoom room); public bool FindRoomByScene(string sceneName, [MaybeNullWhen(false)] out RagonRoom room);
public void Persist(RagonRoom room); public void Persist(RagonRoom room);
public void RemoveIfEmpty(RagonRoom room); public bool RemoveIfEmpty(RagonRoom room);
} }
@@ -16,8 +16,9 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using NLog; using NLog;
using Ragon.Server.Room;
namespace Ragon.Server; namespace Ragon.Server.Lobby;
public class LobbyInMemory : IRagonLobby public class LobbyInMemory : IRagonLobby
{ {
@@ -28,8 +29,7 @@ public class LobbyInMemory : IRagonLobby
{ {
foreach (var existRagonRoom in _rooms) foreach (var existRagonRoom in _rooms)
{ {
var info = existRagonRoom.Info; if (existRagonRoom.Id == RagonRoomId && existRagonRoom.PlayerMin < existRagonRoom.PlayerMax)
if (existRagonRoom.Id == RagonRoomId && info.Min < info.Max)
{ {
room = existRagonRoom; room = existRagonRoom;
return true; return true;
@@ -40,12 +40,11 @@ public class LobbyInMemory : IRagonLobby
return false; return false;
} }
public bool FindRoomByMap(string map, [MaybeNullWhen(false)] out RagonRoom room) public bool FindRoomByScene(string sceneName, [MaybeNullWhen(false)] out RagonRoom room)
{ {
foreach (var existsRoom in _rooms) foreach (var existsRoom in _rooms)
{ {
var info = existsRoom.Info; if (existsRoom.Scene == sceneName && existsRoom.PlayerCount < existsRoom.PlayerMax)
if (info.Map == map && existsRoom.Players.Count < info.Max)
{ {
room = existsRoom; room = existsRoom;
return true; return true;
@@ -62,18 +61,23 @@ public class LobbyInMemory : IRagonLobby
_logger.Trace($"New room: {room.Id}"); _logger.Trace($"New room: {room.Id}");
foreach (var r in _rooms) foreach (var r in _rooms)
_logger.Trace($"Room: {r.Id} {r.Info} Players: {r.Players.Count} Entities: {r.Entities.Count}"); _logger.Trace($"Room: {r.Id} Scene: {r.Scene} Players: {r.Players.Count} Entities: {r.Entities.Count}");
} }
public void RemoveIfEmpty(RagonRoom room) public bool RemoveIfEmpty(RagonRoom room)
{ {
var result = false;
if (room.Players.Count == 0) if (room.Players.Count == 0)
{ {
_rooms.Remove(room); _rooms.Remove(room);
_logger.Trace($"Room {room.Id} removed"); _logger.Trace($"Room {room.Id} removed");
result = true;
} }
foreach (var r in _rooms) foreach (var r in _rooms)
_logger.Trace($"Room: {r.Id} {r.Info} Players: {r.Players.Count} Entities: {r.Entities.Count}"); _logger.Trace($"Room: {r.Id} Scene: {r.Scene} Players: {r.Players.Count} Entities: {r.Entities.Count}");
return result;
} }
} }
+12 -11
View File
@@ -14,28 +14,29 @@
* limitations under the License. * limitations under the License.
*/ */
namespace Ragon.Server; using Ragon.Server.IO;
public enum LobbyPlayerStatus namespace Ragon.Server.Lobby;
public enum ConnectionStatus
{ {
Unauthorized, Unauthorized,
InProcess,
Authorized, Authorized,
} }
public class RagonLobbyPlayer public class RagonLobbyPlayer
{ {
public INetworkConnection Connection { get; }
public string Id { get; private set; } public string Id { get; private set; }
public string Name { get; set; } public string Name { get; private set; }
public byte[] AdditionalData { get; set; } public string Payload { get; private set; }
public LobbyPlayerStatus Status { get; set; }
public INetworkConnection Connection { get; private set; }
public RagonLobbyPlayer(INetworkConnection connection) public RagonLobbyPlayer(INetworkConnection connection, string id, string name, string payload)
{ {
Id = Guid.NewGuid().ToString(); Id = id;
Name = name;
Payload = payload;
Connection = connection; Connection = connection;
Status = LobbyPlayerStatus.Unauthorized;
Name = "None";
AdditionalData = Array.Empty<byte>();
} }
} }
@@ -0,0 +1,67 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Server.Entity;
using Ragon.Server.IO;
using Ragon.Server.Room;
namespace Ragon.Server.Plugin;
public class BaseRoomPlugin: IRoomPlugin
{
public IRagonRoom Room { get; private set; }
public virtual void OnAttached(IRagonRoom room)
{
Room = room;
}
public virtual void OnDetached()
{
}
#region VIRTUAL
public virtual bool OnPlayerJoined(RagonRoomPlayer player)
{
return true;
}
public virtual bool OnPlayerLeaved(RagonRoomPlayer player)
{
return true;
}
public virtual void Tick(float dt)
{
}
public virtual bool OnEntityCreate(RagonRoomPlayer creator, IRagonEntity entity)
{
return true;
}
public virtual bool OnEntityRemove(RagonRoomPlayer remover, IRagonEntity entity)
{
return true;
}
#endregion
}
@@ -0,0 +1,56 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Server.IO;
using Ragon.Server.Lobby;
using Ragon.Server.Room;
namespace Ragon.Server.Plugin;
public class BaseServerPlugin: IServerPlugin
{
public IRagonServer Server { get; protected set; }
public virtual void OnAttached(IRagonServer server)
{
Server = server;
}
public virtual void OnDetached()
{
}
public virtual bool OnRoomCreate(RagonLobbyPlayer player, RagonRoom room)
{
return true;
}
public virtual bool OnRoomRemove(RagonLobbyPlayer player, RagonRoom room)
{
return true;
}
public virtual bool OnCommand(string command, string payload)
{
return true;
}
public IRoomPlugin CreateRoomPlugin(RoomInformation information)
{
return new BaseRoomPlugin();
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Server.Entity;
using Ragon.Server.Room;
namespace Ragon.Server.Plugin;
public interface IRoomPlugin
{
void Tick(float dt);
void OnAttached(IRagonRoom room);
void OnDetached();
bool OnPlayerJoined(RagonRoomPlayer player);
bool OnPlayerLeaved(RagonRoomPlayer player);
bool OnEntityCreate(RagonRoomPlayer player, IRagonEntity entity);
bool OnEntityRemove(RagonRoomPlayer player, IRagonEntity entity);
}
@@ -0,0 +1,31 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Server.Http;
using Ragon.Server.Lobby;
using Ragon.Server.Room;
namespace Ragon.Server.Plugin;
public interface IServerPlugin
{
void OnAttached(IRagonServer server);
void OnDetached();
bool OnRoomCreate(RagonLobbyPlayer player, RagonRoom room);
bool OnRoomRemove(RagonLobbyPlayer player, RagonRoom room);
bool OnCommand(string command, string payload);
IRoomPlugin CreateRoomPlugin(RoomInformation information);
}
@@ -0,0 +1,32 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Server.Room;
namespace Ragon.Server.Plugin.Web;
[Serializable]
public class PlayerDto
{
public string Id { get; set;}
public string Name { get; set; }
public PlayerDto(RagonRoomPlayer ragonRoomPlayer)
{
Id = ragonRoomPlayer.Id;
Name = ragonRoomPlayer.Name;
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Server.Room;
namespace Ragon.Server.Plugin.Web;
[Serializable]
public class RoomDto
{
public string Id { get; set;}
public int PlayerMin { get; set; }
public int PlayerMax { get; set; }
public int PlayerCount { get; set; }
public PlayerDto[] Players { get; set; }
public RoomDto(RagonRoom room)
{
Id = room.Id;
PlayerMin = room.PlayerMin;
PlayerMax = room.PlayerMax;
PlayerCount = room.PlayerCount;
Players = room.PlayerList.Select(p => new PlayerDto(p)).ToArray();
}
}
@@ -0,0 +1,135 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Net;
using System.Net.Http.Json;
using Newtonsoft.Json;
using Ragon.Protocol;
using Ragon.Server.Handler;
using Ragon.Server.Lobby;
using Ragon.Server.Room;
namespace Ragon.Server.Plugin.Web;
public class RagonWebHookPlugin
{
private Dictionary<string, string> _webHooks;
private RagonServer _server;
private HttpClient _httpClient;
public RagonWebHookPlugin(RagonServer server, RagonServerConfiguration configuration)
{
_webHooks = new Dictionary<string, string>(configuration.WebHooks);
_httpClient = new HttpClient();
_server = server;
}
public bool RequestAuthorization(RagonContext context, string name, string password)
{
if (_webHooks.TryGetValue("authorization-request", out var value))
{
var httpContent = new StringContent("");
var executor = context.Executor;
executor.Run(async () =>
{
var authorizationOperation = (AuthorizationOperation) _server.ResolveOperation(RagonOperation.AUTHORIZE);
var response = await _httpClient.PostAsync(new Uri(value), httpContent);
if (response.StatusCode != HttpStatusCode.OK)
{
authorizationOperation.Reject(context);
return;
}
var content = await response.Content.ReadAsStringAsync();
var authorizationResponse = JsonConvert.DeserializeObject<AuthorizationResponse>(content);
if (authorizationResponse != null)
{
var lobbyPlayer = new RagonLobbyPlayer(context.Connection, authorizationResponse.Id, authorizationResponse.Name, authorizationResponse.Payload);
context.SetPlayer(lobbyPlayer);
authorizationOperation.Approve(context);
}
else
{
authorizationOperation.Reject(context);
}
});
return true;
}
return false;
}
public void RoomCreated(RagonContext context, RagonRoom room, RagonRoomPlayer player)
{
if (_webHooks.TryGetValue("room-created", out var value) && !string.IsNullOrEmpty(value))
{
var request = new RoomCreatedRequest()
{
Room = new RoomDto(room),
Player = new PlayerDto(player)
};
var content = JsonContent.Create(request);
var executor = context.Executor;
executor.Run(() => _httpClient.PostAsync(new Uri(value), content, CancellationToken.None));
}
}
public void RoomRemoved(RagonContext context, RagonRoom ragonRoom)
{
if (_webHooks.TryGetValue("room-removed", out var value) && !string.IsNullOrEmpty(value))
{
var request = new RoomRemovedRequest()
{
Room = new RoomDto(ragonRoom)
};
var content = JsonContent.Create(request);
var executor = context.Executor;
executor.Run(() => _httpClient.PostAsync(new Uri(value), content, CancellationToken.None));
}
}
public void RoomJoined(RagonContext context, RagonRoom existsRoom, RagonRoomPlayer player)
{
if (_webHooks.TryGetValue("room-joined", out var value) && !string.IsNullOrEmpty(value))
{
var request = new RoomJoinedRequest()
{
Room = new RoomDto(existsRoom),
Player = new PlayerDto(player)
};
var content = JsonContent.Create(request);
var executor = context.Executor;
executor.Run(() => _httpClient.PostAsync(new Uri(value), content, CancellationToken.None));
}
}
public void RoomLeaved(RagonContext context, RagonRoom room, RagonRoomPlayer roomPlayer)
{
if (_webHooks.TryGetValue("room-leaved", out var value) && !string.IsNullOrEmpty(value))
{
var request = new RoomLeavedRequest()
{
Room = new RoomDto(room),
Player = new PlayerDto(roomPlayer)
};
var content = JsonContent.Create(request);
var executor = context.Executor;
executor.Run(() => _httpClient.PostAsync(new Uri(value), content, CancellationToken.None));
}
}
}
@@ -14,4 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
global using Xunit; namespace Ragon.Server.Plugin.Web;
[Serializable]
public class AuthorizationRequest
{
public string Name;
public string Token;
}

Some files were not shown because too many files have changed in this diff Show More