Compare commits

..

77 Commits

Author SHA1 Message Date
edmand46 efebf4ceda 🐛 scene entities 2023-04-14 17:42:27 +04:00
edmand46 17d1b7307d ♻️ plugin api 2023-04-14 14:32:24 +04:00
edmand46 fc28f512ba ♻️ plugin api 2023-04-14 14:32:04 +04:00
edmand46 6c4a51534a Merge pull request #12 from edmand46/develop
Plugins
2023-04-13 20:49:31 +04:00
edmand46 c91551ae08 📝 update headers 2023-04-13 20:49:00 +04:00
edmand46 e1a9ad476c http-commands 2023-04-13 20:42:05 +04:00
edmand46 24c9aa2043 🎨 namespaces 2023-04-09 11:06:52 +04:00
edmand46 bfd6c1b54b 🚧 plugin system, webhook system 2023-04-09 10:52:18 +04:00
edmand46 f2edc94958 chore: move sources to Sources folder 2023-04-05 18:53:21 +04:00
edmand46 b8dfc4cf41 chore: removed unused project 2023-04-05 18:48:37 +04:00
edmand46 bd7713bfcb fixed: cache sending on disconnecting 2023-03-31 12:56:23 +04:00
edmand46 043523d712 fixed: checking owner 2023-03-25 20:52:36 +04:00
edmand46 0dc5307b92 fixed: tickrate 2023-03-23 19:17:54 +04:00
edmand46 7b581b9afe chore: added tickrate logging 2023-03-23 18:07:48 +04:00
edmand46 8c5e063ef0 fixed: initial dirty property not tracked 2023-03-23 14:05:03 +04:00
edmand46 1a5f72a815 fixed: property update 2023-03-23 05:28:47 +04:00
edmand46 828da0d3da fix: wrong initial capacity 2023-03-22 11:04:14 +04:00
edmand46 951174e491 feat: split interface IRagonListener 2023-03-07 13:12:48 +04:00
edmand46 10b85867af feat: split interface IRagonListener 2023-03-07 00:57:13 +04:00
edmand46 252a46a713 chore: naming 2023-03-06 10:46:02 +04:00
edmand46 273c167c87 chore: naming 2023-03-06 10:31:42 +04:00
edmand46 192fb9e8eb chore: grammarly mistake 2023-03-06 10:29:22 +04:00
edmand46 a8ddc40268 Merge pull request #11 from edmand46/next
Update readme
2023-03-06 10:23:13 +04:00
edmand46 1ae545b353 chore: update readme.md 2023-03-06 10:22:27 +04:00
edmand46 68cd246641 fix: ci/cd 2023-03-06 10:18:47 +04:00
edmand46 b2058d21ce Merge pull request #10 from edmand46/next
Major update
2023-03-06 10:11:32 +04:00
edmand46 cbda5e9974 major update 2023-03-06 10:06:43 +04:00
edmand46 e84511e1ae chore: update link to docs 2022-12-25 09:53:05 -08:00
edmand46 b793ca3e68 chore: update default socket typoe 2022-12-25 07:34:22 -08:00
edmand46 3936e5c95b chore: update version 2022-12-25 03:16:59 -08:00
edmand46 e9418f4b22 fixed: websocket server wrong execution thread 2022-12-25 03:13:01 -08:00
edmand46 f34b05e6ff chore: update version libs 2022-12-24 01:47:49 -08:00
edmand46 a5a67963be feat: websockets 2022-12-20 12:20:52 -08:00
edmand46 ab85578ccf wip 2022-12-17 21:16:02 +04:00
edmand46 13044357a5 refactor: remove some warnings 2022-12-17 18:58:37 +04:00
edmand46 a9be230960 Merge branch 'develop'
# Conflicts:
#	Ragon/Sources/Application.cs
#	Ragon/Sources/Authorization.cs
#	Ragon/Sources/Configuration.cs
#	Ragon/Sources/Entity/Entity.cs
#	Ragon/Sources/GameRoom.cs
#	Ragon/Sources/IApplicationHandler.cs
#	Ragon/Sources/RoomManager.cs
#	Ragon/Sources/Server/ENet/ENetServer.cs
#	Ragon/Sources/Server/Http/WebSocketServer.cs
2022-12-17 14:08:28 +04:00
edmand46 5a4bf0c24e refactor: entity structure changes 2022-12-17 14:05:53 +04:00
edmand46 fa6ace4dc8 wip 2022-12-16 23:36:51 +04:00
edmand46 4d8ed1105a wip 2022-12-16 00:05:46 +04:00
edmand46 e2ef761bd7 fixed: windows server 2022-12-15 23:55:59 +04:00
edmand46 828112855f fixed: crash on send in websocket server 2022-12-04 23:01:05 +04:00
edmand46 06ff76fe0b fixed: second migration 2022-12-01 22:24:03 +04:00
edmand46 c92b5a5bc4 chore: update readme 2022-11-30 23:56:17 +04:00
edmand46 f83d3ea0c7 chore: update version 2022-11-13 18:54:23 +04:00
edmand46 3564eb2adc feat: added except invoker target 2022-11-13 18:53:56 +04:00
edmand46 3531432758 fixed: crash on concurrent authorization 2022-11-13 00:20:41 +04:00
edmand46 6bda468607 v1.0.23-rc 2022-10-24 22:19:50 +04:00
edmand46 7ddd52bf9d feat: added websocket support 2022-10-23 18:50:05 +04:00
edmand46 ecf3631163 Merge branch 'retry'
# Conflicts:
#	Ragon/Sources/Game/GameRoom.cs
2022-10-23 18:49:32 +04:00
edmand46 8a149eb3b4 fixed: added catch error websocket 2022-10-23 18:45:37 +04:00
edmand46 14ae5e8189 fixed: temporary scheduler 2022-10-22 22:58:15 +04:00
edmand46 b7e8327ca8 feat: websocket 2022-10-22 21:34:35 +04:00
edmand46 b9e79af9d8 fixed: player joined opcode 2022-10-21 22:19:21 +04:00
edmand46 545ec02ecc chore: update version 2022-10-21 00:05:01 +04:00
edmand46 73feb77169 fixed: wrong peerId in restored events 2022-10-21 00:04:33 +04:00
edmand46 cbcf1773aa wip 2022-10-16 18:27:40 +04:00
edmand46 5519ae7679 wip 2022-10-16 18:00:32 +04:00
edmand46 1558b5eefb wip 2022-10-16 17:14:32 +04:00
edmand46 aaa0e4a317 wip 2022-10-16 16:50:22 +04:00
edmand46 ff712dc094 wip 2022-10-16 16:22:27 +04:00
edmand46 c2c75cb513 fixed: double authorization request 2022-10-15 18:48:59 +04:00
edmand46 9a22566f79 fixed: additional data missed 2022-10-15 18:48:48 +04:00
edmand46 783d2ce922 chore: bump version 2022-10-02 16:13:53 +04:00
edmand46 5771ec738b fixed: ownership changing 2022-10-02 16:00:34 +04:00
edmand46 85081e1da7 fix: replication entity event 2022-09-11 15:05:32 +04:00
edmand46 dbaa5d9d92 chore: renaming enums 2022-09-10 10:25:45 +04:00
edmand46 54786c065d wip 2022-09-06 22:39:52 +04:00
edmand46 0b3a0dd1ac refactor: spawning entities with properties 2022-09-04 14:43:17 +04:00
edmand46 72ff37e94a bump version 2022-08-30 01:54:50 +04:00
edmand46 75dab9d16f fixed: on scene entities load 2022-08-30 01:53:47 +04:00
edmand46 25b0e54d13 fixed: changed flow of joining 2022-08-29 01:45:47 +04:00
edmand46 e4f664b557 refactor: renamed plugin api 2022-08-28 19:31:07 +04:00
edmand46 174a4c4422 bump version 2022-08-28 11:51:14 +04:00
edmand46 a51d7c73cd fixed: mistype 2022-08-28 11:46:30 +04:00
edmand46 957622a170 fix: on player leave logic 2022-08-28 00:18:26 +04:00
edmand46 4a07424293 feat: added checks for size of payload 2022-08-27 11:21:51 +04:00
edmand46 568a3282c3 fix: restoring properties 2022-08-27 10:51:57 +04:00
205 changed files with 9158 additions and 2721 deletions
+6 -6
View File
@@ -19,7 +19,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tag_name: ${{ github.ref }} tag_name: ${{ github.ref }}
release_name: Ragon.SimpleServer-${{ github.ref }} release_name: Ragon.Relay-${{ github.ref }}
prerelease: true prerelease: true
build: build:
@@ -49,14 +49,14 @@ jobs:
- name: Setup dotnet - name: Setup dotnet
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: 6.0.x dotnet-version: 7.0.x
- name: Build - name: Build
shell: bash shell: bash
run: | run: |
release_name="Ragon.SimpleServer-${{ env.TAG }}-${{ matrix.target }}" release_name="Ragon.Relay-${{ env.TAG }}-${{ matrix.target }}"
# Build everything # Build everything
dotnet publish Ragon.SimpleServer/Ragon.SimpleServer.csproj -c Release --runtime "${{ matrix.target }}" -p:PublishSingleFile=true -o "$release_name" dotnet publish Ragon.Relay/Ragon.Relay.csproj -c Release --runtime "${{ matrix.target }}" -p:PublishSingleFile=true -o "$release_name"
# Pack files # Pack files
7z a -tzip "${release_name}.zip" "./${release_name}/*" 7z a -tzip "${release_name}.zip" "./${release_name}/*"
@@ -69,6 +69,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ needs.release.outputs.upload_url }} upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: Ragon.SimpleServer-${{ env.TAG }}-${{ matrix.target }}.zip asset_path: Ragon.Relay-${{ env.TAG }}-${{ matrix.target }}.zip
asset_name: Ragon.SimpleServer-${{ env.TAG }}-${{ matrix.target }}.zip asset_name: Ragon.Relay-${{ env.TAG }}-${{ matrix.target }}.zip
asset_content_type: application/zip asset_content_type: application/zip
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.
+6 -11
View File
@@ -6,18 +6,16 @@
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://ragon-server.com/docs/category/basics">Documentation</a> <a href="https://www.ragon-server.com/docs/installation">Documentation</a>
<br>
<a href="https://ragon-server.com/docs/get-started">Get started</a>
### Features: ### Features:
- Effective - Effective
- Free - Free
- Simple matchmaking - Lobby
- Room based architecture - Room based architecture
- Сustomizable authorization - Сustomizable authorization
- Сustomizable server-side logic via plugins with flexible API - Сustomizable server-side logic via plugins with flexible API*(2)
- No CCU limitations* - No CCU limitations*(1)
- No Room count limitations - No Room count limitations
- Reliable UDP - Reliable UDP
@@ -27,10 +25,7 @@ Ragon is fully free, small and high perfomance room based game server with plugi
### Dependencies ### Dependencies
* ENet-Sharp [v2.4.8] * ENet-Sharp [v2.4.8]
* NetStack [latest]
### License
MIT
### Tips ### Tips
\* Limited to 4095 CCU by library ENet-Sharp \* Limited to 4095 CCU by library ENet-Sharp (1)
\* Non finally (2)
@@ -0,0 +1,13 @@
<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>
@@ -0,0 +1,60 @@
/*
* 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
@@ -0,0 +1,104 @@
/*
* 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
@@ -0,0 +1,87 @@
/*
* 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();
}
}
}
@@ -0,0 +1,68 @@
/*
* 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
@@ -0,0 +1,21 @@
/*
* 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();
@@ -0,0 +1,26 @@
<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
@@ -0,0 +1,110 @@
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");
}
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;
}
}
}
@@ -0,0 +1,130 @@
/*
* 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();
}
}
}
@@ -0,0 +1,40 @@
/*
* 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 Ragon.Protocol;
namespace Ragon.Client;
public sealed class ENetReliableChannel : INetworkChannel
{
private Peer _peer;
private byte _channelId;
public ENetReliableChannel(Peer peer, int channelId)
{
_peer = peer;
_channelId = (byte) channelId;
}
public void Send(byte[] data)
{
var newPacket = new Packet();
newPacket.Create(data, data.Length, PacketFlags.Reliable);
_peer.Send(_channelId, ref newPacket);
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using 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);
}
}
@@ -0,0 +1,131 @@
/*
* 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();
}
}
}
@@ -0,0 +1,38 @@
/*
* 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 Ragon.Protocol;
namespace Ragon.Client;
public sealed class NullReliableChannel : INetworkChannel
{
private Peer _peer;
private byte _channelId;
public NullReliableChannel(Peer peer, int channelId)
{
_peer = peer;
_channelId = (byte) channelId;
}
public void Send(byte[] data)
{
}
}
@@ -0,0 +1,38 @@
/*
* 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 Ragon.Protocol;
namespace Ragon.Client;
public sealed class NullUnreliableChannel : INetworkChannel
{
private Peer _peer;
private byte _channelId;
public NullUnreliableChannel(Peer peer, int channelId)
{
_peer = peer;
_channelId = (byte) channelId;
}
public void Send(byte[] data)
{
}
}
@@ -0,0 +1,35 @@
/*
* 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.Sources;
public class PlayerPayload : IRagonPayload
{
public uint Name { get; set; }
public void Serialize(RagonBuffer buffer)
{
buffer.Write(Name, 16);
}
public void Deserialize(RagonBuffer buffer)
{
Name = buffer.Read(16);
}
}
@@ -0,0 +1,42 @@
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();
}
}
+26
View File
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>10</LangVersion>
<RootNamespace>Ragon.Client.Simulation</RootNamespace>
<Authors>Eduard Kargin (Edmand46)</Authors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>none</DebugType>
<OutputPath>/Users/edmand46/RagonProjects/ragon-oss-sdk/Assets/Ragon/Plugins/</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>/Users/edmand46/RagonProjects/ragon-unity-sdk/Assets/Ragon/Runtime/Plugins</OutputPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ragon.Protocol\Ragon.Protocol.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,57 @@
/*
* 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;
}
}
@@ -0,0 +1,44 @@
/*
* 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
@@ -0,0 +1,218 @@
/*
* 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);
}
}
}
@@ -0,0 +1,77 @@
/*
* 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);
}
}
}
}
@@ -0,0 +1,51 @@
/*
* 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}";
}
}
@@ -0,0 +1,124 @@
/*
* 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)
{
}
}
}
@@ -0,0 +1,35 @@
/*
* 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 AuthorizeFailedHandler: Handler
{
private readonly RagonListenerList _listenerList;
public AuthorizeFailedHandler(RagonListenerList list)
{
_listenerList = list;
}
public void Handle(RagonBuffer buffer)
{
var message = buffer.ReadString();
_listenerList.OnAuthorizationFailed(message);
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
internal class AuthorizeSuccessHandler: Handler
{
private readonly RagonListenerList _listenerList;
public AuthorizeSuccessHandler(RagonListenerList listenerList)
{
_listenerList = listenerList;
}
public void Handle(RagonBuffer buffer)
{
var playerId = buffer.ReadString();
var playerName = buffer.ReadString();
var playerPayload = buffer.ReadString();
_listenerList.OnAuthorizationSuccess(playerId, playerName, playerPayload);
}
}
@@ -0,0 +1,58 @@
/*
* 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);
}
}
@@ -0,0 +1,57 @@
/*
* 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);
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
internal class EntityRemoveHandler: Handler
{
private readonly RagonEntityCache _entityCache;
public EntityRemoveHandler(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);
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
internal class StateEntityHandler: Handler
{
private readonly RagonEntityCache _entityCache;
public StateEntityHandler(RagonEntityCache entityCache)
{
_entityCache = entityCache;
}
public void Handle(RagonBuffer buffer)
{
var entitiesCount = buffer.ReadUShort();
for (var i = 0; i < entitiesCount; i++)
{
var entityId = buffer.ReadUShort();
_entityCache.OnState(entityId, buffer);
}
}
}
+25
View File
@@ -0,0 +1,25 @@
/*
* 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 interface Handler
{
public void Handle(RagonBuffer buffer);
}
@@ -0,0 +1,36 @@
/*
* 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 JoinFailedHandler: Handler
{
private readonly RagonListenerList _listenerList;
public JoinFailedHandler(RagonListenerList listenerList)
{
_listenerList = listenerList;
}
public void Handle(RagonBuffer buffer)
{
var message = buffer.ReadString();
_listenerList.OnFailed(message);
}
}
@@ -0,0 +1,80 @@
/*
* 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 RagonRoomInformation
{
public RagonRoomInformation(string roomId, string playerId, string ownerId, ushort min, ushort max)
{
RoomId = roomId;
PlayerId = playerId;
OwnerId = ownerId;
Min = min;
Max = max;
}
public string RoomId { get; private set; }
public string PlayerId { get; private set; }
public string OwnerId { get; private set; }
public ushort Min { get; private set; }
public ushort Max { get; private set; }
}
internal class JoinSuccessHandler : Handler
{
private readonly RagonListenerList _listenerList;
private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache;
private readonly RagonClient _client;
private readonly RagonBuffer _buffer;
public JoinSuccessHandler(
RagonClient client,
RagonBuffer buffer,
RagonListenerList listenerList,
RagonPlayerCache playerCache,
RagonEntityCache entityCache
)
{
_buffer = buffer;
_client = client;
_listenerList = listenerList;
_entityCache = entityCache;
_playerCache = playerCache;
}
public void Handle(RagonBuffer buffer)
{
var roomId = buffer.ReadString();
var localId = buffer.ReadString();
var ownerId = buffer.ReadString();
var min = buffer.ReadUShort();
var max = buffer.ReadUShort();
var map = buffer.ReadString();
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);
_client.AssignRoom(room);
_listenerList.OnLevel(map);
}
}
@@ -0,0 +1,44 @@
/*
* 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 LeaveRoomHandler : Handler
{
private readonly RagonClient _client;
private readonly RagonListenerList _listenerList;
private readonly RagonEntityCache _entityCache;
public LeaveRoomHandler(
RagonClient client,
RagonListenerList listenerList,
RagonEntityCache entityCache)
{
_client = client;
_listenerList = listenerList;
_entityCache = entityCache;
}
public void Handle(RagonBuffer buffer)
{
_listenerList.OnLeft();
_entityCache.Cleanup();
_client.Room.Cleanup();
}
}
@@ -0,0 +1,44 @@
/*
* 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);
}
}
@@ -0,0 +1,53 @@
/*
* 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 OwnershipHandler: Handler
{
private readonly RagonListenerList _listenerList;
private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache;
public OwnershipHandler(
RagonListenerList listenerList,
RagonPlayerCache playerCache,
RagonEntityCache entityCache)
{
_listenerList = listenerList;
_playerCache = playerCache;
_entityCache = entityCache;
}
public void Handle(RagonBuffer buffer)
{
var newOwnerId = buffer.ReadString();
var player = _playerCache.GetPlayerById(newOwnerId);
_playerCache.OnOwnershipChanged(newOwnerId);
_listenerList.OnOwnershipChanged(player);
var entities = buffer.ReadUShort();
for (var i = 0; i < entities; i++)
{
var entityId = buffer.ReadUShort();
_entityCache.OnOwnershipChanged(player, entityId);
}
}
}
@@ -0,0 +1,50 @@
/*
* 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 PlayerJoinHandler : Handler
{
private RagonPlayerCache _playerCache;
private RagonListenerList _listenerList;
public PlayerJoinHandler(
RagonPlayerCache playerCache,
RagonListenerList listenerList
)
{
_playerCache = playerCache;
_listenerList = listenerList;
}
public void Handle(RagonBuffer buffer)
{
var playerPeerId = buffer.ReadUShort();
var playerId = buffer.ReadString();
var playerName = buffer.ReadString();
_playerCache.AddPlayer(playerPeerId, playerId, playerName);
var player = _playerCache.GetPlayerById(playerId);
if (player != null)
_listenerList.OnPlayerJoined(player);
else
RagonLog.Trace($"[Joined] {playerId}");
}
}
@@ -0,0 +1,60 @@
/*
* 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 PlayerLeftHandler : Handler
{
private RagonPlayerCache _playerCache;
private RagonEntityCache _entityCache;
private RagonListenerList _listenerList;
public PlayerLeftHandler(
RagonEntityCache entityCache,
RagonPlayerCache playerCache,
RagonListenerList listenerList
)
{
_entityCache = entityCache;
_playerCache = playerCache;
_listenerList = listenerList;
}
public void Handle(RagonBuffer buffer)
{
var playerId = buffer.ReadString();
var player = _playerCache.GetPlayerById(playerId);
if (player != null)
{
_playerCache.RemovePlayer(playerId);
_listenerList.OnPlayerLeft(player);
var entities = buffer.ReadUShort();
var toDeleteIds = new ushort[entities];
for (var i = 0; i < entities; i++)
{
var entityId = buffer.ReadUShort();
toDeleteIds[i] = entityId;
}
foreach (var id in toDeleteIds)
_entityCache.OnDestroy(id, new RagonPayload());
}
}
}
@@ -0,0 +1,97 @@
/*
* 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,23 @@
/*
* 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 enum DisconnectReason
{
MANUAL,
TIMEOUT,
}
@@ -0,0 +1,22 @@
/*
* 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 INetworkChannel
{
void Send(byte[] data);
}
@@ -0,0 +1,35 @@
/*
* 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 INetworkConnection: IRagonConnection
{
public INetworkChannel Reliable { get; }
public INetworkChannel Unreliable { get; }
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; }
public void Prepare();
public void Connect(string address, ushort port, uint protocol);
public void Disconnect();
public void Update();
public void Dispose();
}
@@ -0,0 +1,62 @@
/*
* 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 class NetworkStatistics
{
private const double Interval = 1.0d;
private double _upstreamBandwidth = 0d;
private double _downstreamBandwidth = 0d;
private double _time = 0d;
private ulong _upstreamData = 0;
private ulong _downstreamData = 0;
private ulong _sent = 0;
private ulong _received = 0;
private int _ping;
public int Ping => _ping;
public double UpstreamBandwidth => _upstreamBandwidth;
public double DownstreamBandwidth => _downstreamBandwidth;
public void Update(ulong sent, ulong received, int ping, float dt)
{
_sent = sent;
_received = received;
_ping = ping;
_time += dt;
if (_time >= Interval)
{
if (_upstreamData > 0)
{
_upstreamData = _sent - _upstreamData;
_upstreamBandwidth = (_upstreamData / _time) / 1000 ;
}
if (_downstreamData > 0)
{
_downstreamData = _received - _downstreamData;
_downstreamBandwidth = (_downstreamData / _time) / 1000;
}
_upstreamData = _sent;
_downstreamData = _received;
_time -= Interval;
}
}
}
+22
View File
@@ -0,0 +1,22 @@
/*
* 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 IRagonConnection
{
}
@@ -0,0 +1,22 @@
/*
* 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 IRagonEntityListener
{
public void OnEntityCreated(RagonEntity entity);
}
+25
View File
@@ -0,0 +1,25 @@
/*
* 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 interface IRagonEvent: IRagonSerializable
{
}
}
+25
View File
@@ -0,0 +1,25 @@
/*
* 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 interface IRagonPayload: IRagonSerializable
{
}
}
@@ -0,0 +1,22 @@
/*
* 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 IRagonSceneCollector
{
public RagonEntity[] Collect();
}
@@ -0,0 +1,24 @@
/*
* 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 IRagonAuthorizationListener
{
void OnAuthorizationSuccess(RagonClient client, string playerId, string playerName);
void OnAuthorizationFailed(RagonClient client, string message);
}
@@ -0,0 +1,23 @@
/*
* 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 IRagonConnectionListener
{
void OnConnected(RagonClient client);
void OnDisconnected(RagonClient client);
}
@@ -0,0 +1,22 @@
/*
* 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 IRagonFailedListener
{
void OnFailed(RagonClient client, string message);
}
@@ -0,0 +1,22 @@
/*
* 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 IRagonJoinListener
{
void OnJoined(RagonClient client);
}
@@ -0,0 +1,22 @@
/*
* 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 IRagonLeftListener
{
void OnLeft(RagonClient client);
}
@@ -0,0 +1,22 @@
/*
* 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);
}
@@ -0,0 +1,31 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client
{
public interface IRagonListener :
IRagonAuthorizationListener,
IRagonConnectionListener,
IRagonFailedListener,
IRagonJoinListener,
IRagonLeftListener,
IRagonLevelListener,
IRagonOwnershipChangedListener,
IRagonPlayerJoinListener,
IRagonPlayerLeftListener
{
}
}
@@ -0,0 +1,22 @@
/*
* 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 IRagonOwnershipChangedListener
{
void OnOwnershipChanged(RagonClient client, RagonPlayer player);
}
@@ -0,0 +1,22 @@
/*
* 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 IRagonPlayerJoinListener
{
void OnPlayerJoined(RagonClient client, RagonPlayer player);
}
@@ -0,0 +1,22 @@
/*
* 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 IRagonPlayerLeftListener
{
void OnPlayerLeft(RagonClient client, RagonPlayer player);
}
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client
{
public interface IRagonLogger
{
public void Warn(string message);
public void Trace(string message);
public void Info(string message);
public void Error(string message);
}
}
@@ -0,0 +1,41 @@
/*
* 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 class RagonConsoleLogger: IRagonLogger
{
public void Warn(string message)
{
Console.WriteLine($"[WARN] {message}");
}
public void Trace(string message)
{
Console.WriteLine($"[TRACE] {message}");
}
public void Info(string message)
{
Console.WriteLine($"[INFO] {message}");
}
public void Error(string message)
{
Console.WriteLine($"[ERROR] {message}");
}
}
}
+29
View File
@@ -0,0 +1,29 @@
/*
* 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 class RagonLog
{
private static IRagonLogger _ragonLogger;
static RagonLog() => _ragonLogger = new RagonConsoleLogger();
public static void Set(IRagonLogger logger) => _ragonLogger = logger;
public static void Warn(string message) => _ragonLogger.Warn(message);
public static void Trace(string message) => _ragonLogger.Trace(message);
public static void Info(string message) => _ragonLogger.Info(message);
public static void Error(string message) => _ragonLogger.Error(message);
}
}
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client;
public enum RagonLogLevel
{
ALL,
CONNECTION,
STATE,
EVENT,
ROOM,
}
+203
View File
@@ -0,0 +1,203 @@
/*
* 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 RagonClient
{
private readonly INetworkConnection _connection;
private readonly IRagonEntityListener _entityListener;
private readonly IRagonSceneCollector _sceneCollector;
private Handler[] _handlers;
private RagonBuffer _readBuffer;
private RagonBuffer _writeBuffer;
private RagonRoom _room;
private RagonSession _session;
private RagonListenerList _listenerList;
private RagonPlayerCache _playerCache;
private RagonEntityCache _entityCache;
private RagonEventCache _eventCache;
private RagonStatus _status;
private NetworkStatistics _stats;
private float _replicationRate = 0;
private float _replicationTime = 0;
public IRagonConnection Connection => _connection;
public RagonStatus Status => _status;
public RagonSession Session => _session;
public RagonEventCache Event => _eventCache;
public RagonEntityCache Entity => _entityCache;
public NetworkStatistics Statistics => _stats;
public RagonRoom Room => _room;
internal RagonBuffer Buffer => _writeBuffer;
internal INetworkChannel Reliable => _connection.Reliable;
internal INetworkChannel Unreliable => _connection.Unreliable;
#region PUBLIC
public RagonClient(
INetworkConnection connection,
IRagonEntityListener entityListener,
IRagonSceneCollector sceneCollector,
int rate)
{
_listenerList = new RagonListenerList(this);
_entityListener = entityListener;
_sceneCollector = sceneCollector;
_connection = connection;
_connection.OnData += OnData;
_connection.OnConnected += OnConnected;
_connection.OnDisconnected += OnDisconnected;
_replicationRate = (1000.0f / rate) / 1000.0f;
_replicationTime = 0;
_eventCache = new RagonEventCache();
_stats = new NetworkStatistics();
_status = RagonStatus.DISCONNECTED;
}
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.REMOVE_ENTITY] = new EntityRemoveHandler(_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);
_connection.Connect(address, port, protocolRaw);
}
public void Disconnect()
{
_status = RagonStatus.DISCONNECTED;
_room.Cleanup();
_connection.Disconnect();
OnDisconnected(DisconnectReason.MANUAL);
}
public void Update(float dt)
{
if (_status != RagonStatus.DISCONNECTED)
{
_replicationTime += dt;
if (_replicationTime >= _replicationRate)
{
_entityCache.WriteState(_readBuffer);
_replicationTime = 0;
}
_stats.Update(_connection.BytesSent, _connection.BytesReceived, _connection.Ping, dt);
}
_connection.Update();
}
public void Dispose()
{
_status = RagonStatus.DISCONNECTED;
_connection.Disconnect();
_connection.Dispose();
}
public void AddListener(IRagonListener listener) => _listenerList.Add(listener);
public void AddListener(IRagonAuthorizationListener listener) => _listenerList.Add(listener);
public void AddListener(IRagonConnectionListener listener) => _listenerList.Add(listener);
public void AddListener(IRagonFailedListener listener) => _listenerList.Add(listener);
public void AddListener(IRagonJoinListener listener) => _listenerList.Add(listener);
public void AddListener(IRagonLeftListener listener) => _listenerList.Add(listener);
public void AddListener(IRagonLevelListener listener) => _listenerList.Add(listener);
public void AddListener(IRagonOwnershipChangedListener listener) => _listenerList.Add(listener);
public void AddListener(IRagonPlayerJoinListener listener) => _listenerList.Add(listener);
public void AddListener(IRagonPlayerLeftListener listener) => _listenerList.Add(listener);
public void RemoveListener(IRagonListener listener) => _listenerList.Remove(listener);
public void RemoveListener(IRagonAuthorizationListener listener) => _listenerList.Remove(listener);
public void RemoveListener(IRagonConnectionListener listener) => _listenerList.Remove(listener);
public void RemoveListener(IRagonFailedListener listener) => _listenerList.Remove(listener);
public void RemoveListener(IRagonJoinListener listener) => _listenerList.Remove(listener);
public void RemoveListener(IRagonLeftListener listener) => _listenerList.Remove(listener);
public void RemoveListener(IRagonLevelListener listener) => _listenerList.Remove(listener);
public void RemoveListener(IRagonOwnershipChangedListener listener) => _listenerList.Remove(listener);
public void RemoveListener(IRagonPlayerJoinListener listener) => _listenerList.Remove(listener);
public void RemoveListener(IRagonPlayerLeftListener listener) => _listenerList.Remove(listener);
#endregion
#region INTERNAL
internal void AssignRoom(RagonRoom room)
{
_room = room;
_status = RagonStatus.ROOM;
}
#endregion
#region PRIVATE
private void OnConnected()
{
RagonLog.Trace("Connected");
_listenerList.OnConnected();
_status = RagonStatus.CONNECTED;
}
private void OnDisconnected(DisconnectReason reason)
{
RagonLog.Trace($"Disconnected: {reason}");
_listenerList.OnDisconnected();
_status = RagonStatus.DISCONNECTED;
}
public void OnData(byte[] data)
{
_readBuffer.Clear();
_readBuffer.FromArray(data);
var operation = _readBuffer.ReadByte();
_handlers[operation].Handle(_readBuffer);
}
#endregion
}
}
+232
View File
@@ -0,0 +1,232 @@
/*
* 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.REMOVE_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!");
}
}
+71
View File
@@ -0,0 +1,71 @@
/*
* 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 class RagonEventCache
{
private readonly Dictionary<Type, ushort> _eventsRegistryByType = new();
private readonly HashSet<ushort> _codes = new();
private readonly HashSet<Type> _types = new();
private ushort _eventIdGenerator = 0;
public ushort GetEventCode<TEvent>(TEvent _) where TEvent : IRagonEvent
{
var type = typeof(TEvent);
var evntCode = _eventsRegistryByType[type];
return evntCode;
}
public void Register<T>() where T : IRagonEvent, new()
{
var type = typeof(T);
if (_types.Contains(type))
{
RagonLog.Trace($"[Ragon] Event already registered: {type.Name}");
return;
}
RagonLog.Trace($"[Ragon] Registered Event: {type.Name} - {_eventIdGenerator}");
_eventsRegistryByType.Add(type, _eventIdGenerator);
_codes.Add(_eventIdGenerator);
_types.Add(type);
_eventIdGenerator++;
}
public void Register<T>(ushort evntCode) where T : IRagonEvent, new()
{
var type = typeof(T);
if (_codes.Contains(evntCode) || _types.Contains(type))
{
RagonLog.Warn($"[Ragon] Event already registered: {type.Name} - {evntCode}");
return;
}
RagonLog.Trace($"[Ragon] Registered Event: {type.Name} - {evntCode}");
_codes.Add(evntCode);
_types.Add(type);
_eventsRegistryByType.Add(type, evntCode);
}
public T Create<T>() where T : IRagonEvent, new()
{
return new T();
}
}
+219
View File
@@ -0,0 +1,219 @@
/*
* 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
{
internal class RagonListenerList
{
private readonly RagonClient _client;
private readonly List<IRagonAuthorizationListener> _authorizationListeners = new();
private readonly List<IRagonConnectionListener> _connectionListeners = new();
private readonly List<IRagonFailedListener> _failedListeners = new();
private readonly List<IRagonJoinListener> _joinListeners = new();
private readonly List<IRagonLeftListener> _leftListeners = new();
private readonly List<IRagonLevelListener> _levelListeners = new();
private readonly List<IRagonOwnershipChangedListener> _ownershipChangedListeners = new();
private readonly List<IRagonPlayerJoinListener> _playerJoinListeners = new();
private readonly List<IRagonPlayerLeftListener> _playerLeftListeners = new();
public RagonListenerList(RagonClient client)
{
_client = client;
}
public void Add(IRagonListener listener)
{
_authorizationListeners.Add(listener);
_connectionListeners.Add(listener);
_failedListeners.Add(listener);
_joinListeners.Add(listener);
_leftListeners.Add(listener);
_levelListeners.Add(listener);
_ownershipChangedListeners.Add(listener);
_playerJoinListeners.Add(listener);
_playerLeftListeners.Add(listener);
}
public void Remove(IRagonListener listener)
{
_authorizationListeners.Remove(listener);
_connectionListeners.Remove(listener);
_failedListeners.Remove(listener);
_joinListeners.Remove(listener);
_leftListeners.Remove(listener);
_levelListeners.Remove(listener);
_ownershipChangedListeners.Remove(listener);
_playerJoinListeners.Remove(listener);
_playerLeftListeners.Remove(listener);
}
public void Add(IRagonAuthorizationListener listener)
{
_authorizationListeners.Add(listener);
}
public void Add(IRagonConnectionListener listener)
{
_connectionListeners.Add(listener);
}
public void Add(IRagonFailedListener listener)
{
_failedListeners.Add(listener);
}
public void Add(IRagonJoinListener listener)
{
_joinListeners.Add(listener);
}
public void Add(IRagonLeftListener listener)
{
_leftListeners.Add(listener);
}
public void Add(IRagonLevelListener listener)
{
_levelListeners.Add(listener);
}
public void Add(IRagonOwnershipChangedListener listener)
{
_ownershipChangedListeners.Add(listener);
}
public void Add(IRagonPlayerJoinListener listener)
{
_playerJoinListeners.Add(listener);
}
public void Add(IRagonPlayerLeftListener listener)
{
_playerLeftListeners.Add(listener);
}
public void Remove(IRagonAuthorizationListener listener)
{
_authorizationListeners.Remove(listener);
}
public void Remove(IRagonConnectionListener listener)
{
_connectionListeners.Remove(listener);
}
public void Remove(IRagonFailedListener listener)
{
_failedListeners.Remove(listener);
}
public void Remove(IRagonJoinListener listener)
{
_joinListeners.Remove(listener);
}
public void Remove(IRagonLeftListener listener)
{
_leftListeners.Remove(listener);
}
public void Remove(IRagonLevelListener listener)
{
_levelListeners.Remove(listener);
}
public void Remove(IRagonOwnershipChangedListener listener)
{
_ownershipChangedListeners.Remove(listener);
}
public void Remove(IRagonPlayerJoinListener listener)
{
_playerJoinListeners.Remove(listener);
}
public void Remove(IRagonPlayerLeftListener listener)
{
_playerLeftListeners.Remove(listener);
}
public void OnAuthorizationSuccess(string playerId, string playerName, string payload)
{
foreach (var listener in _authorizationListeners)
listener.OnAuthorizationSuccess(_client, playerId, playerName);
}
public void OnAuthorizationFailed(string message)
{
foreach (var listener in _authorizationListeners)
listener.OnAuthorizationFailed(_client, message);
}
public void OnLeft()
{
foreach (var listener in _leftListeners)
listener.OnLeft(_client);
}
public void OnFailed(string message)
{
foreach (var listener in _failedListeners)
listener.OnFailed(_client, message);
}
public void OnOwnershipChanged(RagonPlayer player)
{
foreach (var listener in _ownershipChangedListeners)
listener.OnOwnershipChanged(_client, player);
}
public void OnPlayerLeft(RagonPlayer player)
{
foreach (var listener in _playerLeftListeners)
listener.OnPlayerLeft(_client, player);
}
public void OnPlayerJoined(RagonPlayer player)
{
foreach (var listener in _playerJoinListeners)
listener.OnPlayerJoined(_client, player);
}
public void OnLevel(string sceneName)
{
foreach (var listener in _levelListeners)
listener.OnLevel(_client, sceneName);
}
public void OnJoined()
{
foreach (var listener in _joinListeners)
listener.OnJoined(_client);
}
public void OnConnected()
{
foreach (var listener in _connectionListeners)
listener.OnConnected(_client);
}
public void OnDisconnected()
{
foreach (var listener in _connectionListeners)
listener.OnDisconnected(_client);
}
}
}
+37
View File
@@ -0,0 +1,37 @@
/*
* 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
{
[Serializable]
public class RagonPlayer
{
public string Id { get; private set; }
public string Name { get; set; }
public ushort PeerId { get; set; }
public bool IsRoomOwner { get; set; }
public bool IsMe { get; set; }
public RagonPlayer(ushort peerId, string playerId, string name, bool isRoomOwner, bool isMe)
{
PeerId = peerId;
IsRoomOwner = isRoomOwner;
IsMe = isMe;
Name = name;
Id = playerId;
}
}
}
+90
View File
@@ -0,0 +1,90 @@
/*
* 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 sealed class RagonPlayerCache
{
private List<RagonPlayer> _players = new List<RagonPlayer>();
private Dictionary<string, RagonPlayer> _playersById = new();
private Dictionary<ushort, RagonPlayer> _playersByConnection = new();
public RagonPlayer Owner { get; private set; }
public RagonPlayer LocalPlayer { get; private set; }
public bool IsRoomOwner => _ownerId == _localId;
public RagonPlayer? GetPlayerById(string playerId) => _playersById[playerId];
public RagonPlayer? GetPlayerByPeer(ushort peerId) => _playersByConnection[peerId];
private string _ownerId;
private string _localId;
public void SetOwnerAndLocal(string ownerId, string localId)
{
_ownerId = ownerId;
_localId = localId;
}
public void AddPlayer(ushort peerId, string playerId, string playerName)
{
if (_playersById.ContainsKey(playerId))
return;
var isOwner = playerId == _ownerId;
var isLocal = playerId == _localId;
RagonLog.Trace($"Added player {peerId}|{playerId}|{playerName} IsOwner: {isOwner} isLocal: {isLocal}");
var player = new RagonPlayer(peerId, playerId, playerName, isOwner, isLocal);
if (player.IsMe)
LocalPlayer = player;
if (player.IsRoomOwner)
Owner = player;
_players.Add(player);
_playersById.Add(player.Id, player);
_playersByConnection.Add(player.PeerId, player);
}
public void RemovePlayer(string playerId)
{
if (_playersById.Remove(playerId, out var player))
{
_players.Remove(player);
_playersByConnection.Remove(player.PeerId);
}
}
public void OnOwnershipChanged(string playerId)
{
foreach (var player in _players)
{
if (player.Id == playerId)
Owner = player;
player.IsRoomOwner = player.Id == playerId;
}
}
public void Cleanup()
{
_players.Clear();
_playersByConnection.Clear();
_playersById.Clear();
}
}
+62
View File
@@ -0,0 +1,62 @@
/*
* 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 class RagonRoom
{
private RagonClient _client;
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;
_information = information;
_entityCache = entityCache;
_playerCache = playerCache;
_scene = scene;
}
internal void Cleanup()
{
_entityCache.Cleanup();
_playerCache.Cleanup();
}
public void LoadScene(string map) => _scene.Load(map);
public void SceneLoaded() => _scene.SceneLoaded();
public void CreateEntity(RagonEntity entity) => CreateEntity(entity, null);
public void CreateEntity(RagonEntity entity, IRagonPayload? payload) => _entityCache.Create(entity, payload);
public void DestroyEntity(RagonEntity entityId) => DestroyEntity(entityId, null);
public void DestroyEntity(RagonEntity entityId, IRagonPayload? payload) => _entityCache.Destroy(entityId, payload);
}
}
+61
View File
@@ -0,0 +1,61 @@
/*
* 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);
}
}
+109
View File
@@ -0,0 +1,109 @@
/*
* 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 RagonSession
{
private readonly RagonClient _client;
private readonly RagonBuffer _buffer;
public RagonSession(RagonClient client, RagonBuffer buffer)
{
_client = client;
_buffer = buffer;
}
public void CreateOrJoin(string map, int minPlayers, int maxPlayers)
{
var parameters = new RagonRoomParameters() {Map = map, Min = minPlayers, Max = maxPlayers};
CreateOrJoin(parameters);
}
public void CreateOrJoin(RagonRoomParameters parameters)
{
_buffer.Clear();
_buffer.WriteOperation(RagonOperation.JOIN_OR_CREATE_ROOM);
parameters.Serialize(_buffer);
var sendData = _buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void Create(string map, int minPlayers, int maxPlayers)
{
Create(null, new RagonRoomParameters() {Map = map, Min = minPlayers, Max = maxPlayers});
}
public void Create(string roomId, string map, int minPlayers, int maxPlayers)
{
Create(roomId, new RagonRoomParameters() {Map = map, Min = minPlayers, Max = maxPlayers});
}
public void Create(string roomId, RagonRoomParameters parameters)
{
_buffer.Clear();
_buffer.WriteOperation(RagonOperation.CREATE_ROOM);
if (roomId != null)
{
_buffer.WriteBool(true);
_buffer.WriteString(roomId);
}
else
{
_buffer.WriteBool(false);
}
parameters.Serialize(_buffer);
var sendData = _buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void Leave()
{
var sendData = new[] {(byte) RagonOperation.LEAVE_ROOM};
_client.Reliable.Send(sendData);
}
public void Join(string roomId)
{
_buffer.Clear();
_buffer.WriteOperation(RagonOperation.JOIN_ROOM);
_buffer.WriteString(roomId);
var sendData = _buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void AuthorizeWithKey(string key, string playerName, string payload = "")
{
_buffer.Clear();
_buffer.WriteOperation(RagonOperation.AUTHORIZE);
_buffer.WriteString(key);
_buffer.WriteString(playerName);
_buffer.WriteString(payload);
var sendData = _buffer.ToArray();
_client.Reliable.Send(sendData);
}
}
}
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client
{
public enum RagonStatus
{
DISCONNECTED,
CONNECTED,
ROOM,
LOBBY,
}
}
-8
View File
@@ -1,8 +0,0 @@
namespace Ragon.Common
{
public enum RagonAuthority: byte
{
OWNER_ONLY,
ALL,
}
}
-32
View File
@@ -1,32 +0,0 @@
namespace Ragon.Common
{
public enum RagonOperation: byte
{
AUTHORIZE,
AUTHORIZED_SUCCESS,
AUTHORIZED_FAILED,
JOIN_OR_CREATE_ROOM,
CREATE_ROOM,
JOIN_ROOM,
LEAVE_ROOM,
OWNERSHIP_CHANGED,
JOIN_SUCCESS,
JOIN_FAILED,
LOAD_SCENE,
SCENE_IS_LOADED,
PLAYER_JOINED,
PLAYER_LEAVED,
CREATE_ENTITY,
CREATE_SCENE_ENTITY,
DESTROY_ENTITY,
SNAPSHOT,
REPLICATE_ENTITY_STATE,
REPLICATE_ENTITY_EVENT,
REPLICATE_EVENT,
}
}
@@ -1,24 +0,0 @@
namespace Ragon.Common
{
public class RagonRoomParameters: IRagonSerializable
{
public string Map { get; set; }
public int Min { get; set; }
public int Max { get; set; }
public void Serialize(RagonSerializer buffer)
{
buffer.WriteString(Map);
buffer.WriteInt(Min);
buffer.WriteInt(Max);
}
public void Deserialize(RagonSerializer buffer)
{
Map = buffer.ReadString();
Min = buffer.ReadInt();
Max = buffer.ReadInt();
}
}
}
@@ -1,8 +0,0 @@
namespace Ragon.Common
{
public interface IRagonSerializable
{
public void Serialize(RagonSerializer serializer);
public void Deserialize(RagonSerializer serializer);
}
}
-335
View File
@@ -1,335 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Ragon.Common
{
[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 RagonSerializer
{
private byte[] _data;
private int _offset;
private int _size;
public int Lenght => _offset;
public int Size => _size - _offset;
public RagonSerializer(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).AsSpan();
ResizeIfNeed(2 + rawData.Length);
WriteUShort((ushort) rawData.Length);
var data = _data.AsSpan().Slice(_offset, rawData.Length);
rawData.CopyTo(data);
_offset += rawData.Length;
return rawData.Length + 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString()
{
var lenght = ReadUShort();
var stringRaw = _data.AsSpan().Slice(_offset, lenght);
var str = Encoding.UTF8.GetString(stringRaw);
_offset += lenght;
return str;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<byte> ReadData(int lenght)
{
var data = _data.AsSpan();
var payloadData = data.Slice(_offset, lenght);
_offset += payloadData.Length;
return payloadData;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteData(ref ReadOnlySpan<byte> payload)
{
ResizeIfNeed(payload.Length);
var data = _data.AsSpan();
var payloadData = data.Slice(_offset, payload.Length);
payload.CopyTo(payloadData);
_offset += payload.Length;
return payload.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> GetWritableData(int lenght)
{
ResizeIfNeed(lenght);
var data = _data.AsSpan();
var payloadData = data.Slice(_offset, lenght);
_offset += lenght;
return payloadData;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteOperation(RagonOperation ragonOperation)
{
ResizeIfNeed(1);
_data[_offset] = (byte) ragonOperation;
_offset += 1;
return 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RagonOperation ReadOperation()
{
var op = (RagonOperation) _data[_offset];
_offset += 1;
return op;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteUShort(ushort value)
{
ResizeIfNeed(2);
_data[_offset] = (byte) (value & 0x00FF);
_data[_offset + 1] = (byte) ((value & 0xFF00) >> 8);
_offset += 2;
return 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteUShort(ushort value, int offset)
{
ResizeIfNeed(2);
_data[offset] = (byte) (value & 0x00FF);
_data[offset + 1] = (byte) ((value & 0xFF00) >> 8);
return 2;
}
[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 ToSpan(ref Span<byte> data)
{
var span = _data.AsSpan();
var dataSpan = span.Slice(0, _offset);
dataSpan.CopyTo(data);
}
public void FromSpan(ref ReadOnlySpan<byte> data)
{
Clear();
ResizeIfNeed(data.Length);
var dataSpan = _data.AsSpan();
data.CopyTo(dataSpan);
_size = data.Length;
}
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;
}
}
}
-9
View File
@@ -1,9 +0,0 @@
namespace Ragon.Common
{
public enum RagonTarget: byte
{
OWNER,
EXCEPT_OWNER,
ALL,
}
}
@@ -5,6 +5,7 @@
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<LangVersion>8</LangVersion> <LangVersion>8</LangVersion>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<RootNamespace>Ragon.Common</RootNamespace>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
@@ -15,7 +16,8 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath></OutputPath> <OutputPath>C:\Users\edmand46\RagonProjects\ragon-unity-sdk\Assets\Ragon-Unity-SDK\Runtime\Plugins</OutputPath>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants> <DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
<DebugType>none</DebugType>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Protocol
{
public enum RagonAuthority: byte
{
None,
OwnerOnly,
All,
}
}
+423
View File
@@ -0,0 +1,423 @@
/*
* 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.
*/
/*
* Copyright (c) 2018 Stanislav Denisov, Maxim Munnig
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* Copyright (c) 2018 Alexander Shoulson
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
namespace Ragon.Protocol
{
public class RagonBuffer
{
private int _read;
private int _write;
private uint[] _buckets;
private readonly UTF8Encoding _utf8Encoding = new UTF8Encoding(false, true);
public int ReadOffset => _read;
public int WriteOffset => _write;
public int Length => ((_write - 1) >> 3) + 1;
public int Capacity => _write - _read;
public RagonBuffer(int capacity = 128)
{
_buckets = new uint[capacity];
_read = 0;
_write = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBool(bool value)
{
Write(value ? 1u : 0u, 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ReadBool()
{
return Read(1) == 1u;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteByte(byte value)
{
Write(value, 8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte()
{
return (byte)Read(8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteOperation(RagonOperation operation)
{
Write((byte)operation, 8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RagonOperation ReadOperation()
{
return (RagonOperation)Read(8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteFloat(float value, float min, float max, float precision)
{
var requiredBits = DeBruijn.Log2((uint)((max - min) * (1.0f / precision) + 0.5f)) + 1;
var mask = (uint)((1L << requiredBits) - 1);
var compressedValue = (uint)((value - min) * (1f / precision) + 0.5f) & mask;
Write(compressedValue, requiredBits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float ReadFloat(float min, float max, float precision)
{
var requiredBits = DeBruijn.Log2((uint)((max - min) * (1.0f / precision) + 0.5f)) + 1;
var compressedValue = Read(requiredBits);
float adjusted = compressedValue * precision + min;
if (adjusted < min)
adjusted = min;
else if (adjusted > max)
adjusted = max;
return adjusted;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteInt(int value, int min, int max)
{
var maxValue = Math.Max(Math.Abs(min), Math.Abs(max));
var requiredBits = Bits.Compute(maxValue);
uint compressedValue = (uint)((value << 1) ^ (value >> 31));
Write(compressedValue, requiredBits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt(int min, int max)
{
var maxValue = Math.Max(Math.Abs(min), Math.Abs(max));
var requiredBits = Bits.Compute(maxValue);
var compressedValue = Read(requiredBits);
var value = (int)((compressedValue >> 1) ^ (-(int)(compressedValue & 1)));
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUShort(ushort value)
{
Write(value, 16);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort ReadUShort()
{
return (ushort)Read(16);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteString(string str)
{
var data = _utf8Encoding.GetBytes(str);
var len = (uint)data.Length;
Write(len, 16);
WriteBytes(data);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString()
{
var len = (int)Read(16);
var data = ReadBytes(len);
var str = _utf8Encoding.GetString(data);
return str;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint Read(int numBits, int offset)
{
var currentBucketIndex = offset >> 5;
var used = offset & 0x0000001F;
var chunkMask = ((1UL << numBits) - 1) << used;
var scratch = (ulong)_buckets[currentBucketIndex];
if (currentBucketIndex + 1 < _buckets.Length)
scratch |= (ulong)_buckets[currentBucketIndex + 1] << 32;
var result = (scratch & chunkMask) >> used;
return (uint)result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(uint value, int numBits, int offset)
{
Debug.Assert(!(numBits < 0));
Debug.Assert(!(numBits > 32));
var index = offset >> 5;
var used = offset & 0x0000001F;
var valueMask = (1UL << numBits) - 1;
var prepared = (value & valueMask) << used;
var scratch = _buckets[index] | (ulong)_buckets[index + 1] << 32;
var result = scratch | prepared;
_buckets[index] = (uint)result;
_buckets[index + 1] = (uint)(result >> 32);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(uint value, int numBits = 16)
{
Debug.Assert(!(numBits < 0));
Debug.Assert(!(numBits > 32));
var currentBucketIndex = _write >> 5;
var used = _write & 0x0000001F;
var mask = (1UL << used) - 1;
var scratch = _buckets[currentBucketIndex] & mask;
var result = scratch | ((ulong)value << used);
if (currentBucketIndex + 1 >= _buckets.Length)
Resize(1);
_buckets[currentBucketIndex] = (uint)result;
_buckets[currentBucketIndex + 1] = (uint)(result >> 32);
_write += numBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint Read(int numBits = 16)
{
var currentBucketIndex = _read >> 5;
var used = _read & 0x0000001F;
var chunkMask = ((1UL << numBits) - 1) << used;
var scratch = (ulong)_buckets[currentBucketIndex];
if (currentBucketIndex + 1 < _buckets.Length)
scratch |= (ulong)_buckets[currentBucketIndex + 1] << 32;
var result = (scratch & chunkMask) >> used;
_read += numBits;
return (uint)result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Do not use this method, will be removed")]
public void WriteBytes(byte[] data)
{
var len = data.Length;
for (int i = 0; i < len; i++)
Write(data[i], 8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Do not use this method, will be removed")]
public byte[] ReadBytes(int lenght)
{
var data = new byte[lenght];
for (int i = 0; i < lenght; i++)
data[i] = (byte)Read(8);
return data;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadSpan(ref Span<uint> data, int size)
{
var used = _read & 0x0000001F;
var index = _read >> 5;
var limit = (size + 32 - 1) / 32;
var capacity = size;
for (int i = 0; i < limit; i++)
{
var dataSize = capacity > 32 ? 32 : capacity;
var mask = (1UL << dataSize) - 1;
var bucketRaw = (ulong)_buckets[index];
if (index + 1 < _buckets.Length)
bucketRaw |= (ulong)_buckets[index + 1] << 32;
var bucket = bucketRaw >> used;
var result = bucket & mask;
data[i] = (uint)result;
if (i + 1 < data.Length)
data[i + 1] = (uint)(result >> 32);
index += 1;
capacity -= dataSize;
}
_read += size;
}
public void WriteSpan(ref ReadOnlySpan<uint> data, int size)
{
var used = _write & 0x0000001F;
var index = _write >> 5;
var limit = (size + 32 - 1) / 32;
if (index + limit >= _buckets.Length)
Resize(size);
for (var i = 0; i < limit; i += 1)
{
var prepared = (ulong) data[i] << used;
var mask = (1UL << used) - 1;
var scratch = _buckets[index] & mask;
var result = scratch | prepared;
_buckets[index] = (uint)result;
_buckets[index + 1] = (uint)(result >> 32);
index += 1;
}
_write += size;
}
public void Clear()
{
_read = 0;
_write = 0;
}
public void FromArray(byte[] data)
{
var length = data.Length;
var bucketsCount = length / 4 + 1;
if (_buckets.Length < bucketsCount)
_buckets = new uint[bucketsCount];
for (var i = 0; i < bucketsCount; i++)
{
var dataIdx = i * 4;
var bucket = 0u;
if (dataIdx < length)
bucket = data[dataIdx];
if (dataIdx + 1 < length)
bucket |= (uint)data[dataIdx + 1] << 8;
if (dataIdx + 2 < length)
bucket |= (uint)data[dataIdx + 2] << 16;
if (dataIdx + 3 < length)
bucket |= (uint)data[dataIdx + 3] << 24;
_buckets[i] = bucket;
}
int positionInByte = Bits.FindBitPosition(data[length - 1]);
_write = ((length - 1) * 8) + positionInByte;
_read = 0;
}
public byte[] ToArray()
{
var data = new byte[Length];
int bucketsCount = (_write >> 5) + 1;
int length = data.Length;
for (int i = 0; i < bucketsCount; i++)
{
int dataIdx = i * 4;
uint bucket = _buckets[i];
if (dataIdx < length)
data[dataIdx] = (byte)(bucket);
if (dataIdx + 1 < length)
data[dataIdx + 1] = (byte)(bucket >> 8);
if (dataIdx + 2 < length)
data[dataIdx + 2] = (byte)(bucket >> 16);
if (dataIdx + 3 < length)
data[dataIdx + 3] = (byte)(bucket >> 24);
}
return data;
}
private void Resize(int capacity)
{
var buckets = new uint[_buckets.Length * 2 + capacity];
Array.Copy(_buckets, buckets, _buckets.Length);
_buckets = buckets;
}
}
}
+42
View File
@@ -0,0 +1,42 @@
/*
* 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.Protocol
{
public enum RagonOperation: byte
{
AUTHORIZE,
AUTHORIZED_SUCCESS,
AUTHORIZED_FAILED,
JOIN_OR_CREATE_ROOM,
CREATE_ROOM,
JOIN_ROOM,
LEAVE_ROOM,
OWNERSHIP_CHANGED,
JOIN_SUCCESS,
JOIN_FAILED,
LOAD_SCENE,
SCENE_LOADED,
PLAYER_JOINED,
PLAYER_LEAVED,
CREATE_ENTITY,
REMOVE_ENTITY,
SNAPSHOT,
REPLICATE_ENTITY_STATE,
REPLICATE_ENTITY_EVENT,
}
}
@@ -0,0 +1,27 @@
/*
* 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.Protocol
{
public enum RagonReplicationMode: byte
{
Local,
Server,
LocalAndServer,
Buffered,
}
}
@@ -0,0 +1,40 @@
/*
* 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.Protocol
{
public class RagonRoomParameters: IRagonSerializable
{
public string Map { get; set; }
public int Min { get; set; }
public int Max { get; set; }
public void Serialize(RagonBuffer buffer)
{
buffer.WriteString(Map);
buffer.WriteInt(Min, 1, 32);
buffer.WriteInt(Max, 1, 32);
}
public void Deserialize(RagonBuffer buffer)
{
Map = buffer.ReadString();
Min = buffer.ReadInt(1, 32);
Max = buffer.ReadInt(1, 32);
}
}
}
@@ -0,0 +1,25 @@
/*
* 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.Protocol
{
public interface IRagonSerializable
{
public void Serialize(RagonBuffer buffer);
public void Deserialize(RagonBuffer buffer);
}
}
+28
View File
@@ -0,0 +1,28 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Protocol
{
public enum RagonTarget: byte
{
Owner,
ExceptOwner,
ExceptInvoker,
All,
Player,
}
}
+80
View File
@@ -0,0 +1,80 @@
/*
* 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.Runtime.CompilerServices;
namespace Ragon.Protocol
{
public static class DeBruijn
{
private static readonly int[] _lookup = new int[32]
{
0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
};
public static int Log2(uint value)
{
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
return _lookup[(value * 0x07C4ACDDU) >> 27];
}
}
public static class Bits
{
static int[] _lookup = new int[256];
static Bits()
{
_lookup[0] = 0;
for (int i = 0; i < 256; i++)
_lookup[i] = (i & 1) + _lookup[i / 2];
}
[MethodImpl(256)]
public static int Compute(int value)
{
var count = 0;
do
{
value >>= 8;
count += 8;
} while (value > 0);
return count;
}
[MethodImpl(256)]
public static int FindBitPosition(byte data)
{
int shiftCount = 0;
while (data > 0)
{
data >>= 1;
shiftCount++;
}
return shiftCount;
}
}
}
+45
View File
@@ -0,0 +1,45 @@
/*
* 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.Protocol
{
public static class RagonVersion
{
public static uint Parse(string version)
{
var strings = version.Split(".");
if (strings.Length < 3)
return 0;
var parts = new uint[] {0, 0, 0};
for (int i = 0; i < parts.Length; i++)
{
if (!uint.TryParse(strings[i], out var v))
return 0;
parts[i] = v;
}
return (parts[0] << 16) | (parts[1] << 8) | parts[2];
}
public static string Parse(uint version)
{
return (version >> 16 & 0xFF) + "." + (version >> 8 & 0xFF) + "." + (version & 0xFF);
}
}
}
+16
View File
@@ -0,0 +1,16 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env
WORKDIR /App
COPY . ./
RUN dotnet restore
RUN dotnet publish -c Release -o out
FROM mcr.microsoft.com/dotnet/runtime:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["Ragon.Relay"]
View File
+27
View File
@@ -0,0 +1,27 @@
/*
* 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.Relay
{
class Program
{
static void Main(string[] args)
{
var relay = new Relay();
relay.Start();
}
}
}
+9 -4
View File
@@ -2,8 +2,8 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <RootNamespace>Ragon.Relay</RootNamespace>
<RootNamespace>Game</RootNamespace> <TargetFramework>net7.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@@ -14,13 +14,18 @@
<None Update="NLog.config"> <None Update="NLog.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="config.json"> <None Update="relay.config.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="libenet.dylib">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ragon\Ragon.csproj" /> <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" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -0,0 +1,6 @@
namespace Ragon.Relay;
public class KickPlayerCommand
{
public string Id;
}
+52
View File
@@ -0,0 +1,52 @@
/*
* 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;
using Ragon.Server.IO;
using Ragon.Server.Plugin;
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 networkServer = new ENetServer();
IServerPlugin plugin = new RelayServerPlugin();
switch (serverType)
{
case ServerType.ENET:
networkServer = new ENetServer();
break;
case ServerType.WEBSOCKET:
networkServer = new DotNetWebSocketServer();
break;
}
var relay = new RagonServer(networkServer, plugin, configuration);
logger.Info("Started");
relay.Start();
}
}
+37
View File
@@ -0,0 +1,37 @@
using System;
using Ragon.Server;
using Ragon.Server.Entity;
using Ragon.Server.Plugin;
using Ragon.Server.Room;
namespace Ragon.Relay;
public class RelayRoomPlugin: BaseRoomPlugin
{
public void Tick(float dt)
{
}
public void OnAttached()
{
Console.WriteLine("Room attached");
}
public void OnDetached()
{
Console.WriteLine("Room detached");
}
public bool OnEntityCreate(RagonRoomPlayer creator, RagonEntity entity)
{
Console.WriteLine($"Entity created: {entity.Id}");
return true;
}
public bool OnEntityRemove(RagonRoomPlayer destroyer, RagonEntity entity)
{
Console.WriteLine($"Entity destroyed: {entity.Id}");
return true;
}
}
+24
View File
@@ -0,0 +1,24 @@
using System;
using Newtonsoft.Json;
using Ragon.Server.Plugin;
namespace Ragon.Relay;
public class RelayServerPlugin: BaseServerPlugin
{
public override bool OnCommand(string command, string payload)
{
Console.WriteLine(command);
if (command == "kick-player")
{
var commandPayload = JsonConvert.DeserializeObject<KickPlayerCommand>(payload);
var player = Server.GetPlayerById(commandPayload.Id);
if (player != null)
player.Connection.Close();
else
Console.WriteLine($"Player not found with Id {commandPayload.Id}");
}
return true;
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"serverKey": "defaultkey",
"serverType": "enet",
"serverTickRate": 30,
"gameProtocol": "1.0.0",
"port": 5000,
"httpPort": 5001,
"httpKey": "defaultkey",
"limitConnections": 4095,
"limitPlayersPerRoom": 20,
"limitRooms": 200,
"webHooks":
{
"room-created": "http://127.0.0.1:3000/service/create-room",
"room-removed": "http://127.0.0.1:3000/service/remove-room",
"room-joined": "http://127.0.0.1:3000/service/join-room",
"room-leaved": "http://127.0.0.1:3000/service/leave-room"
}
}
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Ragon.WebSockets</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NLog" Version="5.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" />
</ItemGroup>
</Project>

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