Compare commits

..

106 Commits

Author SHA1 Message Date
edmand46 2f919e4fd8 wip 2025-01-18 17:30:03 +03:00
edmand46 edf90b39c4 wip 2024-11-03 11:36:58 +03:00
edmand46 672bb1ff6d wip 2024-09-28 20:11:56 +03:00
edmand46 5136f08dab feat: config for size of property
fix: websocket server now can receive large messages
fix: buffer resize on read array
2024-08-17 10:29:27 +03:00
edmand46 f7719e1bca chore: update server version 2024-08-15 23:27:53 +03:00
edmand46 211b24fe2b feat: added server address in relay.config.json 2024-08-15 23:20:23 +03:00
edmand46 bdf7d4f94a fix: connection key is invalid 2024-07-21 09:46:01 +03:00
edmand46 3bec19c2b2 chore: updated copyright 2024-05-19 12:26:42 +03:00
edmand46 0f2d316523 chore: updated projects properties for nuget 2024-05-19 12:25:56 +03:00
edmand46 7cf1353869 feat: improved server plugin api 2024-05-19 11:28:36 +03:00
edmand46 6199af3d1e fix: added checking max players per room from config 2024-05-19 08:59:36 +03:00
edmand46 e9dc45265a refactoring: removed external dependencies from server, removed webhooks from server 2024-05-19 08:56:21 +03:00
edmand46 b84538b238 fix: stop tick room on delete 2024-05-19 08:00:56 +03:00
edmand46 7a2196ff50 feat: improved api for plugins 2024-05-18 17:24:57 +03:00
edmand46 5634a182e6 feat: plugin api for replication of data 2024-05-16 21:28:47 +03:00
edmand46 0ede864f40 refactor: updated order of plugins callbacks 2024-05-12 11:37:44 +03:00
edmand46 646744c9a1 feat: player/room user data now available on joined event 2024-05-12 10:57:46 +03:00
edmand46 a9e6a3e853 feat: Room Properties and Player Properties 2024-05-09 21:26:32 +03:00
edmand46 c74ce0f00f Merge pull request #16 from edmand46/feat/room_property
Feat Room/Player Properties
2024-05-09 10:55:54 +03:00
edmand46 d3ae5a4465 feat: player propeties 2024-05-09 10:50:59 +03:00
edmand46 5bf1881f81 feat: room properties ready, player properties wip 2024-05-07 22:42:45 +03:00
edmand46 6886808132 feat(wip): room properties 2024-05-05 15:45:28 +03:00
edmand46 b4cba20d82 feat(wip): player properties 2024-04-29 09:12:42 +03:00
edmand46 b39bd8bd0a fix: updated workflows 2024-04-14 19:23:08 +03:00
edmand46 12b80c546c fix: updated workflows 2024-04-14 19:14:57 +03:00
edmand46 accd442388 feat(wip) room properties 2024-04-14 19:07:48 +03:00
edmand46 d115c98b79 feat(wip): Room Property and Player Property 2024-04-14 08:49:30 +03:00
edmand46 5c20fbafc1 Merge pull request #15 from edmand46/feat/rooms
feat: Room List
2024-04-13 18:27:28 +03:00
edmand46 bd0c6df5ea fix: timer, providing api for listeners 2024-04-13 18:25:39 +03:00
edmand46 c4811ef052 feat(wip): list rooms 2024-04-13 17:43:23 +03:00
edmand46 d82964526c feat(wip): room list support 2024-04-13 16:17:31 +03:00
edmand46 acedaef270 ♻️ unified event api for room and entities 2024-04-07 17:15:52 +03:00
edmand46 0c4bdb83c9 added new method for RagonBuffer 2024-04-07 17:11:45 +03:00
edmand46 5a79502848 RagonStream 2024-04-06 19:11:16 +03:00
edmand46 b8fe2bb13f 🐛 wrong condition in lobby in memory on finding room by id 2024-02-04 20:06:07 +03:00
edmand46 89d130a193 🐛 fixed double call on invoke local flag in ragon property 2024-01-21 12:20:47 +03:00
edmand46 0cd912a1aa 🎨 added null checks 2024-01-13 15:15:29 +03:00
edmand46 c64cc61c78 🐛 prediction spawning for wrong connection, payload capacity 2024-01-04 23:29:35 +03:00
edmand46 2dcb047014 chore: added logging 2023-11-05 22:19:55 +03:00
edmand46 33f8bba2ed fixed: remove exception on non exists player 2023-11-05 22:08:41 +03:00
edmand46 5aa159ed2f fixed: custom property wrong size 2023-11-05 21:53:57 +03:00
edmand46 892558ab16 chore: update version 2023-10-22 21:10:31 +03:00
edmand46 f55634877a 📝 update readme.md 2023-10-21 23:10:41 +03:00
edmand46 8b3a29a750 📝 update readme.md 2023-10-21 23:09:49 +03:00
edmand46 6a50bffe1e 📝 update readme.md 2023-10-21 23:03:55 +03:00
edmand46 9144ad58ef 📝 update readme.md 2023-10-21 23:03:37 +03:00
edmand46 ee9f3fbe3a 🐛 crash on abnormal disconnecting from websocket server 2023-10-19 20:20:40 +03:00
edmand46 893c73512a feat: allow control replication via code, fix null owner on OnEntityCreated 2023-10-16 23:37:35 +03:00
edmand46 e7dd693147 🐛 Event Payload 2023-10-14 23:50:25 +03:00
edmand46 fef1007c8c 🔖 update version 2023-10-14 21:48:33 +03:00
edmand46 e33c442a18 🐛 WebSocket buffer size 2023-10-14 21:35:51 +03:00
edmand46 09b185a3ad 🐛 checking size event 2023-10-13 12:10:46 +03:00
edmand46 7d154ea4d4 🎨 cleanup 2023-10-13 11:02:56 +03:00
edmand46 d2577e5d1f remove allocation on raw data replication 2023-10-13 10:58:15 +03:00
edmand46 e25f42f9ff Merge pull request #14 from edmand46/v1.3.0
Ragon v1.3.0
2023-10-12 17:41:30 +03:00
edmand46 28cc41c3ad 🎨 update project 2023-10-12 17:40:57 +03:00
edmand46 fc483c0854 🚑 crash on timeout disconnect 2023-10-12 16:09:24 +03:00
edmand46 bcc45f7db8 🐛 lost piece of data 2023-10-12 15:38:34 +03:00
edmand46 689a240e5b 🐛 lost piece of data 2023-10-12 15:35:40 +03:00
edmand46 85b75766a9 🎨 remove $ from logs 2023-10-12 11:21:21 +03:00
edmand46 b90ed974e5 🐛 checking authority on destroy object 2023-10-12 11:18:04 +03:00
edmand46 3da57e086e 🐛 channels more then capacity 2023-10-11 21:21:58 +03:00
edmand46 745d196a8b wip 2023-10-11 20:49:00 +03:00
edmand46 860051777e wip 2023-10-11 19:38:26 +03:00
edmand46 6422db783a wip 2023-10-11 19:37:50 +03:00
edmand46 5d812d7acc 🚧 clean up listeners 2023-10-09 09:17:43 +03:00
edmand46 c214b6ca7f wip 2023-10-08 21:13:31 +03:00
edmand46 64842886d7 multiple subscribers, and unsubscribe 2023-10-07 20:20:02 +03:00
edmand46 c892c2b67a 🐛 operation code for room 2023-10-07 19:33:55 +03:00
edmand46 e1a3ea45e2 🚧 pass-through raw data, refactoring 2023-10-07 19:30:52 +03:00
edmand46 8788cb0fcf wip 2023-10-04 14:42:59 +03:00
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
184 changed files with 5056 additions and 4456 deletions
+25
View File
@@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
- name: Setup dotnet - name: Setup dotnet
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Build - name: Build
shell: bash shell: bash
run: | run: |
+3
View File
@@ -1,6 +1,9 @@
.DS_Store .DS_Store
.idea .idea
.vs .vs
.vscode
obj obj
bin bin
*.user *.user
*.dylib
*.dll
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+2 -2
View File
@@ -1,12 +1,12 @@
<p align="center"> <p align="center">
<img src="Images/ragon-logo.png" width="200" > <img src="Images/logo.png">
</p> </p>
## Ragon Server ## Ragon Server
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
@@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ragon.Client\Ragon.Client.csproj" />
</ItemGroup>
</Project>
@@ -1,60 +0,0 @@
/*
* 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.Simulation
{
[Serializable]
public class RagonBool : RagonProperty
{
public bool Value
{
get => _value;
set
{
_value = value;
MarkAsChanged();
}
}
private bool _value;
public RagonBool(
bool initialValue,
bool invokeLocal = true,
int priority = 0
) : base(priority, invokeLocal)
{
_value = initialValue;
SetFixedSize(1);
}
public override void Serialize(RagonBuffer buffer)
{
buffer.WriteBool(_value);
}
public override void Deserialize(RagonBuffer buffer)
{
_value = buffer.ReadBool();
InvokeChanged();
}
}
}
-104
View File
@@ -1,104 +0,0 @@
/*
* 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
{
[Serializable]
public class RagonFloat : RagonProperty
{
public float Value
{
get => _value;
set
{
_value = value;
if (_value < _min)
_value = _min;
else if (_value > _max)
_value = _max;
MarkAsChanged();
}
}
private float _value;
private float _min;
private float _max;
private int _requiredBits;
private float _precision;
private uint _mask;
public RagonFloat(
float initialValue,
bool invokeLocal = true,
int priority = 0
) : base(priority, invokeLocal)
{
_value = initialValue;
_min = -1024.0f;
_max = 1024.0f;
_precision = 0.01f;
_requiredBits = DeBruijn.Log2((uint)((_max - _min) * (1.0f / _precision) + 0.5f)) + 1;
_mask = (uint)((1L << _requiredBits) - 1);
SetFixedSize(_requiredBits);
}
public RagonFloat(
float initialValue,
float min = -1024.0f,
float max = 1024.0f,
float precision = 0.01f,
bool invokeLocal = true,
int priority = 0
) : base(priority, invokeLocal)
{
_value = initialValue;
_min = min;
_max = max;
_precision = precision;
_requiredBits = DeBruijn.Log2((uint)((_max - _min) * (1.0f / _precision) + 0.5f)) + 1;
_mask = (uint)((1L << _requiredBits) - 1);
SetFixedSize(_requiredBits);
}
public override void Serialize(RagonBuffer buffer)
{
var compressedValue = (uint)((_value - _min) * (1f / _precision) + 0.5f) & _mask;
buffer.Write(compressedValue, _requiredBits);
}
public override void Deserialize(RagonBuffer buffer)
{
var compressedValue = buffer.Read(_requiredBits);
_value = compressedValue * _precision + _min;
if (_value < _min)
_value = _min;
else if (_value > _max)
_value = _max;
InvokeChanged();
}
}
}
-87
View File
@@ -1,87 +0,0 @@
/*
* 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.Simulation
{
[Serializable]
public class RagonInt : RagonProperty
{
public int Value
{
get => _value;
set
{
_value = value;
MarkAsChanged();
}
}
private int _min;
private int _max;
private int _requiredBits;
private int _value;
public RagonInt(
int initialValue,
bool invokeLocal = true,
int priority = 0
) : base(priority, invokeLocal)
{
_value = initialValue;
_min = -1000;
_max = 1000;
_max = Math.Max(Math.Abs(_min), Math.Abs(_max));
_requiredBits = Bits.Compute(_max);
SetFixedSize(_requiredBits);
}
public RagonInt(
int initialValue,
int min = -1000,
int max = 1000,
bool invokeLocal = true,
int priority = 0
) : base(priority, invokeLocal)
{
_value = initialValue;
_min = min;
_max = max;
_requiredBits = Bits.Compute(_max);
SetFixedSize(_requiredBits);
}
public override void Serialize(RagonBuffer buffer)
{
uint compressedValue = (uint)((_value << 1) ^ (_value >> 31));
buffer.Write(compressedValue, _requiredBits);
}
public override void Deserialize(RagonBuffer buffer)
{
var compressedValue = buffer.Read(_requiredBits);
_value = (int)((compressedValue >> 1) ^ -(int)(compressedValue & 1));
InvokeChanged();
}
}
}
@@ -1,68 +0,0 @@
/*
* 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.Text;
using Ragon.Protocol;
namespace Ragon.Client.Simulation
{
[Serializable]
public class RagonString : RagonProperty
{
public string Value
{
get => _value;
set
{
_value = value;
MarkAsChanged();
}
}
private string _value;
private readonly UTF8Encoding _utf8Encoding = new UTF8Encoding(false, true);
public RagonString(
string initialValue,
bool invokeLocal = true,
int priority = 0
) : base(priority, invokeLocal)
{
_value = initialValue;
}
public override void Serialize(RagonBuffer buffer)
{
var data = _utf8Encoding.GetBytes(_value);
var len = (uint) data.Length;
buffer.Write(len, 16);
buffer.WriteBytes(data);
}
public override void Deserialize(RagonBuffer buffer)
{
var len = (int) buffer.Read(16);
var data = buffer.ReadBytes(len);
_value = _utf8Encoding.GetString(data);
InvokeChanged();
}
}
}
-21
View File
@@ -1,21 +0,0 @@
/*
* 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.Client.Simulation;
var simulation = new Simulation();
simulation.Start();
@@ -1,26 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ragon.Client.Property\Ragon.Client.Property.csproj" />
<ProjectReference Include="..\Ragon.Client\Ragon.Client.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ENet-CSharp" Version="2.4.8" />
</ItemGroup>
<ItemGroup>
<None Update="libenet.dylib">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
-110
View File
@@ -1,110 +0,0 @@
namespace Ragon.Client.Simulation;
public class Game : IRagonListener
{
private RagonFloat _health;
private RagonInt _points;
private RagonString _name;
private RagonEntity _entity;
private RagonClient _client;
public Game(RagonClient client)
{
_client = client;
}
public void OnConnected(RagonClient client)
{
RagonLog.Trace("Connected");
_client.Session.AuthorizeWithKey("defaultkey", "Player Eduard", Array.Empty<byte>());
}
public void OnAuthorizationSuccess(RagonClient client, string playerId, string playerName)
{
RagonLog.Trace("Authorized");
client.Session.CreateOrJoin("Example", 1, 20);
}
public void OnAuthorizationFailed(RagonClient client, string message)
{
Console.WriteLine($"Authorization failed: {message}");
}
public void OnJoined(RagonClient client)
{
RagonLog.Trace("Joined");
_health = new RagonFloat(100.0f, false, 0);
_health.Changed += () => Console.WriteLine($"[Ragon Property] Health: {_health.Value}");
_points = new RagonInt(0, -1000, 1000, false, 0);
_points.Changed += () => Console.WriteLine($"[Ragon Property] Points: {_points.Value}");
_name = new RagonString("Edmand 000", false);
_name.Changed += () => Console.WriteLine($"[Ragon Property] Name: {_name.Value}");
_entity = new RagonEntity(12, 0);
_entity.State.AddProperty(_health);
_entity.State.AddProperty(_points);
_entity.State.AddProperty(_name);
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;
public void Update()
{
if (_client.Status != RagonStatus.ROOM)
return;
_timer += 1 / 60.0f;
if (_timer > 1)
{
_health.Value += 20.0f;
_points.Value += 10;
_name.Value = $"Edmand 00{_client.Room.Local.PeerId}";
Console.WriteLine($"{_health.Value} {_points.Value} {_name.Value}");
_timer = 0;
}
}
}
@@ -1,130 +0,0 @@
/*
* 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 ENet;
using Event = ENet.Event;
using EventType = ENet.EventType;
namespace Ragon.Client
{
public class RagonENetConnection : INetworkConnection
{
public ushort Id { get; }
public NetworkStatistics Statistics { get; private set; }
public INetworkChannel Reliable { get; private set; }
public INetworkChannel Unreliable { get; private set; }
public Action<byte[]> OnData { get; set; }
public Action OnConnected { get; set; }
public Action<DisconnectReason> OnDisconnected { get; set; }
public ulong BytesSent { get; }
public ulong BytesReceived { get; }
public int Ping { get; }
private static bool _libraryLoaded = false;
private Host _host;
private Peer _peer;
private Event _netEvent;
public RagonENetConnection()
{
_host = new Host();
_host.Create();
}
public void Prepare()
{
if (!_libraryLoaded)
{
Library.Initialize();
_libraryLoaded = true;
}
}
public void Disconnect()
{
if (_peer.IsSet)
_peer.DisconnectNow(0);
}
public void Connect(string server, ushort port, uint protocol)
{
Address address = new Address();
address.SetHost(server);
address.Port = port;
_peer = _host.Connect(address, 2, protocol);
_peer.Timeout(32, 5000, 5000);
}
public void Update()
{
bool polled = false;
while (!polled)
{
if (_host.CheckEvents(out _netEvent) <= 0)
{
if (_host.Service(0, out _netEvent) <= 0)
break;
polled = true;
}
switch (_netEvent.Type)
{
case EventType.None:
break;
case EventType.Connect:
Statistics = new NetworkStatistics();
Reliable = new ENetReliableChannel(_netEvent.Peer, 0);
Unreliable = new ENetUnreliableChannel(_netEvent.Peer, 1);
OnConnected?.Invoke();
break;
case EventType.Disconnect:
OnDisconnected?.Invoke(DisconnectReason.MANUAL);
break;
case EventType.Timeout:
OnDisconnected?.Invoke(DisconnectReason.TIMEOUT);
break;
case EventType.Receive:
var data = new byte[_netEvent.Packet.Length];
_netEvent.Packet.CopyTo(data);
_netEvent.Packet.Dispose();
OnData?.Invoke(data);
break;
}
}
}
public void Dispose()
{
if (_host.IsSet)
{
_host?.Flush();
_host?.Dispose();
}
if (_libraryLoaded)
Library.Deinitialize();
}
}
}
@@ -1,39 +0,0 @@
/*
* 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 ENet;
namespace Ragon.Client;
public sealed class ENetUnreliableChannel : INetworkChannel
{
private Peer _peer;
private byte _channelId;
public ENetUnreliableChannel(Peer peer, int channelId)
{
_peer = peer;
_channelId = (byte) channelId;
}
public void Send(byte[] data)
{
var newPacket = new Packet();
newPacket.Create(data, data.Length, PacketFlags.None);
_peer.Send(_channelId, ref newPacket);
}
}
@@ -1,131 +0,0 @@
/*
* 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 ENet;
using Event = ENet.Event;
using EventType = ENet.EventType;
namespace Ragon.Client
{
public class RagonNullConnection : INetworkConnection
{
public ushort Id { get; }
public NetworkStatistics Statistics { get; private set; }
public INetworkChannel Reliable { get; private set; }
public INetworkChannel Unreliable { get; private set; }
public Action<byte[]> OnData { get; set; }
public Action OnConnected { get; set; }
public Action<DisconnectReason> OnDisconnected { get; set; }
public ulong BytesSent { get; }
public ulong BytesReceived { get; }
public int Ping { get; }
private static bool _libraryLoaded = false;
private Host _host;
private Peer _peer;
private Event _netEvent;
public RagonNullConnection()
{
_host = new Host();
_host.Create();
}
public void Prepare()
{
if (!_libraryLoaded)
{
Library.Initialize();
_libraryLoaded = true;
}
}
public void Disconnect()
{
if (_peer.IsSet)
_peer.DisconnectNow(0);
}
public void Connect(string server, ushort port, uint protocol)
{
Address address = new Address();
address.SetHost(server);
address.Port = port;
_peer = _host.Connect(address, 2, protocol);
_peer.Timeout(32, 5000, 5000);
Statistics = new NetworkStatistics();
Reliable = new NullReliableChannel(_netEvent.Peer, 0);
Unreliable = new NullUnreliableChannel(_netEvent.Peer, 1);
}
public void Update()
{
bool polled = false;
while (!polled)
{
if (_host.CheckEvents(out _netEvent) <= 0)
{
if (_host.Service(0, out _netEvent) <= 0)
break;
polled = true;
}
switch (_netEvent.Type)
{
case EventType.None:
break;
case EventType.Connect:
OnConnected?.Invoke();
break;
case EventType.Disconnect:
OnDisconnected?.Invoke(DisconnectReason.MANUAL);
break;
case EventType.Timeout:
OnDisconnected?.Invoke(DisconnectReason.TIMEOUT);
break;
case EventType.Receive:
var data = new byte[_netEvent.Packet.Length];
_netEvent.Packet.CopyTo(data);
_netEvent.Packet.Dispose();
OnData?.Invoke(data);
break;
}
}
}
public void Dispose()
{
if (_host.IsSet)
{
_host?.Flush();
_host?.Dispose();
}
if (_libraryLoaded)
Library.Deinitialize();
}
}
}
@@ -1,42 +0,0 @@
namespace Ragon.Client.Simulation;
public class EntityListener : IRagonEntityListener
{
public void OnEntityCreated(RagonEntity entity)
{
var health = new RagonFloat(100.0f, false, 0);
health.Value = 50;
health.Changed += () => Console.WriteLine($"[Ragon Property] Another Health: {health.Value}");
var points = new RagonInt(0, -1000, 1000, false, 0);
points.Changed += () => Console.WriteLine($"[Ragon Property] Anther Points: {points.Value}");
var name = new RagonString("Eduard", false);
name.Changed += () => Console.WriteLine($"[Ragon Property] Another Name: {name.Value}");
entity.State.AddProperty(health);
entity.State.AddProperty(points);
entity.State.AddProperty(name);
}
}
public class Simulation
{
public void Start()
{
// INetworkConnection protocol = debug ? new RagonNullConnection() : new RagonENetConnection();
// var network = new RagonClient(protocol, new EntityListener(), 30);
// var game = new Game(network);
// network.AddListener(game);
// network.Connect("127.0.0.1", 5001, "1.0.0");
// var dt = 1000 / 60.0f;
// while (true)
// {
// game.Update();
// network.Update(dt);
// Thread.Sleep((int) dt);
// }
//
// network.Disconnect();
}
}
+5 -4
View File
@@ -1,22 +1,23 @@
<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</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></OutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>/Users/edmand46/RagonProjects/ragon-unity-sdk/Assets/Ragon/Runtime/Plugins</OutputPath> <OutputPath></OutputPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -1,57 +0,0 @@
/*
* 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.Compressor;
public class FloatCompressor
{
public float Min { get; private set; }
public float Max { get; private set; }
public float Precision { get; private set; }
public int RequiredBits { get; private set; }
private uint _mask;
public FloatCompressor(float min = -1024.0f, float max = 1024.0f, float precision = 0.01f)
{
Min = min;
Max = max;
Precision = precision;
RequiredBits = DeBruijn.Log2((uint)((Max - Min) * (1f / Precision) + 0.5f)) + 1;
_mask = (uint)((1L << RequiredBits) - 1);
}
public uint Compress(float value)
{
return (uint)((value - Min) * 1f / Precision + 0.5f) & _mask;
}
public float Decompress(uint value)
{
var result = value * Precision + Min;
if (result < Min)
result = Min;
else if (result > Max)
result = Max;
return result;
}
}
@@ -1,44 +0,0 @@
/*
* 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.Compressor;
public class IntCompressor
{
public int Min { get; private set; }
public int Max { get; private set; }
public int RequiredBits { get; private set; }
public IntCompressor(int min = -1000, int max = 1000)
{
Min = min;
Max = max;
RequiredBits = Bits.Compute(Max);
}
public uint Compress(int value)
{
return (uint)((value << 1) ^ (value >> 31));;
}
public int Decompress(uint value)
{
return (int)((value >> 1) ^ -(int)(value & 1));
}
}
-218
View File
@@ -1,218 +0,0 @@
/*
* 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
{
public sealed class RagonEntity
{
private delegate void OnEventDelegate(RagonPlayer player, RagonBuffer serializer);
private RagonClient _client;
public ushort Id { get; private set; }
public ushort Type { get; private set; }
public bool Replication { get; private set; }
public RagonAuthority Authority { get; private set; }
public RagonPlayer Owner { get; private set; }
public RagonEntityState State { get; private set; }
public bool IsAttached { get; private set; }
public bool HasAuthority { get; private set; }
public event Action<RagonEntity> Attached;
public event Action Detached;
public event Action<RagonPlayer, RagonPlayer> OwnershipChanged;
internal bool PropertiesChanged => _propertiesChanged;
internal ushort SceneId => _sceneId;
private ushort _sceneId;
private bool _propertiesChanged;
private RagonPayload _spawnPayload;
private RagonPayload _destroyPayload;
private Dictionary<int, OnEventDelegate> _events = new();
private Dictionary<int, Action<RagonPlayer, IRagonEvent>> _localEvents = new();
public RagonEntity(ushort type = 0, ushort sceneId = 0)
{
State = new RagonEntityState(this);
Type = type;
_sceneId = sceneId;
}
internal void Attach(RagonClient client, ushort entityId, ushort entityType, bool hasAuthority, RagonPlayer owner, RagonPayload payload)
{
Type = entityType;
Id = entityId;
Owner = owner;
IsAttached = true;
Replication = true;
HasAuthority = hasAuthority;
_client = client;
_spawnPayload = payload;
Attached?.Invoke(this);
}
internal void Detach(RagonPayload payload)
{
_destroyPayload = payload;
Detached?.Invoke();
}
internal T GetPayload<T>(RagonPayload data) where T : IRagonPayload, new()
{
var buffer = new RagonBuffer();
data.Write(buffer);
var payload = new T();
payload.Deserialize(buffer);
return payload;
}
public T GetSpawnPayload<T>() where T : IRagonPayload, new()
{
return GetPayload<T>(_spawnPayload);
}
public T GetDestroyPayload<T>() where T : IRagonPayload, new()
{
return GetPayload<T>(_destroyPayload);
}
public void ReplicateEvent<TEvent>(TEvent evnt, RagonPlayer target, RagonReplicationMode replicationMode)
where TEvent : IRagonEvent, new()
{
var evntId = _client.Event.GetEventCode(evnt);
var buffer = _client.Buffer;
buffer.Clear();
buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
buffer.WriteUShort(Id);
buffer.WriteUShort(evntId);
buffer.WriteByte((byte) replicationMode);
buffer.WriteByte((byte) RagonTarget.Player);
buffer.WriteUShort((ushort) target.PeerId);
evnt.Serialize(buffer);
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void ReplicateEvent<TEvent>(
TEvent evnt,
RagonTarget target = RagonTarget.All,
RagonReplicationMode replicationMode = RagonReplicationMode.Server)
where TEvent : IRagonEvent, new()
{
if (target != RagonTarget.ExceptOwner)
{
if (replicationMode == RagonReplicationMode.Local)
{
var eventCode = _client.Event.GetEventCode(evnt);
_localEvents[eventCode].Invoke(_client.Room.Local, evnt);
return;
}
if (replicationMode == RagonReplicationMode.LocalAndServer)
{
var eventCode = _client.Event.GetEventCode(evnt);
_localEvents[eventCode].Invoke(_client.Room.Local, evnt);
}
}
var evntId = _client.Event.GetEventCode(evnt);
var buffer = _client.Buffer;
buffer.Clear();
buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
buffer.WriteUShort(Id);
buffer.WriteUShort(evntId);
buffer.WriteByte((byte) replicationMode);
buffer.WriteByte((byte) target);
evnt.Serialize(buffer);
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void OnEvent<TEvent>(Action<RagonPlayer, TEvent> callback) where TEvent : IRagonEvent, new()
{
var t = new TEvent();
var eventCode = _client.Event.GetEventCode(t);
if (_events.ContainsKey(eventCode))
{
RagonLog.Warn($"Event already {eventCode} subscribed");
return;
}
_localEvents.Add(eventCode, (player, eventData) => { callback.Invoke(player, (TEvent) eventData); });
_events.Add(eventCode, (player, serializer) =>
{
t.Deserialize(serializer);
callback.Invoke(player, t);
});
}
internal void Write(RagonBuffer buffer)
{
buffer.WriteUShort(Id);
State.WriteState(buffer);
_propertiesChanged = false;
}
internal void Read(RagonBuffer buffer)
{
State.ReadState(buffer);
}
internal void Event(ushort eventCode, RagonPlayer caller, RagonBuffer buffer)
{
if (_events.ContainsKey(eventCode))
_events[eventCode]?.Invoke(caller, buffer);
}
internal void TrackChangedProperty(RagonProperty property)
{
_propertiesChanged = true;
}
public void OnOwnershipChanged(RagonPlayer player)
{
var prevOwner = Owner;
Owner = player;
HasAuthority = player.PeerId == _client.Room.Local.PeerId;
OwnershipChanged?.Invoke(prevOwner, player);
}
}
}
@@ -1,77 +0,0 @@
/*
* 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;
public sealed class RagonEntityState
{
private List<RagonProperty> _properties;
private RagonEntity _entity;
public RagonEntityState(RagonEntity entity)
{
_entity = entity;
_properties = new List<RagonProperty>(6);
}
public void AddProperty(RagonProperty property)
{
_properties.Add(property);
property.AssignEntity(_entity);
}
internal void WriteInfo(RagonBuffer buffer)
{
buffer.WriteUShort((ushort)_properties.Count);
foreach (var property in _properties)
{
buffer.WriteBool(property.IsFixed);
buffer.WriteUShort((ushort)property.Size);
}
}
internal void ReadState(RagonBuffer buffer)
{
foreach (var property in _properties)
{
var changed = buffer.ReadBool();
if (changed)
property.Deserialize(buffer);
}
}
internal void WriteState(RagonBuffer buffer)
{
foreach (var prop in _properties)
{
if (prop.IsDirty)
{
buffer.WriteBool(true);
prop.Write(buffer);
prop.Flush();
}
else
{
prop.AddTick();
buffer.WriteBool(false);
}
}
}
}
@@ -1,51 +0,0 @@
/*
* 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;
public struct RagonPayload
{
private uint[] _data = new uint[128];
private int _size = 0;
public RagonPayload(int capacity)
{
_size = capacity;
}
public int Size => _size;
public void Read(RagonBuffer buffer)
{
var readOnlySpan = _data.AsSpan();
buffer.ReadSpan(ref readOnlySpan, _size);
}
public void Write(RagonBuffer buffer)
{
ReadOnlySpan<uint> readOnlySpan = _data.AsSpan();
buffer.WriteSpan(ref readOnlySpan, _size);
}
public override string ToString()
{
return $"Payload Size: {_size}";
}
}
@@ -1,124 +0,0 @@
/*
* 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
{
[Serializable]
public class RagonProperty
{
public string Name => _name;
public RagonEntity Entity => _entity;
public event Action Changed;
public bool IsDirty => _dirty && _ticks >= _priority;
public bool IsFixed => _fixed;
public int Size => _size;
private bool _fixed;
private string _name;
protected bool _invokeLocal;
private RagonEntity _entity;
private bool _dirty;
private int _size;
private int _ticks;
private int _priority;
protected RagonProperty(int priority, bool invokeLocal)
{
_size = 0;
_priority = priority;
_fixed = false;
_invokeLocal = invokeLocal;
}
public void SetName(string name)
{
_name = name;
}
protected void SetFixedSize(int size)
{
_size = size;
_fixed = true;
}
protected void InvokeChanged()
{
if (!_invokeLocal)
return;
Changed?.Invoke();
}
protected void MarkAsChanged()
{
InvokeChanged();
if (_dirty || _entity == null)
return;
_dirty = true;
_entity.TrackChangedProperty(this);
}
internal void Flush()
{
_dirty = false;
_ticks = 0;
}
internal void AddTick()
{
_ticks++;
}
internal void AssignEntity(RagonEntity ent)
{
_entity = ent;
Changed?.Invoke();
}
internal void Write(RagonBuffer buffer)
{
if (_fixed)
{
Serialize(buffer);
return;
}
var sizeOffset = buffer.WriteOffset;
buffer.Write(0, 16);
var propOffset = buffer.WriteOffset;
Serialize(buffer);
var propSize = (uint)(buffer.WriteOffset - propOffset);
buffer.Write(propSize, 16, sizeOffset);
}
public virtual void Serialize(RagonBuffer buffer)
{
}
public virtual void Deserialize(RagonBuffer buffer)
{
}
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
internal class AuthorizeFailedHandler: Handler internal class AuthorizeFailedHandler: IHandler
{ {
private readonly RagonListenerList _listenerList; private readonly RagonListenerList _listenerList;
public AuthorizeFailedHandler(RagonListenerList list) public AuthorizeFailedHandler(RagonListenerList list)
@@ -27,9 +27,9 @@ internal class AuthorizeFailedHandler: Handler
_listenerList = list; _listenerList = list;
} }
public void Handle(RagonBuffer buffer) public void Handle(RagonStream reader)
{ {
var message = buffer.ReadString(); var message = reader.ReadString();
_listenerList.OnAuthorizationFailed(message); _listenerList.OnAuthorizationFailed(message);
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,20 +19,26 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
internal class AuthorizeSuccessHandler: Handler internal class AuthorizeSuccessHandler: IHandler
{ {
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;
} }
public void Handle(RagonBuffer buffer) public void Handle(RagonStream reader)
{ {
var playerId = buffer.ReadString(); var playerId = reader.ReadString();
var playerName = buffer.ReadString(); var playerName = reader.ReadString();
var playerPayload = reader.ReadString();
_listenerList.OnAuthorizationSuccess(playerId, playerName); _client.UpdateState(RagonState.LOBBY);
_listenerList.OnAuthorizationSuccess(playerId, playerName, playerPayload);
} }
} }
@@ -1,58 +0,0 @@
/*
* 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 EntityCreateHandler : Handler
{
private readonly RagonClient _client;
private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache;
public EntityCreateHandler(
RagonClient client,
RagonPlayerCache playerCache,
RagonEntityCache entityCache
)
{
_client = client;
_entityCache = entityCache;
_playerCache = playerCache;
}
public void Handle(RagonBuffer buffer)
{
var attachId = buffer.ReadUShort();
var entityType = buffer.ReadUShort();
var entityId = buffer.ReadUShort();
var ownerId = buffer.ReadUShort();
var player = _playerCache.GetPlayerByPeer(ownerId);
var payload = new RagonPayload(buffer.Capacity);
payload.Read(buffer);
if (player == null)
{
RagonLog.Warn($"Owner {ownerId}|{player.Name} not found in players");
return;
}
var hasAuthority = _playerCache.LocalPlayer.Id == player.Id;
var entity = _entityCache.OnCreate(attachId, entityType, 0, entityId, hasAuthority);
entity.Attach(_client, entityId, entityType, hasAuthority, player, payload);
}
}
@@ -1,39 +0,0 @@
/*
* 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 EntityDestroyHandler: Handler
{
private readonly RagonEntityCache _entityCache;
public EntityDestroyHandler(RagonEntityCache entityCache)
{
_entityCache = entityCache;
}
public void Handle(RagonBuffer buffer)
{
var entityId = buffer.ReadUShort();
var payload = new RagonPayload(buffer.Capacity);
payload.Read(buffer);
_entityCache.OnDestroy(entityId, payload);
}
}
@@ -1,57 +0,0 @@
/*
* 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 EntityEventHandler : Handler
{
private readonly RagonClient _client;
private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache;
public EntityEventHandler(
RagonClient client,
RagonPlayerCache playerCache,
RagonEntityCache entityCache
)
{
_client = client;
_playerCache = playerCache;
_entityCache = entityCache;
}
public void Handle(RagonBuffer buffer)
{
var eventCode = buffer.ReadUShort();
var peerId = buffer.ReadUShort();
var executionMode = (RagonReplicationMode)buffer.ReadByte();
var entityId = buffer.ReadUShort();
var player = _playerCache.GetPlayerByPeer(peerId);
if (player == null)
{
RagonLog.Warn($"Player not found for event {eventCode}");
return;
}
if (player.IsMe && executionMode == RagonReplicationMode.LocalAndServer)
return;
_entityCache.OnEvent(player, entityId, eventCode, buffer);
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
public interface Handler public interface IHandler
{ {
public void Handle(RagonBuffer buffer); public void Handle(RagonStream reader);
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
internal class JoinFailedHandler: Handler internal class JoinFailedHandler: IHandler
{ {
private readonly RagonListenerList _listenerList; private readonly RagonListenerList _listenerList;
@@ -28,9 +28,9 @@ internal class JoinFailedHandler: Handler
_listenerList = listenerList; _listenerList = listenerList;
} }
public void Handle(RagonBuffer buffer) public void Handle(RagonStream reader)
{ {
var message = buffer.ReadString(); var message = reader.ReadString();
_listenerList.OnFailed(message); _listenerList.OnFailed(message);
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,9 +19,9 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
public struct RagonRoomInformation public struct RoomParameters
{ {
public RagonRoomInformation(string roomId, string playerId, string ownerId, ushort min, ushort max) public RoomParameters(string roomId, string playerId, string ownerId, ushort min, ushort max)
{ {
RoomId = roomId; RoomId = roomId;
PlayerId = playerId; PlayerId = playerId;
@@ -37,44 +37,52 @@ public struct RagonRoomInformation
public ushort Max { get; private set; } public ushort Max { get; private set; }
} }
internal class JoinSuccessHandler : Handler internal class JoinSuccessHandler : IHandler
{ {
private readonly RagonListenerList _listenerList; private readonly RagonListenerList _listenerList;
private readonly RagonPlayerCache _playerCache; private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache;
private readonly RagonClient _client; private readonly RagonClient _client;
private readonly RagonBuffer _buffer; private readonly RagonRoom _room;
public JoinSuccessHandler( public JoinSuccessHandler(
RagonClient client, RagonClient client,
RagonBuffer buffer, RagonRoom room
RagonListenerList listenerList,
RagonPlayerCache playerCache,
RagonEntityCache entityCache
) )
{ {
_buffer = buffer;
_client = client; _client = client;
_listenerList = listenerList; _room = room;
_entityCache = entityCache;
_playerCache = playerCache;
} }
public void Handle(RagonBuffer buffer) public void Handle(RagonStream reader)
{ {
var roomId = buffer.ReadString(); var roomId = reader.ReadString();
var localId = buffer.ReadString(); var min = reader.ReadUShort();
var ownerId = buffer.ReadString(); var max = reader.ReadUShort();
var min = buffer.ReadUShort(); var localId = reader.ReadString();
var max = buffer.ReadUShort(); var ownerId = reader.ReadString();
var map = buffer.ReadString(); var roomInfo = new RoomParameters(roomId, localId, ownerId, min, max);
var scene = new RagonScene(_client, _playerCache, _entityCache);
var roomInfo = new RagonRoomInformation(roomId, localId, ownerId, min, max);
var room = new RagonRoom(_client, _entityCache, _playerCache, roomInfo, scene);
_playerCache.SetOwnerAndLocal(ownerId, localId); _playerCache.SetOwnerAndLocal(ownerId, localId);
_client.AssignRoom(room); _room.Reset(roomInfo);
_listenerList.OnLevel(map); _room.UserData.Read(reader);
var playersCount = reader.ReadUShort();
RagonLog.Trace("Players: " + playersCount);
for (var i = 0; i < playersCount; i++)
{
var playerPeerId = reader.ReadUShort();
var playerId = reader.ReadString();
var playerName = reader.ReadString();
var player = _playerCache.AddPlayer(playerPeerId, playerId, playerName);
player.UserData.Read(reader);
RagonLog.Trace($"Player {playerPeerId} - {playerId} - {playerName}");
}
_client.UpdateState(RagonState.ROOM);
_listenerList.OnJoined();
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,26 +19,23 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
internal class LeaveRoomHandler : Handler internal class LeaveRoomHandler : IHandler
{ {
private readonly RagonClient _client; private readonly RagonClient _client;
private readonly RagonListenerList _listenerList; private readonly RagonListenerList _listenerList;
private readonly RagonEntityCache _entityCache;
public LeaveRoomHandler( public LeaveRoomHandler(
RagonClient client, RagonClient client,
RagonListenerList listenerList, RagonListenerList listenerList)
RagonEntityCache entityCache)
{ {
_client = client; _client = client;
_listenerList = listenerList; _listenerList = listenerList;
_entityCache = entityCache;
} }
public void Handle(RagonBuffer buffer) public void Handle(RagonStream reader)
{ {
_listenerList.OnLeft(); _listenerList.OnLeft();
_entityCache.Cleanup();
_client.Room.Cleanup(); _client.Room.Clear();
} }
} }
@@ -1,44 +0,0 @@
/*
* 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 SceneLoadHandler: Handler
{
private readonly RagonClient _client;
private readonly RagonListenerList _listenerList;
public SceneLoadHandler(
RagonClient ragonClient,
RagonListenerList listenerList
)
{
_client = ragonClient;
_listenerList = listenerList;
}
public void Handle(RagonBuffer buffer)
{
var sceneName = buffer.ReadString();
var room = _client.Room;
room.Cleanup();
_listenerList.OnLevel(sceneName);
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,35 +19,32 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
internal class OwnershipHandler: Handler internal class OwnershipRoomHandler : IHandler
{ {
private readonly RagonListenerList _listenerList; private readonly RagonListenerList _listenerList;
private readonly RagonPlayerCache _playerCache; private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache;
public OwnershipHandler( public OwnershipRoomHandler(
RagonListenerList listenerList, RagonListenerList listenerList,
RagonPlayerCache playerCache, RagonPlayerCache playerCache)
RagonEntityCache entityCache)
{ {
_listenerList = listenerList; _listenerList = listenerList;
_playerCache = playerCache; _playerCache = playerCache;
_entityCache = entityCache;
} }
public void Handle(RagonBuffer buffer) public void Handle(RagonStream reader)
{ {
var newOwnerId = buffer.ReadString(); var newOwnerId = reader.ReadUShort();
var player = _playerCache.GetPlayerById(newOwnerId); var player = _playerCache.GetPlayerByPeer(newOwnerId);
if (player == null)
{
RagonLog.Warn($"Player with peerId:{newOwnerId} not found in cache");
_playerCache.Dump();
return;
}
_playerCache.OnOwnershipChanged(newOwnerId); _playerCache.OnOwnershipChanged(newOwnerId);
_listenerList.OnOwnershipChanged(player); _listenerList.OnOwnershipChanged(player);
var entities = buffer.ReadUShort();
for (var i = 0; i < entities; i++)
{
var entityId = buffer.ReadUShort();
_entityCache.OnOwnershipChanged(player, entityId);
}
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
internal class PlayerJoinHandler : Handler internal class PlayerJoinHandler : IHandler
{ {
private RagonPlayerCache _playerCache; private RagonPlayerCache _playerCache;
private RagonListenerList _listenerList; private RagonListenerList _listenerList;
@@ -33,11 +33,11 @@ internal class PlayerJoinHandler : Handler
_listenerList = listenerList; _listenerList = listenerList;
} }
public void Handle(RagonBuffer buffer) public void Handle(RagonStream reader)
{ {
var playerPeerId = buffer.ReadUShort(); var playerPeerId = reader.ReadUShort();
var playerId = buffer.ReadString(); var playerId = reader.ReadString();
var playerName = buffer.ReadString(); var playerName = reader.ReadString();
_playerCache.AddPlayer(playerPeerId, playerId, playerName); _playerCache.AddPlayer(playerPeerId, playerId, playerName);
@@ -45,6 +45,6 @@ internal class PlayerJoinHandler : Handler
if (player != null) if (player != null)
_listenerList.OnPlayerJoined(player); _listenerList.OnPlayerJoined(player);
else else
RagonLog.Trace($"[Joined] {playerId}"); RagonLog.Warn($"Player with Id:{playerId} not found in cache");
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,42 +19,45 @@ using Ragon.Protocol;
namespace Ragon.Client; namespace Ragon.Client;
internal class PlayerLeftHandler : Handler internal class PlayerLeftHandler : IHandler
{ {
private RagonPlayerCache _playerCache; private RagonPlayerCache _playerCache;
private RagonEntityCache _entityCache;
private RagonListenerList _listenerList; private RagonListenerList _listenerList;
public PlayerLeftHandler( public PlayerLeftHandler(
RagonEntityCache entityCache,
RagonPlayerCache playerCache, RagonPlayerCache playerCache,
RagonListenerList listenerList RagonListenerList listenerList
) )
{ {
_entityCache = entityCache;
_playerCache = playerCache; _playerCache = playerCache;
_listenerList = listenerList; _listenerList = listenerList;
} }
public void Handle(RagonBuffer buffer) public void Handle(RagonStream reader)
{ {
var playerId = buffer.ReadString(); var playerId = reader.ReadString();
var player = _playerCache.GetPlayerById(playerId); var player = _playerCache.GetPlayerById(playerId);
if (player != null) if (player != null)
{ {
_playerCache.RemovePlayer(playerId); _playerCache.RemovePlayer(playerId);
_listenerList.OnPlayerLeft(player); _listenerList.OnPlayerLeft(player);
var entities = buffer.ReadUShort(); var entities = reader.ReadUShort();
var toDeleteIds = new ushort[entities]; var toDeleteIds = new ushort[entities];
for (var i = 0; i < entities; i++) for (var i = 0; i < entities; i++)
{ {
var entityId = buffer.ReadUShort(); var entityId = reader.ReadUShort();
toDeleteIds[i] = entityId; toDeleteIds[i] = entityId;
} }
foreach (var id in toDeleteIds)
_entityCache.OnDestroy(id, new RagonPayload()); // var emptyPayload = new RagonPayload(0);
// foreach (var id in toDeleteIds)
// _entityCache.OnDestroy(id, emptyPayload);
}
else
{
RagonLog.Warn($"Player with Id:{playerId} not found in cache");
} }
} }
} }
@@ -0,0 +1,52 @@
/*
* Copyright 2024 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 PlayerUserDataHandler: IHandler
{
private RagonPlayerCache _playerCache;
private RagonListenerList _listenerList;
public PlayerUserDataHandler(
RagonPlayerCache playerCache,
RagonListenerList listenerList
)
{
_playerCache = playerCache;
_listenerList = listenerList;
}
public void Handle(RagonStream reader)
{
var playerPeerId = reader.ReadUShort();
var player = _playerCache.GetPlayerByPeer(playerPeerId);
if (player != null)
{
var changes = player.UserData.Read(reader);
_listenerList.OnPlayerUserData(player, changes);
return;
}
RagonLog.Warn("Received user data for unknown player.");
}
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2023-2024 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;
public class RoomEventHandler : IHandler
{
private readonly RagonClient _client;
private readonly RagonPlayerCache _playerCache;
public RoomEventHandler(
RagonClient client,
RagonPlayerCache playerCache
)
{
_client = client;
_playerCache = playerCache;
}
public void Handle(RagonStream buffer)
{
var eventCode = buffer.ReadUShort();
var peerId = buffer.ReadUShort();
var executionMode = (RagonReplicationMode) buffer.ReadByte();
var player = _playerCache.GetPlayerByPeer(peerId);
if (player == null)
{
RagonLog.Error($"Player with peerId:{peerId} not found as owner of event with code:{eventCode}");
_playerCache.Dump();
return;
}
if (player.IsLocal && executionMode == RagonReplicationMode.LocalAndServer)
return;
_client.Room.HandleEvent(eventCode, player, buffer);
}
}
@@ -0,0 +1,43 @@
using Ragon.Protocol;
namespace Ragon.Client;
internal class RoomListHandler: IHandler
{
private RagonListenerList _listenerList;
private RagonSession _session;
public RoomListHandler(RagonSession session, RagonListenerList list)
{
_session = session;
_listenerList = list;
}
public void Handle(RagonStream reader)
{
var roomCount = reader.ReadUShort();
var roomList = new RagonRoomInformation[roomCount];
for (int i = 0; i < roomCount; i++)
{
var id = reader.ReadString();
var scene = reader.ReadString();
var maxPlayers = reader.ReadUShort();
var minPlayers = reader.ReadUShort();
var players = reader.ReadUShort();
var roomInfo = new RagonRoomInformation()
{
Id = id,
Scene = scene,
PlayerCount = players,
PlayerMax = maxPlayers,
PlayerMin = minPlayers,
Properties = new Dictionary<string, byte[]>()
};
roomList[i] = roomInfo;
}
_listenerList.OnRoomList(roomList);
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,8 +18,21 @@ using Ragon.Protocol;
namespace Ragon.Client namespace Ragon.Client
{ {
public interface IRagonPayload: IRagonSerializable internal class RoomUserDataHandler : IHandler
{ {
private readonly RagonClient _client;
private readonly RagonListenerList _listenerList;
public RoomUserDataHandler(RagonClient client, RagonListenerList listenerList)
{
_client = client;
_listenerList = listenerList;
}
public void Handle(RagonStream reader)
{
var changes = _client.Room?.UserData.Read(reader);
_listenerList.OnRoomUserData(changes);
}
} }
} }
@@ -1,97 +0,0 @@
/*
* 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 SnapshotHandler : Handler
{
private RagonClient _client;
private RagonListenerList _listenerList;
private RagonEntityCache _entityCache;
private RagonPlayerCache _playerCache;
public SnapshotHandler(
RagonClient ragonClient,
RagonListenerList listenerList,
RagonEntityCache entityCache,
RagonPlayerCache playerCache
)
{
_client = ragonClient;
_listenerList = listenerList;
_entityCache = entityCache;
_playerCache = playerCache;
}
public void Handle(RagonBuffer buffer)
{
var playersCount = buffer.ReadUShort();
RagonLog.Trace("Players: " + playersCount);
for (var i = 0; i < playersCount; i++)
{
var playerPeerId = buffer.ReadUShort();
var playerId = buffer.ReadString();
var playerName = buffer.ReadString();
_playerCache.AddPlayer(playerPeerId, playerId, playerName);
}
var dynamicEntities = buffer.ReadUShort();
RagonLog.Trace("Dynamic Entities: " + dynamicEntities);
for (var i = 0; i < dynamicEntities; i++)
{
var entityType = buffer.ReadUShort();
var entityId = buffer.ReadUShort();
var ownerPeerId = buffer.ReadUShort();
var payloadSize = buffer.ReadUShort();
var player = _playerCache.GetPlayerByPeer(ownerPeerId);
var payload = new RagonPayload(payloadSize);
payload.Read(buffer);
var hasAuthority = _playerCache.LocalPlayer.Id == player.Id;
var entity = _entityCache.OnCreate(0, entityType, 0, entityId, hasAuthority);
entity.Read(buffer);
entity.Attach(_client, entityId, entityType, hasAuthority, player, payload);
}
var staticEntities = buffer.ReadUShort();
RagonLog.Trace("Scene Entities: " + staticEntities);
for (var i = 0; i < staticEntities; i++)
{
var entityType = buffer.ReadUShort();
var entityId = buffer.ReadUShort();
var staticId = buffer.ReadUShort();
var ownerPeerId = buffer.ReadUShort();
var payloadSize = buffer.ReadUShort();
var player = _playerCache.GetPlayerByPeer(ownerPeerId);
var payload = new RagonPayload(payloadSize);
payload.Read(buffer);
var hasAuthority = _playerCache.LocalPlayer.Id == player.Id;
var entity = _entityCache.OnCreate(0, entityType, staticId, entityId, hasAuthority);
entity.Read(buffer);
entity.Attach(_client, entityId, entityType, hasAuthority, player, payload);
}
_listenerList.OnJoined();
}
}
@@ -0,0 +1,21 @@
using Ragon.Protocol;
namespace Ragon.Client;
public class TimestampHandler: IHandler
{
private readonly RagonClient _client;
public TimestampHandler(RagonClient client)
{
_client = client;
}
public void Handle(RagonStream buffer)
{
var timestamp0 = (uint)buffer.ReadInt();
var timestamp1 = (uint)buffer.ReadInt();
var value = new DoubleToUInt { Int0 = timestamp0, Int1 = timestamp1 };
_client.UpdateTimestamp(value.Double);
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -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; }
+1 -1
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,5 +18,5 @@ namespace Ragon.Client;
public interface IRagonConnection public interface IRagonConnection
{ {
public void Close();
} }
+1 -2
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -20,6 +20,5 @@ namespace Ragon.Client
{ {
public interface IRagonEvent: IRagonSerializable public interface IRagonEvent: IRagonSerializable
{ {
} }
} }
+27
View File
@@ -0,0 +1,27 @@
/*
* Copyright 2024 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;
public interface IUserData
{
public byte[] this[string key] { get; set; }
bool Dirty { get; }
IReadOnlyList<string> Read(RagonStream buffer);
void Write(RagonStream buffer);
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -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);
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
namespace Ragon.Client; namespace Ragon.Client;
public interface IRagonSceneCollector public interface IRagonDataListener
{ {
public RagonEntity[] Collect(); public void OnData(RagonClient client, RagonPlayer player, byte[] data);
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -1,22 +0,0 @@
/*
* 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.Client;
public interface IRagonLevelListener
{
void OnLevel(RagonClient client, string sceneName);
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,14 +18,17 @@ namespace Ragon.Client
{ {
public interface IRagonListener : public interface IRagonListener :
IRagonAuthorizationListener, IRagonAuthorizationListener,
IRagonConnectedListener, IRagonConnectionListener,
IRagonFailedListener, IRagonFailedListener,
IRagonJoinListener, IRagonJoinListener,
IRagonLeftListener, IRagonLeftListener,
IRagonLevelListener,
IRagonOwnershipChangedListener, IRagonOwnershipChangedListener,
IRagonPlayerJoinListener, IRagonPlayerJoinListener,
IRagonPlayerLeftListener IRagonPlayerLeftListener,
IRagonRoomListListener,
IRagonRoomUserDataListener,
IRagonPlayerUserDataListener
{ {
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
namespace Ragon.Client; namespace Ragon.Client;
public interface IRagonEntityListener public interface IRagonPlayerUserDataListener
{ {
public void OnEntityCreated(RagonEntity entity); void OnPlayerUserDataUpdated(RagonClient client, RagonPlayer player, IReadOnlyList<string> changes);
} }
@@ -0,0 +1,6 @@
namespace Ragon.Client;
public interface IRagonRoomListListener
{
public void OnRoomListUpdate(RagonClient client, IReadOnlyList<RagonRoomInformation> roomsInfos);
}
@@ -0,0 +1,6 @@
namespace Ragon.Client;
public interface IRagonRoomUserDataListener
{
public void OnUserDataUpdated(RagonClient client, IReadOnlyList<string> changes);
}
+1 -1
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
+1 -1
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
+1 -1
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
+137 -85
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,46 +21,39 @@ 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 Handler[] _handlers; private IHandler[] _handlers;
private RagonBuffer _readBuffer; private RagonStream _readBuffer;
private RagonBuffer _writeBuffer; private RagonStream _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 RagonEventCache _eventCache; private RagonEventCache _eventCache;
private RagonStatus _status; private RagonState _state;
private NetworkStatistics _stats;
private double _serverTimestamp;
private float _replicationRate = 0; private float _replicationRate = 0;
private float _replicationTime = 0; private float _replicationTime = 0;
public double ServerTimestamp => _serverTimestamp;
public IRagonConnection Connection => _connection; public IRagonConnection Connection => _connection;
public RagonStatus Status => _status; public RagonState State => _state;
public RagonSession Session => _session; public RagonSession Session => _session;
public RagonEventCache Event => _eventCache; public RagonEventCache Event => _eventCache;
public RagonEntityCache Entity => _entityCache;
public NetworkStatistics Statistics => _stats; public NetworkStatistics Statistics => _stats;
public RagonRoom Room => _room; public RagonRoom Room => _room;
internal RagonBuffer Buffer => _writeBuffer; internal RagonStream Buffer => _writeBuffer;
internal INetworkChannel Reliable => _connection.Reliable; internal INetworkChannel Reliable => _connection.Reliable;
internal INetworkChannel Unreliable => _connection.Unreliable; internal INetworkChannel Unreliable => _connection.Unreliable;
#region PUBLIC #region PUBLIC
public RagonClient( public RagonClient(INetworkConnection connection, int rate)
INetworkConnection connection,
IRagonEntityListener entityListener,
IRagonSceneCollector sceneCollector,
int rate)
{ {
_listenerList = new RagonListenerList(this); _listeners = new RagonListenerList(this);
_entityListener = entityListener;
_sceneCollector = sceneCollector;
_connection = connection; _connection = connection;
_connection.OnData += OnData; _connection.OnData += OnData;
@@ -71,125 +64,184 @@ namespace Ragon.Client
_replicationTime = 0; _replicationTime = 0;
_eventCache = new RagonEventCache(); _eventCache = new RagonEventCache();
_writeBuffer = new RagonStream();
_readBuffer = new RagonStream();
_playerCache = new RagonPlayerCache();
_session = new RagonSession(this, _writeBuffer);
_room = new RagonRoom(this, _playerCache);
_stats = new NetworkStatistics(); _stats = new NetworkStatistics();
_status = RagonStatus.DISCONNECTED; _state = RagonState.DISCONNECTED;
_handlers = new IHandler[byte.MaxValue];
_handlers[(byte)RagonOperation.AUTHORIZED_SUCCESS] = new AuthorizeSuccessHandler(this, _listeners);
_handlers[(byte)RagonOperation.AUTHORIZED_FAILED] = new AuthorizeFailedHandler(_listeners);
_handlers[(byte)RagonOperation.JOIN_SUCCESS] = new JoinSuccessHandler(this, _room);
_handlers[(byte)RagonOperation.JOIN_FAILED] = new JoinFailedHandler(_listeners);
_handlers[(byte)RagonOperation.LEAVE_ROOM] = new LeaveRoomHandler(this, _listeners);
_handlers[(byte)RagonOperation.OWNERSHIP_ROOM_CHANGED] = new OwnershipRoomHandler(_listeners, _playerCache);
_handlers[(byte)RagonOperation.PLAYER_JOINED] = new PlayerJoinHandler(_playerCache, _listeners);
_handlers[(byte)RagonOperation.PLAYER_LEAVED] = new PlayerLeftHandler(_playerCache, _listeners);
_handlers[(byte)RagonOperation.REPLICATE_ROOM_EVENT] = new RoomEventHandler(this, _playerCache);
_handlers[(byte)RagonOperation.TIMESTAMP_SYNCHRONIZATION] = new TimestampHandler(this);
_handlers[(byte)RagonOperation.ROOM_LIST_UPDATED] = new RoomListHandler(_session, _listeners);
_handlers[(byte)RagonOperation.ROOM_DATA_UPDATED] = new RoomUserDataHandler(this, _listeners);
_handlers[(byte)RagonOperation.PLAYER_DATA_UPDATED] = new PlayerUserDataHandler(_playerCache, _listeners);
} }
public void Connect(string address, ushort port, string protocol) public void Connect(string address, ushort port, string protocol)
{ {
_writeBuffer = new RagonBuffer();
_readBuffer = new RagonBuffer();
_session = new RagonSession(this, _readBuffer);
_playerCache = new RagonPlayerCache();
_entityCache = new RagonEntityCache(this, _playerCache, _entityListener, _sceneCollector);
_handlers = new Handler[byte.MaxValue];
_handlers[(byte)RagonOperation.AUTHORIZED_SUCCESS] = new AuthorizeSuccessHandler(_listenerList);
_handlers[(byte)RagonOperation.AUTHORIZED_FAILED] = new AuthorizeFailedHandler(_listenerList);
_handlers[(byte)RagonOperation.JOIN_SUCCESS] =
new JoinSuccessHandler(this, _readBuffer, _listenerList, _playerCache, _entityCache);
_handlers[(byte)RagonOperation.JOIN_FAILED] = new JoinFailedHandler(_listenerList);
_handlers[(byte)RagonOperation.LEAVE_ROOM] = new LeaveRoomHandler(this, _listenerList, _entityCache);
_handlers[(byte)RagonOperation.OWNERSHIP_CHANGED] =
new OwnershipHandler(_listenerList, _playerCache, _entityCache);
_handlers[(byte)RagonOperation.PLAYER_JOINED] = new PlayerJoinHandler(_playerCache, _listenerList);
_handlers[(byte)RagonOperation.PLAYER_LEAVED] =
new PlayerLeftHandler(_entityCache, _playerCache, _listenerList);
_handlers[(byte)RagonOperation.LOAD_SCENE] = new SceneLoadHandler(this, _listenerList);
_handlers[(byte)RagonOperation.CREATE_ENTITY] = new EntityCreateHandler(this, _playerCache, _entityCache);
_handlers[(byte)RagonOperation.DESTROY_ENTITY] = new EntityDestroyHandler(_entityCache);
_handlers[(byte)RagonOperation.REPLICATE_ENTITY_STATE] = new StateEntityHandler(_entityCache);
_handlers[(byte)RagonOperation.REPLICATE_ENTITY_EVENT] =
new EntityEventHandler(this, _playerCache, _entityCache);
_handlers[(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);
} }
public void Disconnect() public void Disconnect()
{ {
_status = RagonStatus.DISCONNECTED; _state = RagonState.DISCONNECTED;
_room.Cleanup(); _room.Clear();
_connection.Disconnect(); _connection.Disconnect();
OnDisconnected(DisconnectReason.MANUAL);
OnDisconnected(RagonDisconnect.MANUAL);
} }
public void Update(float dt) public void Update(float dt)
{
if (_state != RagonState.DISCONNECTED)
{ {
_replicationTime += dt; _replicationTime += dt;
if (_replicationTime >= _replicationRate) if (_replicationTime >= _replicationRate)
{ {
_entityCache.WriteState(_readBuffer);
_replicationTime = 0; _replicationTime = 0;
SendTimestamp();
SendRoomUserData();
SendPlayerUserData();
} }
_stats.Update(_connection.BytesSent, _connection.BytesReceived, _connection.Ping, dt); _stats.Update(_connection.BytesSent, _connection.BytesReceived, _connection.Ping, dt);
}
_listeners.Update();
_connection.Update(); _connection.Update();
} }
public void Dispose() public void Dispose()
{ {
_status = RagonStatus.DISCONNECTED; if (_state != RagonState.DISCONNECTED)
{
_state = RagonState.DISCONNECTED;
_connection.Disconnect(); _connection.Disconnect();
}
_connection.Dispose(); _connection.Dispose();
} }
public void AddListener(IRagonListener listener) => _listenerList.Add(listener); public void AddListener(IRagonListener listener) => _listeners.Add(listener);
public void AddListener(IRagonAuthorizationListener listener) => _listenerList.Add(listener); public void AddListener(IRagonAuthorizationListener listener) => _listeners.Add(listener);
public void AddListener(IRagonConnectedListener listener) => _listenerList.Add(listener); public void AddListener(IRagonConnectionListener listener) => _listeners.Add(listener);
public void AddListener(IRagonFailedListener listener) => _listenerList.Add(listener); public void AddListener(IRagonFailedListener listener) => _listeners.Add(listener);
public void AddListener(IRagonJoinListener listener) => _listenerList.Add(listener); public void AddListener(IRagonJoinListener listener) => _listeners.Add(listener);
public void AddListener(IRagonLeftListener listener) => _listenerList.Add(listener); public void AddListener(IRagonLeftListener listener) => _listeners.Add(listener);
public void AddListener(IRagonLevelListener listener) => _listenerList.Add(listener); public void AddListener(IRagonOwnershipChangedListener listener) => _listeners.Add(listener);
public void AddListener(IRagonOwnershipChangedListener listener) => _listenerList.Add(listener); public void AddListener(IRagonPlayerJoinListener listener) => _listeners.Add(listener);
public void AddListener(IRagonPlayerJoinListener listener) => _listenerList.Add(listener); public void AddListener(IRagonPlayerLeftListener listener) => _listeners.Add(listener);
public void AddListener(IRagonPlayerLeftListener listener) => _listenerList.Add(listener); public void AddListener(IRagonRoomListListener listener) => _listeners.Add(listener);
public void AddListener(IRagonPlayerUserDataListener listener) => _listeners.Add(listener);
public void RemoveListener(IRagonListener listener) => _listenerList.Remove(listener); public void AddListener(IRagonRoomUserDataListener listener) => _listeners.Add(listener);
public void RemoveListener(IRagonAuthorizationListener listener) => _listenerList.Remove(listener); public void RemoveListener(IRagonListener listener) => _listeners.Remove(listener);
public void RemoveListener(IRagonConnectedListener listener) => _listenerList.Remove(listener); public void RemoveListener(IRagonAuthorizationListener listener) => _listeners.Remove(listener);
public void RemoveListener(IRagonFailedListener listener) => _listenerList.Remove(listener); public void RemoveListener(IRagonConnectionListener listener) => _listeners.Remove(listener);
public void RemoveListener(IRagonJoinListener listener) => _listenerList.Remove(listener); public void RemoveListener(IRagonFailedListener listener) => _listeners.Remove(listener);
public void RemoveListener(IRagonLeftListener listener) => _listenerList.Remove(listener); public void RemoveListener(IRagonJoinListener listener) => _listeners.Remove(listener);
public void RemoveListener(IRagonLevelListener listener) => _listenerList.Remove(listener); public void RemoveListener(IRagonLeftListener listener) => _listeners.Remove(listener);
public void RemoveListener(IRagonOwnershipChangedListener listener) => _listenerList.Remove(listener); public void RemoveListener(IRagonOwnershipChangedListener listener) => _listeners.Remove(listener);
public void RemoveListener(IRagonPlayerJoinListener listener) => _listenerList.Remove(listener); public void RemoveListener(IRagonPlayerJoinListener listener) => _listeners.Remove(listener);
public void RemoveListener(IRagonPlayerLeftListener listener) => _listenerList.Remove(listener); public void RemoveListener(IRagonPlayerLeftListener listener) => _listeners.Remove(listener);
public void RemoveListener(IRagonRoomListListener listener) => _listeners.Remove(listener);
public void RemoveListener(IRagonRoomUserDataListener listener) => _listeners.Remove(listener);
public void RemoveListener(IRagonPlayerUserDataListener listener) => _listeners.Remove(listener);
#endregion #endregion
#region INTERNAL #region INTERNAL
internal void AssignRoom(RagonRoom room) internal void UpdateState(RagonState state)
{ {
_room = room; _state = state;
_status = RagonStatus.ROOM; }
internal void UpdateTimestamp(double time)
{
_serverTimestamp = time;
} }
#endregion #endregion
#region PRIVATE #region PRIVATE
private void SendTimestamp()
{
var timestamp = RagonTime.CurrentTimestamp();
var value = new DoubleToUInt()
{
Double = timestamp,
};
_writeBuffer.Clear();
_writeBuffer.WriteOperation(RagonOperation.TIMESTAMP_SYNCHRONIZATION);
_writeBuffer.WriteInt((int)value.Int0);
_writeBuffer.WriteInt((int)value.Int1);
}
private void SendRoomUserData()
{
if (_room == null) return;
var props = _room.UserData;
if (!props.Dirty) return;
_writeBuffer.Clear();
_writeBuffer.WriteOperation(RagonOperation.ROOM_DATA_UPDATED);
props.Write(_writeBuffer);
var sendData = _writeBuffer.ToArray();
_connection.Reliable.Send(sendData);
}
private void SendPlayerUserData()
{
if (_playerCache.Local == null) return;
var props = _playerCache.Local.UserData;
if (!props.Dirty) return;
_writeBuffer.Clear();
_writeBuffer.WriteOperation(RagonOperation.PLAYER_DATA_UPDATED);
props.Write(_writeBuffer);
var sendData = _writeBuffer.ToArray();
_connection.Reliable.Send(sendData);
}
private void OnConnected() private void OnConnected()
{ {
RagonLog.Trace("Connected"); RagonLog.Trace("Connected");
_listenerList.OnConnected(); _listeners.OnConnected();
_status = RagonStatus.CONNECTED; _state = RagonState.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; _state = RagonState.DISCONNECTED;
} }
public void OnData(byte[] data) private void OnData(byte[] data)
{ {
_readBuffer.Clear(); _readBuffer.Clear();
_readBuffer.FromArray(data); _readBuffer.FromArray(data);
-232
View File
@@ -1,232 +0,0 @@
/*
* 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;
public sealed class RagonEntityCache
{
private readonly List<RagonEntity> _entityList = new();
private readonly Dictionary<uint, RagonEntity> _entityMap = new();
private readonly Dictionary<uint, RagonEntity> _pendingEntities = new();
private readonly Dictionary<uint, RagonEntity> _sceneEntities = new();
private readonly RagonClient _client;
private readonly IRagonEntityListener _entityListener;
private readonly IRagonSceneCollector _sceneCollector;
private readonly RagonPlayerCache _playerCache;
private int _localEntitiesCounter = 0;
public RagonEntityCache(
RagonClient client,
RagonPlayerCache playerCache,
IRagonEntityListener listener,
IRagonSceneCollector sceneCollector
)
{
_client = client;
_entityListener = listener;
_sceneCollector = sceneCollector;
_playerCache = playerCache;
}
public RagonEntity FindById(ushort id)
{
return _entityMap[id];
}
public void Create(RagonEntity entity, IRagonPayload? spawnPayload)
{
var attachId = (ushort) (_playerCache.LocalPlayer.PeerId + _localEntitiesCounter++) ;
var buffer = _client.Buffer;
buffer.Clear();
buffer.WriteOperation(RagonOperation.CREATE_ENTITY);
buffer.WriteUShort(attachId);
buffer.WriteUShort(entity.Type);
buffer.WriteByte((byte) entity.Authority);
entity.State.WriteInfo(buffer);
spawnPayload?.Serialize(buffer);
_pendingEntities.Add(attachId, entity);
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void Destroy(RagonEntity entity, IRagonPayload? destroyPayload)
{
if (!entity.IsAttached)
{
RagonLog.Warn("Can't destroy object, he is not created");
return;
}
var buffer = _client.Buffer;
buffer.Clear();
buffer.WriteOperation(RagonOperation.DESTROY_ENTITY);
buffer.WriteUShort(entity.Id);
destroyPayload?.Serialize(buffer);
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
internal void WriteState(RagonBuffer buffer)
{
var changedEntities = 0u;
buffer.Clear();
buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
var offset = buffer.WriteOffset;
buffer.Write(0, 16);
foreach (var ent in _entityList)
{
if (!ent.IsAttached ||
!ent.Replication ||
!ent.PropertiesChanged) continue;
ent.Write(buffer);
changedEntities++;
}
if (changedEntities <= 0) return;
buffer.Write(changedEntities, 16, offset);
var data = buffer.ToArray();
_client.Unreliable.Send(data);
}
internal void WriteScene(RagonBuffer buffer)
{
_sceneEntities.Clear();
var entities = _sceneCollector.Collect();
buffer.WriteUShort((ushort) entities.Length);
foreach (var entity in entities)
{
buffer.WriteUShort(entity.Type);
buffer.WriteByte((byte) entity.Authority);
buffer.WriteUShort(entity.SceneId);
entity.State.WriteInfo(buffer);
_sceneEntities.Add(entity.SceneId, entity);
}
}
internal void CacheScene()
{
_sceneEntities.Clear();
var entities = _sceneCollector.Collect();
foreach (var entity in entities)
_sceneEntities.Add(entity.SceneId, entity);
}
internal void Cleanup()
{
var payload = new RagonPayload();
foreach (var ent in _entityList)
ent.Detach(payload);
_entityMap.Clear();
_entityList.Clear();
}
internal RagonEntity OnCreate(ushort attachId, ushort entityType, ushort sceneId, ushort entityId, bool hasAuthority)
{
if (sceneId > 0)
{
if (_sceneEntities.TryGetValue(sceneId, out var entity))
{
_entityMap.Add(entityId, entity);
if (hasAuthority)
_entityList.Add(entity);
return entity;
}
}
if (_pendingEntities.Remove(attachId, out var existsEntity))
{
_entityMap.Add(entityId, existsEntity);
if (hasAuthority)
_entityList.Add(existsEntity);
return existsEntity;
}
else
{
var entity = new RagonEntity(entityType, sceneId);
_entityMap.Add(entityId, entity);
if (hasAuthority)
_entityList.Add(entity);
_entityListener.OnEntityCreated(entity);
return entity;
}
}
internal void OnDestroy(ushort entityId, RagonPayload payload)
{
if (_entityMap.Remove(entityId, out var ragonEntity))
{
_entityList.Remove(ragonEntity);
ragonEntity.Detach(payload);
}
}
internal void OnState(ushort entityId, RagonBuffer buffer)
{
if (_entityMap.TryGetValue(entityId, out var entity))
entity.Read(buffer);
else
RagonLog.Warn($"Entity {entityId} not found!");
}
internal void OnEvent(RagonPlayer player, ushort entityId, ushort eventCode, RagonBuffer buffer)
{
if (_entityMap.TryGetValue(entityId, out var entity))
entity.Event(eventCode, player, buffer);
else
RagonLog.Warn($"Entity {entityId} not found!");
}
internal void OnOwnershipChanged(RagonPlayer player, ushort entityId)
{
if (_entityMap.TryGetValue(entityId, out var entity))
entity.OnOwnershipChanged(player);
else
RagonLog.Warn($"Entity {entityId} not found!");
}
}
+9 -3
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -26,8 +26,14 @@ 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];
return evntCode; if (!_eventsRegistryByType.TryGetValue(type, out var eventCode))
{
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()
+85 -35
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,20 +14,25 @@
* limitations under the License. * limitations under the License.
*/ */
using Ragon.Protocol;
namespace Ragon.Client namespace Ragon.Client
{ {
internal class RagonListenerList internal class RagonListenerList
{ {
private readonly RagonClient _client; private readonly RagonClient _client;
private readonly List<IRagonAuthorizationListener> _authorizationListeners = new(); private readonly List<IRagonAuthorizationListener> _authorizationListeners = new();
private readonly List<IRagonConnectedListener> _connectionListeners = new(); private readonly List<IRagonConnectionListener> _connectionListeners = new();
private readonly List<IRagonFailedListener> _failedListeners = new(); private readonly List<IRagonFailedListener> _failedListeners = new();
private readonly List<IRagonJoinListener> _joinListeners = new(); private readonly List<IRagonJoinListener> _joinListeners = new();
private readonly List<IRagonLeftListener> _leftListeners = new(); private readonly List<IRagonLeftListener> _leftListeners = new();
private readonly List<IRagonLevelListener> _levelListeners = new();
private readonly List<IRagonOwnershipChangedListener> _ownershipChangedListeners = new(); private readonly List<IRagonOwnershipChangedListener> _ownershipChangedListeners = new();
private readonly List<IRagonPlayerJoinListener> _playerJoinListeners = new(); private readonly List<IRagonPlayerJoinListener> _playerJoinListeners = new();
private readonly List<IRagonPlayerLeftListener> _playerLeftListeners = new(); private readonly List<IRagonPlayerLeftListener> _playerLeftListeners = new();
private readonly List<IRagonRoomListListener> _roomListListeners = new();
private readonly List<IRagonRoomUserDataListener> _roomUserDataListeners = new();
private readonly List<IRagonPlayerUserDataListener> _playerUserDataListeners = new();
private readonly List<Action> _delayedActions = new();
public RagonListenerList(RagonClient client) public RagonListenerList(RagonClient client)
{ {
@@ -41,23 +46,36 @@ namespace Ragon.Client
_failedListeners.Add(listener); _failedListeners.Add(listener);
_joinListeners.Add(listener); _joinListeners.Add(listener);
_leftListeners.Add(listener); _leftListeners.Add(listener);
_levelListeners.Add(listener);
_ownershipChangedListeners.Add(listener); _ownershipChangedListeners.Add(listener);
_playerJoinListeners.Add(listener); _playerJoinListeners.Add(listener);
_playerLeftListeners.Add(listener); _playerLeftListeners.Add(listener);
_roomUserDataListeners.Add(listener);
_playerUserDataListeners.Add(listener);
} }
public void Remove(IRagonListener listener) public void Remove(IRagonListener listener)
{
_delayedActions.Add(() =>
{ {
_authorizationListeners.Remove(listener); _authorizationListeners.Remove(listener);
_connectionListeners.Remove(listener); _connectionListeners.Remove(listener);
_failedListeners.Remove(listener); _failedListeners.Remove(listener);
_joinListeners.Remove(listener); _joinListeners.Remove(listener);
_leftListeners.Remove(listener); _leftListeners.Remove(listener);
_levelListeners.Remove(listener);
_ownershipChangedListeners.Remove(listener); _ownershipChangedListeners.Remove(listener);
_playerJoinListeners.Remove(listener); _playerJoinListeners.Remove(listener);
_playerLeftListeners.Remove(listener); _playerLeftListeners.Remove(listener);
_roomUserDataListeners.Remove(listener);
_playerUserDataListeners.Remove(listener);
});
}
public void Update()
{
foreach (var action in _delayedActions)
action.Invoke();
_delayedActions.Clear();
} }
public void Add(IRagonAuthorizationListener listener) public void Add(IRagonAuthorizationListener listener)
@@ -65,7 +83,7 @@ namespace Ragon.Client
_authorizationListeners.Add(listener); _authorizationListeners.Add(listener);
} }
public void Add(IRagonConnectedListener listener) public void Add(IRagonConnectionListener listener)
{ {
_connectionListeners.Add(listener); _connectionListeners.Add(listener);
} }
@@ -85,11 +103,6 @@ namespace Ragon.Client
_leftListeners.Add(listener); _leftListeners.Add(listener);
} }
public void Add(IRagonLevelListener listener)
{
_levelListeners.Add(listener);
}
public void Add(IRagonOwnershipChangedListener listener) public void Add(IRagonOwnershipChangedListener listener)
{ {
_ownershipChangedListeners.Add(listener); _ownershipChangedListeners.Add(listener);
@@ -105,52 +118,77 @@ namespace Ragon.Client
_playerLeftListeners.Add(listener); _playerLeftListeners.Add(listener);
} }
public void Remove(IRagonAuthorizationListener listener) public void Add(IRagonRoomListListener listener)
{ {
_authorizationListeners.Remove(listener); _roomListListeners.Add(listener);
} }
public void Remove(IRagonConnectedListener listener) public void Add(IRagonRoomUserDataListener listener)
{ {
_connectionListeners.Remove(listener); _roomUserDataListeners.Add(listener);
}
public void Add(IRagonPlayerUserDataListener listener)
{
_playerUserDataListeners.Add(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) public void Remove(IRagonFailedListener listener)
{ {
_failedListeners.Remove(listener); _delayedActions.Add(() => _failedListeners.Remove(listener));
} }
public void Remove(IRagonJoinListener listener) public void Remove(IRagonJoinListener listener)
{ {
_joinListeners.Remove(listener); _delayedActions.Add(() => _joinListeners.Remove(listener));
} }
public void Remove(IRagonLeftListener listener) public void Remove(IRagonLeftListener listener)
{ {
_leftListeners.Remove(listener); _delayedActions.Add(() => _leftListeners.Remove(listener));
}
public void Remove(IRagonLevelListener listener)
{
_levelListeners.Remove(listener);
} }
public void Remove(IRagonOwnershipChangedListener listener) public void Remove(IRagonOwnershipChangedListener listener)
{ {
_ownershipChangedListeners.Remove(listener); _delayedActions.Add(() => _ownershipChangedListeners.Remove(listener));
} }
public void Remove(IRagonPlayerJoinListener listener) public void Remove(IRagonPlayerJoinListener listener)
{ {
_playerJoinListeners.Remove(listener); _delayedActions.Add(() => _playerJoinListeners.Remove(listener));
} }
public void Remove(IRagonPlayerLeftListener listener) public void Remove(IRagonPlayerLeftListener listener)
{ {
_playerLeftListeners.Remove(listener); _delayedActions.Add(() => _playerLeftListeners.Remove(listener));
} }
public void OnAuthorizationSuccess(string playerId, string playerName) public void Remove(IRagonRoomListListener listener)
{
_delayedActions.Add(() => _roomListListeners.Remove(listener));
}
public void Remove(IRagonRoomUserDataListener listener)
{
_delayedActions.Add(() => _roomUserDataListeners.Remove(listener));
}
public void Remove(IRagonPlayerUserDataListener listener)
{
_delayedActions.Add(() => _playerUserDataListeners.Remove(listener));
}
public void OnAuthorizationSuccess(string playerId, string playerName, string payload)
{ {
foreach (var listener in _authorizationListeners) foreach (var listener in _authorizationListeners)
listener.OnAuthorizationSuccess(_client, playerId, playerName); listener.OnAuthorizationSuccess(_client, playerId, playerName);
@@ -192,12 +230,6 @@ namespace Ragon.Client
listener.OnPlayerJoined(_client, player); listener.OnPlayerJoined(_client, player);
} }
public void OnLevel(string sceneName)
{
foreach (var listener in _levelListeners)
listener.OnLevel(_client, sceneName);
}
public void OnJoined() public void OnJoined()
{ {
foreach (var listener in _joinListeners) foreach (var listener in _joinListeners)
@@ -210,10 +242,28 @@ namespace Ragon.Client
listener.OnConnected(_client); listener.OnConnected(_client);
} }
public void OnDisconnected() public void OnDisconnected(RagonDisconnect disconnect)
{ {
foreach (var listener in _connectionListeners) foreach (var listener in _connectionListeners)
listener.OnDisconnected(_client); listener.OnDisconnected(_client, disconnect);
}
public void OnRoomList(RagonRoomInformation[] roomInfos)
{
foreach (var listListener in _roomListListeners)
listListener.OnRoomListUpdate(_client, roomInfos);
}
public void OnRoomUserData(IReadOnlyList<string> changes)
{
foreach (var userDataListener in _roomUserDataListeners)
userDataListener.OnUserDataUpdated(_client, changes);
}
public void OnPlayerUserData(RagonPlayer player, IReadOnlyList<string> changes)
{
foreach(var playerUserDataListener in _playerUserDataListeners)
playerUserDataListener.OnPlayerUserDataUpdated(_client, player, changes);
} }
} }
} }
+6 -4
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,15 +23,17 @@ 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 IUserData UserData { get; private 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;
UserData = isLocal ? new RagonUserData() : new RagonUserDataReadOnly();
} }
} }
} }
+44 -15
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,16 +18,30 @@ 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)
public RagonPlayer? GetPlayerByPeer(ushort peerId) => _playersByConnection[peerId]; {
if (_playersById.TryGetValue(playerId, out var player))
return player;
return null;
}
public RagonPlayer? GetPlayerByPeer(ushort peerId)
{
if (_playersByConnection.TryGetValue(peerId, out var player))
return player;
return null;
}
private string _ownerId; private string _ownerId;
private string _localId; private string _localId;
@@ -38,10 +52,10 @@ public sealed class RagonPlayerCache
_localId = localId; _localId = localId;
} }
public void AddPlayer(ushort peerId, string playerId, string playerName) public RagonPlayer AddPlayer(ushort peerId, string playerId, string playerName)
{ {
if (_playersById.ContainsKey(playerId)) if (_playersById.ContainsKey(playerId))
return; return null;
var isOwner = playerId == _ownerId; var isOwner = playerId == _ownerId;
var isLocal = playerId == _localId; var isLocal = playerId == _localId;
@@ -50,8 +64,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;
@@ -59,24 +73,29 @@ public sealed class RagonPlayerCache
_players.Add(player); _players.Add(player);
_playersById.Add(player.Id, player); _playersById.Add(player.Id, player);
_playersByConnection.Add(player.PeerId, player); _playersByConnection.Add(player.PeerId, player);
return player;
} }
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;
}
} }
} }
@@ -87,4 +106,14 @@ public sealed class RagonPlayerCache
_playersByConnection.Clear(); _playersByConnection.Clear();
_playersById.Clear(); _playersById.Clear();
} }
public void Dump()
{
RagonLog.Trace("Players: ");
RagonLog.Trace("[Connection] [ID] [Name]");
foreach (var player in _players)
{
RagonLog.Trace($"[{player.PeerId}] {player.Id} {player.Name}");
}
}
} }
+169 -32
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,49 +14,186 @@
* limitations under the License. * limitations under the License.
*/ */
using Ragon.Protocol;
namespace Ragon.Client namespace Ragon.Client
{ {
public class RagonRoom public class RagonRoom
{ {
private RagonClient _client; private class EventSubscription : IDisposable
private RagonScene _scene;
private RagonEntityCache _entityCache;
private RagonPlayerCache _playerCache;
private RagonRoomInformation _information;
public string Id => _information.RoomId;
public int MinPlayers => _information.Min;
public int MaxPlayers => _information.Max;
public RagonPlayer Local => _playerCache.LocalPlayer;
public RagonPlayer Owner => _playerCache.Owner;
public RagonRoom(RagonClient client,
RagonEntityCache entityCache,
RagonPlayerCache playerCache,
RagonRoomInformation information,
RagonScene scene)
{ {
_client = client; private List<Action<RagonPlayer, IRagonEvent>> _callbacks;
_information = information; private List<Action<RagonPlayer, IRagonEvent>> _localCallbacks;
_entityCache = entityCache; private Action<RagonPlayer, IRagonEvent> _callback;
_playerCache = playerCache;
_scene = scene; public EventSubscription(
List<Action<RagonPlayer, IRagonEvent>> callbacks,
List<Action<RagonPlayer, IRagonEvent>> localCallbacks,
Action<RagonPlayer, IRagonEvent> callback)
{
_callbacks = callbacks;
_localCallbacks = localCallbacks;
_callback = callback;
} }
internal void Cleanup() public void Dispose()
{ {
_entityCache.Cleanup(); _callbacks?.Remove(_callback);
_localCallbacks?.Remove(_callback);
_callbacks = null!;
_localCallbacks = null!;
_callback = null!;
}
}
private delegate void OnEventDelegate(RagonPlayer player, RagonStream serializer);
private readonly RagonClient _client;
private readonly RagonPlayerCache _playerCache;
private RoomParameters _parameters;
private RagonUserData _userData;
public string Id => _parameters.RoomId;
public int MinPlayers => _parameters.Min;
public int MaxPlayers => _parameters.Max;
public IReadOnlyList<RagonPlayer> Players => _playerCache.Players;
public RagonPlayer Local => _playerCache.Local;
public RagonPlayer Owner => _playerCache.Owner;
public RagonUserData UserData => _userData;
private readonly Dictionary<int, OnEventDelegate> _events = new();
private readonly Dictionary<int, List<Action<RagonPlayer, IRagonEvent>>> _localListeners = new();
private readonly Dictionary<int, List<Action<RagonPlayer, IRagonEvent>>> _listeners = new();
public RagonRoom(RagonClient client, RagonPlayerCache playerCache)
{
_client = client;
_playerCache = playerCache;
}
public void Reset(RoomParameters parameters)
{
Clear();
_userData = new RagonUserData();
_parameters = parameters;
_playerCache.Cleanup(); _playerCache.Cleanup();
} }
public void LoadScene(string map) => _scene.Load(map); internal void HandleEvent(ushort eventCode, RagonPlayer caller, RagonStream buffer)
public void SceneLoaded() => _scene.SceneLoaded(); {
if (_events.TryGetValue(eventCode, out var evnt))
evnt?.Invoke(caller, buffer);
else
RagonLog.Warn($"Handler event {Id} with eventCode {eventCode} not defined");
}
public void CreateEntity(RagonEntity entity) => CreateEntity(entity, null); internal void HandleUserData(RagonStream buffer)
public void CreateEntity(RagonEntity entity, IRagonPayload? payload) => _entityCache.Create(entity, payload); {
_userData.Read(buffer);
}
public void DestroyEntity(RagonEntity entityId) => DestroyEntity(entityId, null); public IDisposable OnEvent<TEvent>(Action<RagonPlayer, TEvent> callback) where TEvent : IRagonEvent, new()
public void DestroyEntity(RagonEntity entityId, IRagonPayload? payload) => _entityCache.Destroy(entityId, payload); {
var t = new TEvent();
var eventCode = _client.Event.GetEventCode(t);
var action = (RagonPlayer player, IRagonEvent eventData) => callback.Invoke(player, (TEvent)eventData);
if (!_listeners.TryGetValue(eventCode, out var callbacks))
{
callbacks = new List<Action<RagonPlayer, IRagonEvent>>();
_listeners.Add(eventCode, callbacks);
}
if (!_localListeners.TryGetValue(eventCode, out var localCallbacks))
{
localCallbacks = new List<Action<RagonPlayer, IRagonEvent>>();
_localListeners.Add(eventCode, localCallbacks);
}
callbacks.Add(action);
localCallbacks.Add(action);
if (!_events.ContainsKey(eventCode))
{
_events.Add(eventCode, (player, serializer) =>
{
t.Deserialize(serializer);
foreach (var callbackListener in callbacks)
callbackListener.Invoke(player, t);
});
}
return new EventSubscription(callbacks, localCallbacks, action);
}
public void ReplicateEvent<TEvent>(TEvent evnt, RagonTarget target, RagonReplicationMode replicationMode)
where TEvent : IRagonEvent, new()
{
var evntId = _client.Event.GetEventCode(evnt);
var buffer = _client.Buffer;
{
if (replicationMode == RagonReplicationMode.Local &&
_localListeners.TryGetValue(evntId, out var localListeners))
{
foreach (var listener in localListeners)
listener.Invoke(_client.Room.Local, evnt);
return;
}
}
{
if (replicationMode == RagonReplicationMode.LocalAndServer &&
_localListeners.TryGetValue(evntId, out var localListeners))
{
foreach (var listener in localListeners)
listener.Invoke(_client.Room.Local, evnt);
}
}
buffer.Clear();
buffer.WriteOperation(RagonOperation.REPLICATE_ROOM_EVENT);
buffer.WriteUShort(evntId);
buffer.WriteByte((byte)replicationMode);
buffer.WriteByte((byte)target);
evnt.Serialize(buffer);
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void ReplicateEvent<TEvent>(TEvent evnt, RagonPlayer target, RagonReplicationMode replicationMode)
where TEvent : IRagonEvent, new()
{
var evntId = _client.Event.GetEventCode(evnt);
var buffer = _client.Buffer;
buffer.Clear();
buffer.WriteOperation(RagonOperation.REPLICATE_ROOM_EVENT);
buffer.WriteUShort(evntId);
buffer.WriteByte((byte)replicationMode);
buffer.WriteByte((byte)RagonTarget.Player);
buffer.WriteUShort(target.PeerId);
evnt.Serialize(buffer);
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void Clear()
{
_events.Clear();
_listeners.Clear();
_localListeners.Clear();
}
} }
} }
@@ -0,0 +1,12 @@
namespace Ragon.Client
{
public struct RagonRoomInformation
{
public string Id;
public string Scene;
public int PlayerMax;
public int PlayerMin;
public int PlayerCount;
public Dictionary<string, byte[]> Properties;
}
}
-61
View File
@@ -1,61 +0,0 @@
/*
* 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;
public class RagonScene
{
private readonly RagonClient _client;
private readonly RagonEntityCache _entityCache;
private readonly RagonPlayerCache _playerCache;
public RagonScene(RagonClient client, RagonPlayerCache playerCache, RagonEntityCache entityCache)
{
_client = client;
_playerCache = playerCache;
_entityCache = entityCache;
}
internal void Load(string map)
{
var buffer = _client.Buffer;
buffer.Clear();
buffer.WriteOperation(RagonOperation.LOAD_SCENE);
buffer.WriteString(map);
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
internal void SceneLoaded()
{
var buffer = _client.Buffer;
buffer.Clear();
buffer.WriteOperation(RagonOperation.SCENE_LOADED);
if (_playerCache.IsRoomOwner)
_entityCache.WriteScene(buffer);
else
_entityCache.CacheScene();
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
}
+13 -13
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,17 +21,18 @@ namespace Ragon.Client
public class RagonSession public class RagonSession
{ {
private readonly RagonClient _client; private readonly RagonClient _client;
private readonly RagonBuffer _buffer;
public RagonSession(RagonClient client, RagonBuffer buffer) private readonly RagonStream _buffer;
public RagonSession(RagonClient client, RagonStream buffer)
{ {
_client = client; _client = client;
_buffer = buffer; _buffer = buffer;
} }
public void CreateOrJoin(string map, int minPlayers, int maxPlayers) public void CreateOrJoin(string sessionName, int minPlayers, int maxPlayers)
{ {
var parameters = new RagonRoomParameters() {Map = map, Min = minPlayers, Max = maxPlayers}; var parameters = new RagonRoomParameters() { Min = minPlayers, Max = maxPlayers };
CreateOrJoin(parameters); CreateOrJoin(parameters);
} }
@@ -46,14 +47,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 sessionName, int minPlayers, int maxPlayers)
{ {
Create(null, new RagonRoomParameters() {Map = map, Min = minPlayers, Max = maxPlayers}); Create(null, new RagonRoomParameters() { Min = minPlayers, Max = maxPlayers });
} }
public void Create(string roomId, string map, int minPlayers, int maxPlayers) public void Create(string roomId, string sessionName, int minPlayers, int maxPlayers)
{ {
Create(roomId, new RagonRoomParameters() {Map = map, Min = minPlayers, Max = maxPlayers}); Create(roomId, new RagonRoomParameters() { Min = minPlayers, Max = maxPlayers });
} }
public void Create(string roomId, RagonRoomParameters parameters) public void Create(string roomId, RagonRoomParameters parameters)
@@ -79,7 +80,7 @@ namespace Ragon.Client
public void Leave() public void Leave()
{ {
var sendData = new[] {(byte) RagonOperation.LEAVE_ROOM}; var sendData = new[] { (byte)RagonOperation.LEAVE_ROOM };
_client.Reliable.Send(sendData); _client.Reliable.Send(sendData);
} }
@@ -93,17 +94,16 @@ 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);
} }
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
namespace Ragon.Client namespace Ragon.Client
{ {
public enum RagonStatus public enum RagonState
{ {
DISCONNECTED, DISCONNECTED,
CONNECTED, CONNECTED,
+104
View File
@@ -0,0 +1,104 @@
/*
* Copyright 2024 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
{
public class RagonUserData : IUserData
{
public byte[] this[string key]
{
get => _properties[key];
set
{
_properties[key] = value;
if (!_changesCache.ContainsKey(key))
{
_localChanges.Add(key);
_changesCache.Add(key, true);
}
}
}
public void Remove(string key)
{
if (_properties.Remove(key))
{
if (!_changesCache.ContainsKey(key))
{
_localChanges.Add(key);
_changesCache.Add(key, true);
}
}
}
public bool Dirty => _localChanges.Count > 0;
private readonly List<string> _localChanges = new();
private readonly Dictionary<string, bool> _changesCache = new();
private readonly Dictionary<string, byte[]> _properties = new();
public RagonUserData()
{
}
public IReadOnlyList<string> Read(RagonStream buffer)
{
var len = buffer.ReadUShort();
var changes = new List<string>(len);
for (int i = 0; i < len; i++)
{
var key = buffer.ReadString();
var valueSize = buffer.ReadUShort();
if (valueSize > 0)
{
var value = buffer.ReadBinary(valueSize);
_properties[key] = value;
}
else
{
_properties.Remove(key);
}
changes.Add(key);
}
return changes;
}
public void Write(RagonStream buffer)
{
buffer.WriteUShort((ushort)_localChanges.Count);
foreach (var propertyChanged in _localChanges)
{
buffer.WriteString(propertyChanged);
if (_properties.TryGetValue(propertyChanged, out var property))
{
buffer.WriteUShort((ushort)property.Length);
buffer.WriteBinary(property);
}
else
{
buffer.WriteUShort(0);
}
}
_localChanges.Clear();
}
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2024 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;
public class RagonUserDataReadOnly : IUserData
{
public byte[] this[string key]
{
get => _properties[key];
set { }
}
public bool Dirty => _dirty;
private bool _dirty = false;
private readonly Dictionary<string, byte[]> _properties = new();
public RagonUserDataReadOnly()
{
}
public void Write(RagonStream buffer)
{
}
public IReadOnlyList<string> Read(RagonStream buffer)
{
var len = buffer.ReadUShort();
var changes = new List<string>(len);
for (int i = 0; i < len; i++)
{
var key = buffer.ReadString();
var valueSize = buffer.ReadUShort();
if (valueSize > 0)
{
var value = buffer.ReadBinary(valueSize);
_properties[key] = value;
}
else
{
_properties.Remove(key);
}
changes.Add(key);
}
return changes;
}
}
+12 -3
View File
@@ -4,8 +4,17 @@
<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>
<Title>Ragon.Protocol</Title>
<Copyright>Eduard Kargin</Copyright>
<Version>1.4.0</Version>
<Authors>Eduard Kargin</Authors>
<PackageProjectUrl>https://ragon.io</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 +25,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>
+1 -1
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
+90 -12
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -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;
@@ -69,16 +68,19 @@ namespace Ragon.Protocol
private int _read; private int _read;
private int _write; private int _write;
private uint[] _buckets; private uint[] _buckets;
private byte[] _rawData;
private readonly UTF8Encoding _utf8Encoding = new UTF8Encoding(false, true); private readonly UTF8Encoding _utf8Encoding = new UTF8Encoding(false, true);
public byte[] RawData => _rawData;
public int ReadOffset => _read; public int ReadOffset => _read;
public int WriteOffset => _write; public int WriteOffset => _write;
public int Length => ((_write - 1) >> 3) + 1; public int Length => ((_write - 1) >> 3) + 1;
public int Capacity => _write - _read; public int Capacity => _write - _read - 1;
public RagonBuffer(int capacity = 128) public RagonBuffer(int capacity = 128)
{ {
_buckets = new uint[capacity]; _buckets = new uint[capacity];
_rawData = Array.Empty<byte>();
_read = 0; _read = 0;
_write = 0; _write = 0;
} }
@@ -119,6 +121,21 @@ namespace Ragon.Protocol
return (RagonOperation)Read(8); return (RagonOperation)Read(8);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteFloat(float value)
{
var bytes = BitConverter.GetBytes(value);
WriteBytes(bytes);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float ReadFloat()
{
var bytes = ReadBytes(4);
var value = BitConverter.ToSingle(bytes, 0);
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteFloat(float value, float min, float max, float precision) public void WriteFloat(float value, float min, float max, float precision)
{ {
@@ -145,6 +162,23 @@ namespace Ragon.Protocol
return adjusted; return adjusted;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteInt(int value)
{
var bytes = BitConverter.GetBytes(value);
WriteBytes(bytes);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt()
{
var bytes = ReadBytes(4);
var value = BitConverter.ToInt32(bytes, 0);
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteInt(int value, int min, int max) public void WriteInt(int value, int min, int max)
{ {
@@ -234,9 +268,6 @@ namespace Ragon.Protocol
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(uint value, int numBits = 16) public void Write(uint value, int numBits = 16)
{ {
Debug.Assert(!(numBits < 0));
Debug.Assert(!(numBits > 32));
var currentBucketIndex = _write >> 5; var currentBucketIndex = _write >> 5;
var used = _write & 0x0000001F; var used = _write & 0x0000001F;
var mask = (1UL << used) - 1; var mask = (1UL << used) - 1;
@@ -252,6 +283,7 @@ namespace Ragon.Protocol
_write += numBits; _write += numBits;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint Read(int numBits = 16) public uint Read(int numBits = 16)
{ {
@@ -282,23 +314,26 @@ namespace Ragon.Protocol
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Do not use this method, will be removed")] [Obsolete("Do not use this method, will be removed")]
public byte[] ReadBytes(int lenght) public byte[] ReadBytes(int len)
{ {
var data = new byte[lenght]; var data = new byte[len];
for (int i = 0; i < lenght; i++) for (int i = 0; i < len; i++)
data[i] = (byte)Read(8); data[i] = (byte)Read(8);
return data; return data;
} }
[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;
var limit = (size + 32 - 1) / 32; var limit = (size + 32 - 1) / 32;
var capacity = size; var capacity = size;
if (index + limit >= _buckets.Length)
Resize(size);
for (int i = 0; i < limit; i++) for (int i = 0; i < limit; i++)
{ {
var dataSize = capacity > 32 ? 32 : capacity; var dataSize = capacity > 32 ? 32 : capacity;
@@ -321,7 +356,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 +369,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 +389,18 @@ namespace Ragon.Protocol
_write = 0; _write = 0;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CopyFrom(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;
@@ -384,10 +433,13 @@ namespace Ragon.Protocol
_write = ((length - 1) * 8) + positionInByte; _write = ((length - 1) * 8) + positionInByte;
_read = 0; _read = 0;
_rawData = data;
} }
public byte[] ToArray() public byte[] ToArray()
{ {
Write(1, 1);
var data = new byte[Length]; var data = new byte[Length];
int bucketsCount = (_write >> 5) + 1; int bucketsCount = (_write >> 5) + 1;
int length = data.Length; int length = data.Length;
@@ -413,6 +465,32 @@ namespace Ragon.Protocol
return data; return data;
} }
public void ToArray(byte[] outData)
{
Write(1, 1);
var bucketsCount = (_write >> 5) + 1;
var length = Length;
for (int i = 0; i < bucketsCount; i++)
{
var dataIdx = i * 4;
var bucket = _buckets[i];
if (dataIdx < length)
outData[dataIdx] = (byte)bucket;
if (dataIdx + 1 < length)
outData[dataIdx + 1] = (byte)(bucket >> 8);
if (dataIdx + 2 < length)
outData[dataIdx + 2] = (byte)(bucket >> 16);
if (dataIdx + 3 < length)
outData[dataIdx + 3] = (byte)(bucket >> 24);
}
}
private void Resize(int capacity) private void Resize(int capacity)
{ {
var buckets = new uint[_buckets.Length * 2 + capacity]; var buckets = new uint[_buckets.Length * 2 + capacity];
@@ -0,0 +1,9 @@
namespace Ragon.Protocol
{
public enum RagonDisconnect
{
MANUAL,
TIMEOUT,
SERVER,
}
}
+20 -21
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,26 +17,25 @@
namespace Ragon.Protocol namespace Ragon.Protocol
{ {
public enum RagonOperation: byte public enum RagonOperation : byte
{ {
AUTHORIZE, AUTHORIZE = 1,
AUTHORIZED_SUCCESS, AUTHORIZED_SUCCESS = 2,
AUTHORIZED_FAILED, AUTHORIZED_FAILED = 3,
JOIN_OR_CREATE_ROOM, JOIN_OR_CREATE_ROOM = 4,
CREATE_ROOM, CREATE_ROOM = 5,
JOIN_ROOM, JOIN_ROOM = 6,
LEAVE_ROOM, LEAVE_ROOM = 7,
OWNERSHIP_CHANGED, OWNERSHIP_ROOM_CHANGED = 9,
JOIN_SUCCESS, JOIN_SUCCESS = 10,
JOIN_FAILED, JOIN_FAILED = 11,
LOAD_SCENE, PLAYER_JOINED = 14,
SCENE_LOADED, PLAYER_LEAVED = 15,
PLAYER_JOINED, REPLICATE_ROOM_EVENT = 22,
PLAYER_LEAVED, TRANSFER_ROOM_OWNERSHIP = 23,
CREATE_ENTITY, TIMESTAMP_SYNCHRONIZATION = 25,
DESTROY_ENTITY, ROOM_LIST_UPDATED = 26,
SNAPSHOT, PLAYER_DATA_UPDATED = 27,
REPLICATE_ENTITY_STATE, ROOM_DATA_UPDATED = 28,
REPLICATE_ENTITY_EVENT,
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
+8 -11
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,24 +17,21 @@
namespace Ragon.Protocol namespace Ragon.Protocol
{ {
public class RagonRoomParameters: IRagonSerializable public class RagonRoomParameters
{ {
public string Map { 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(RagonStream buffer)
{ {
buffer.WriteString(Map); buffer.WriteInt(Min);
buffer.WriteInt(Min, 1, 32); buffer.WriteInt(Max);
buffer.WriteInt(Max, 1, 32);
} }
public void Deserialize(RagonBuffer buffer) public void Deserialize(RagonStream buffer)
{ {
Map = buffer.ReadString(); Min = buffer.ReadInt();
Min = buffer.ReadInt(1, 32); Max = buffer.ReadInt();
Max = buffer.ReadInt(1, 32);
} }
} }
} }
+3 -3
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@ namespace Ragon.Protocol
{ {
public interface IRagonSerializable public interface IRagonSerializable
{ {
public void Serialize(RagonBuffer buffer); public void Serialize(RagonStream buffer);
public void Deserialize(RagonBuffer buffer); public void Deserialize(RagonStream buffer);
} }
} }
+309
View File
@@ -0,0 +1,309 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Ragon.Protocol
{
[StructLayout(LayoutKind.Explicit)]
internal struct ValueConverter
{
[FieldOffset(0)] public int Int;
[FieldOffset(0)] public float Float;
[FieldOffset(0)] public long Long;
[FieldOffset(0)] public byte Byte0;
[FieldOffset(1)] public byte Byte1;
[FieldOffset(2)] public byte Byte2;
[FieldOffset(3)] public byte Byte3;
[FieldOffset(4)] public byte Byte4;
[FieldOffset(5)] public byte Byte5;
[FieldOffset(6)] public byte Byte6;
[FieldOffset(7)] public byte Byte7;
}
public class RagonStream
{
private byte[] _data;
private int _offset;
private int _size;
public int Lenght => _offset;
public int Size => _size - _offset;
public RagonStream(int capacity = 256)
{
_data = new byte[capacity];
_offset = 0;
_size = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset()
{
_size = _offset;
_offset = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddOffset(int offset)
{
_offset += offset;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteByte(byte value)
{
ResizeIfNeed(1);
_data[_offset] = value;
_offset += 1;
return 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte()
{
var value = _data[_offset];
_offset += 1;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteBool(bool value)
{
ResizeIfNeed(1);
_data[_offset] = value ? (byte)1 : (byte)0;
_offset += 1;
return 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ReadBool()
{
var value = _data[_offset];
_offset += 1;
return value == 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteInt(int value)
{
ResizeIfNeed(4);
var converter = new ValueConverter() { Int = value };
_data[_offset] = converter.Byte0;
_data[_offset + 1] = converter.Byte1;
_data[_offset + 2] = converter.Byte2;
_data[_offset + 3] = converter.Byte3;
_offset += 4;
return 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteInt(int value, int offset)
{
ResizeIfNeed(4);
var converter = new ValueConverter() { Int = value };
_data[offset] = converter.Byte0;
_data[offset + 1] = converter.Byte1;
_data[offset + 2] = converter.Byte2;
_data[offset + 3] = converter.Byte3;
return 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt()
{
var converter = new ValueConverter
{ Byte0 = _data[_offset], Byte1 = _data[_offset + 1], Byte2 = _data[_offset + 2], Byte3 = _data[_offset + 3] };
_offset += 4;
return converter.Int;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLong(long value)
{
ResizeIfNeed(8);
WriteLong(value, _offset);
_offset += 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteLong(long value, int offset)
{
var converter = new ValueConverter() { Long = value };
_data[offset] = converter.Byte0;
_data[offset + 1] = converter.Byte1;
_data[offset + 2] = converter.Byte2;
_data[offset + 3] = converter.Byte3;
_data[offset + 4] = converter.Byte4;
_data[offset + 5] = converter.Byte5;
_data[offset + 6] = converter.Byte6;
_data[offset + 7] = converter.Byte7;
return 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadLong()
{
var converter = new ValueConverter
{
Byte0 = _data[_offset],
Byte1 = _data[_offset + 1],
Byte2 = _data[_offset + 2],
Byte3 = _data[_offset + 3],
Byte4 = _data[_offset + 4],
Byte5 = _data[_offset + 5],
Byte6 = _data[_offset + 6],
Byte7 = _data[_offset + 7],
};
_offset += 8;
return converter.Long;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteFloat(float value)
{
var converter = new ValueConverter() { Float = value };
WriteInt(converter.Int);
return 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float ReadFloat()
{
var rawValue = ReadInt();
var converter = new ValueConverter() { Int = rawValue };
var value = converter.Float;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteString(string value)
{
var rawData = Encoding.UTF8.GetBytes(value);
var len = rawData.Length;
ResizeIfNeed(2 + len);
WriteUShort((ushort)len);
Buffer.BlockCopy(rawData, 0, _data, _offset, len);
_offset += len;
return len + 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString()
{
var len = ReadUShort();
var rawData = new byte[len];
Buffer.BlockCopy(_data, _offset, rawData, 0, len);
var str = Encoding.UTF8.GetString(rawData);
_offset += len;
return str;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte[] ReadBinary(int len)
{
if (len >= _data.Length)
return Array.Empty<byte>();
var payload = new byte[len];
Buffer.BlockCopy(_data, _offset, payload, 0, len);
_offset += len;
return payload;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBinary(byte[] payload)
{
ResizeIfNeed(payload.Length);
Array.Copy(payload, 0, _data, _offset, payload.Length);
_offset += payload.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteOperation(RagonOperation operation)
{
ResizeIfNeed(1);
_data[_offset] = (byte)operation;
_offset += 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RagonOperation ReadOperation()
{
var op = (RagonOperation)_data[_offset];
_offset += 1;
return op;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUShort(ushort value)
{
ResizeIfNeed(2);
_data[_offset] = (byte)(value & 0x00FF);
_data[_offset + 1] = (byte)((value & 0xFF00) >> 8);
_offset += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUShort(ushort value, int offset)
{
ResizeIfNeed(2);
_data[offset] = (byte)(value & 0x00FF);
_data[offset + 1] = (byte)((value & 0xFF00) >> 8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort ReadUShort()
{
var value = (ushort)(_data[_offset] + (_data[_offset + 1] << 8));
_offset += 2;
return value;
}
public void Clear()
{
_offset = 0;
_size = 0;
}
public void FromArray(byte[] data)
{
Clear();
ResizeIfNeed(data.Length);
Buffer.BlockCopy(data, 0, _data, 0, data.Length);
_size = data.Length;
}
public byte[] ToArray()
{
var bytes = new byte[_offset];
Buffer.BlockCopy(_data, 0, bytes, 0, _offset);
return bytes;
}
private void ResizeIfNeed(int lenght)
{
if (_offset + lenght < _data.Length)
return;
var newData = new byte[_data.Length * 4 + lenght];
Buffer.BlockCopy(_data, 0, newData, 0, _data.Length);
_data = newData;
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
+26 -1
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,9 +16,34 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ragon.Protocol namespace Ragon.Protocol
{ {
[StructLayout(LayoutKind.Explicit)]
public struct DoubleToUInt
{
[FieldOffset(0)]
public double Double;
[FieldOffset(0)]
public uint Int0;
[FieldOffset(4)]
public uint Int1;
}
public static class RagonTime {
public static double CurrentTimestamp()
{
var currentTime = System.DateTime.UtcNow.ToUniversalTime().Subtract(
new System.DateTime(1970, 1, 1, 0, 0, 0, System.DateTimeKind.Utc)
).TotalMilliseconds;
return currentTime;
}
}
public static class DeBruijn public static class DeBruijn
{ {
private static readonly int[] _lookup = new int[32] private static readonly int[] _lookup = new int[32]
+2 -2
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -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
@@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com> * Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
+9 -3
View File
@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>Ragon.Relay</RootNamespace> <RootNamespace>Ragon.Relay</RootNamespace>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@@ -23,9 +23,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ragon.Server.ENet\Ragon.Server.ENet.csproj" />
<ProjectReference Include="..\Ragon.Server.DotNetWebSockets\Ragon.Server.DotNetWebSockets.csproj" />
<ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" /> <ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="ENet-CSharp" Version="2.4.8" />
<PackageReference Include="Google.Protobuf" Version="3.29.0" />
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.3.2" />
</ItemGroup>
</Project> </Project>
-53
View File
@@ -1,53 +0,0 @@
/*
* 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 Ragon.Server;
using Ragon.Server.ENet;
using Ragon.Server.DotNetWebsockets;
namespace Ragon.Relay;
public class Relay
{
public void Start()
{
var logger = LogManager.GetLogger("Ragon.Relay");
logger.Info("Relay Application");
var configuration = Configuration.Load("relay.config.json");
var serverType = Configuration.GetServerType(configuration.ServerType);
INetworkServer server = null;
switch (serverType)
{
case ServerType.ENET:
server = new ENetServer();
break;
case ServerType.WEBSOCKET:
server = new DotNetWebSocketServer();
break;
default:
server = new ENetServer();
break;
}
var relay = new RagonServer(server, configuration);
logger.Info("Started");
relay.Start();
}
}
+40
View File
@@ -0,0 +1,40 @@
using System;
using NLog;
using Ragon.Server.Logging;
namespace Ragon.Relay;
public class RelayLogger: IRagonLogger
{
private Logger _nlogger;
public RelayLogger(string tag)
{
_nlogger = LogManager.GetLogger(tag);
}
public void Warning(string message)
{
_nlogger.Warn(message);
}
public void Info(string message)
{
_nlogger.Info(message);
}
public void Error(string message)
{
_nlogger.Error(message);
}
public void Error(Exception ex)
{
_nlogger.Error(ex);
}
public void Trace(string message)
{
_nlogger.Trace(message);
}
}
@@ -0,0 +1,11 @@
using Ragon.Server.Logging;
namespace Ragon.Relay;
public class RelayLoggerFactory: IRagonLoggerFactory
{
public IRagonLogger GetLogger(string tag)
{
return new RelayLogger(tag);
}
}
@@ -0,0 +1,23 @@
using System;
using Ragon.Server.Plugin;
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");
}
}
@@ -0,0 +1,43 @@
using Ragon.Server;
using Ragon.Server.Lobby;
using Ragon.Server.Plugin;
using Ragon.Server.Time;
namespace Ragon.Relay
{
public class RelayServerPlugin : BaseServerPlugin
{
private RelayConfiguration _relayConfiguration;
private RagonScheduler _scheduler;
private RagonConnectionRegistry _connectionRegistry;
private IRagonLobby _lobby;
private Reporter _reporter;
public RelayServerPlugin(RelayConfiguration config)
{
_relayConfiguration = config;
}
public override void OnAttached(IRagonServer server)
{
base.OnAttached(server);
_lobby = server.Lobby;
_connectionRegistry = server.ConnectionRegistry;
_scheduler = server.Scheduler;
_reporter = new Reporter(_relayConfiguration, server, "127.0.0.1", 5000);
server.Scheduler.Run(new RagonActionTimer(() => _reporter.Done(), 1, -1));
}
public override bool OnCommand(string command, string payload)
{
return true;
}
public override IRoomPlugin CreateRoomPlugin(RoomInformation information)
{
return new RelayRoomPlugin();
}
}
}
+81
View File
@@ -0,0 +1,81 @@
/*
* Copyright 2023-2024 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.IO;
using System.Threading;
using Newtonsoft.Json;
using Ragon.Server;
using Ragon.Server.IO;
using Ragon.Server.Logging;
using Ragon.Server.Plugin;
using Ragon.Server.WebSocketServer;
using Ragon.Transport;
namespace Ragon.Relay
{
public class Relay
{
public void Start()
{
LoggerManager.SetLoggerFactory(new RelayLoggerFactory());
var logger = LoggerManager.GetLogger("Relay");
logger.Info("Relay Application");
var data = File.ReadAllText("relay.config.json");
var configuration = JsonConvert.DeserializeObject<RelayConfiguration>(data);
var serverType = RagonServerConfiguration.GetServerType(configuration.ServerType);
INetworkServer networkServer = new ENetServer();
IServerPlugin plugin = new RelayServerPlugin();
switch (serverType)
{
case ServerType.ENET:
networkServer = new ENetServer();
break;
case ServerType.WEBSOCKET:
networkServer = new WebSocketServer();
break;
}
var serverConfiguration = new RagonServerConfiguration()
{
LimitConnections = configuration.LimitConnections,
LimitRooms = configuration.LimitRooms,
LimitBufferedEvents = configuration.LimitBufferedEvents,
LimitPlayersPerRoom = configuration.LimitPlayersPerRoom,
LimitUserDataSize = configuration.LimitUserDataSize,
LimitPropertySize = configuration.LimitPropertySize,
Port = configuration.Port,
Protocol = configuration.Protocol,
ServerKey = configuration.ServerKey,
ServerTickRate = configuration.ServerTickRate,
ServerAddress = configuration.ServerAddress,
};
var relay = new RagonServer(networkServer, plugin, serverConfiguration);
relay.Listen();
while (relay.IsRunning)
{
relay.Tick();
Thread.Sleep(1);
}
relay.Dispose();
}
}
}

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