Compare commits

..

89 Commits

Author SHA1 Message Date
edmand46 27db256902 🐛 scene entities not execute buffered events 2023-09-03 13:29:37 +03:00
edmand46 8705e93929 🐛 event size 2023-08-02 22:13:51 +03:00
edmand46 08e931d1bd 🚑 remove static entity spawn 2023-08-01 22:21:21 +03:00
edmand46 fb58dedfaf changed scene loading flow 2023-07-30 21:46:42 +03:00
edmand46 92062cd708 🎨 update naming of fields 2023-07-30 21:19:39 +03:00
edmand46 5fc55eaddc wip 2023-07-30 21:14:14 +03:00
edmand46 4a8aae11e3 🚑 fixed empty state values 2023-07-30 17:44:51 +03:00
edmand46 cd9304e63a Merge pull request #13 from edmand46/fix/payload
Fix/payload
2023-07-30 17:01:08 +03:00
edmand46 5199b5271b 🐛 Remove listener inside callback 2023-07-30 16:56:11 +03:00
edmand46 c01b748031 wip 2023-07-29 10:58:06 +03:00
edmand46 0a8d761cc1 🐛 empty payload 2023-07-23 15:56:08 +03:00
edmand46 f38c7e98de ⬆️ deps 2023-07-23 11:23:38 +03:00
edmand46 0479a21980 feat: added safe get entity by id 2023-07-09 07:40:06 +03:00
edmand46 1406b17d62 🚑 independent buffers in properties 2023-07-01 08:12:40 +03:00
edmand46 105457ffa0 added transfer ownership, limit buffered events 2023-07-01 07:47:57 +03:00
edmand46 20662ae24d wip 2023-06-27 23:41:30 +03:00
edmand46 6c441d9dee feat: Entity.OnEvent now support resubscribing 2023-05-25 11:28:18 +03:00
edmand46 907bd2611e added reason of disconnect at callback 2023-05-08 00:16:43 +03:00
edmand46 91d8516ac9 independent buffers for ragon properties, extended buffer functionality 2023-05-07 23:54:07 +03:00
edmand46 ecdafeab00 ♻️ naming changing 2023-05-07 18:16:46 +03:00
edmand46 88baff9fee compressor extensions for write/read data and reduce boilerplate 2023-05-07 18:07:56 +03:00
edmand46 fdb41649b2 ♻️ added checks in client sdk 2023-05-07 12:46:39 +03:00
edmand46 aa607a7eb9 Update README.md 2023-05-06 15:30:16 +04:00
edmand46 efebf4ceda 🐛 scene entities 2023-04-14 17:42:27 +04:00
edmand46 17d1b7307d ♻️ plugin api 2023-04-14 14:32:24 +04:00
edmand46 fc28f512ba ♻️ plugin api 2023-04-14 14:32:04 +04:00
edmand46 6c4a51534a Merge pull request #12 from edmand46/develop
Plugins
2023-04-13 20:49:31 +04:00
edmand46 c91551ae08 📝 update headers 2023-04-13 20:49:00 +04:00
edmand46 e1a9ad476c http-commands 2023-04-13 20:42:05 +04:00
edmand46 24c9aa2043 🎨 namespaces 2023-04-09 11:06:52 +04:00
edmand46 bfd6c1b54b 🚧 plugin system, webhook system 2023-04-09 10:52:18 +04:00
edmand46 f2edc94958 chore: move sources to Sources folder 2023-04-05 18:53:21 +04:00
edmand46 b8dfc4cf41 chore: removed unused project 2023-04-05 18:48:37 +04:00
edmand46 bd7713bfcb fixed: cache sending on disconnecting 2023-03-31 12:56:23 +04:00
edmand46 043523d712 fixed: checking owner 2023-03-25 20:52:36 +04:00
edmand46 0dc5307b92 fixed: tickrate 2023-03-23 19:17:54 +04:00
edmand46 7b581b9afe chore: added tickrate logging 2023-03-23 18:07:48 +04:00
edmand46 8c5e063ef0 fixed: initial dirty property not tracked 2023-03-23 14:05:03 +04:00
edmand46 1a5f72a815 fixed: property update 2023-03-23 05:28:47 +04:00
edmand46 828da0d3da fix: wrong initial capacity 2023-03-22 11:04:14 +04:00
edmand46 951174e491 feat: split interface IRagonListener 2023-03-07 13:12:48 +04:00
edmand46 10b85867af feat: split interface IRagonListener 2023-03-07 00:57:13 +04:00
edmand46 252a46a713 chore: naming 2023-03-06 10:46:02 +04:00
edmand46 273c167c87 chore: naming 2023-03-06 10:31:42 +04:00
edmand46 192fb9e8eb chore: grammarly mistake 2023-03-06 10:29:22 +04:00
edmand46 a8ddc40268 Merge pull request #11 from edmand46/next
Update readme
2023-03-06 10:23:13 +04:00
edmand46 1ae545b353 chore: update readme.md 2023-03-06 10:22:27 +04:00
edmand46 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
213 changed files with 9547 additions and 2814 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
+3 -1
View File
@@ -3,4 +3,6 @@
.vs .vs
obj obj
bin bin
*.user *.user
*.dylib
*.dll
+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.
+7 -12
View File
@@ -6,20 +6,18 @@
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/overview">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
### Requirements ### Requirements
- OSX, Windows, Linux(Ubuntu, Debian) - OSX, Windows, Linux(Ubuntu, Debian)
@@ -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>
+116
View File
@@ -0,0 +1,116 @@
using Ragon.Protocol;
namespace Ragon.Client.Simulation;
public class Game : IRagonListener, IRagonSceneRequestListener
{
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)
{
}
public void OnFailed(RagonClient client, string message)
{
RagonLog.Trace("Failed to join");
}
public void OnLeft(RagonClient client)
{
RagonLog.Trace("Left");
}
public void OnDisconnected(RagonClient client, RagonDisconnect ragonDisconnect)
{
RagonLog.Trace("Disconnected");
}
public void OnPlayerJoined(RagonClient client, RagonPlayer player)
{
RagonLog.Trace("Player joined");
}
public void OnPlayerLeft(RagonClient client, RagonPlayer player)
{
RagonLog.Trace("Player left");
}
public void OnOwnershipChanged(RagonClient client, RagonPlayer player)
{
RagonLog.Trace("Owner ship changed");
}
public void OnSceneLoaded(RagonClient client)
{
RagonLog.Trace("Joined");
_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);
}
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;
}
}
public void OnRequestScene(RagonClient client, string sceneName)
{
client.Room.SceneLoaded();
}
}
@@ -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 Ragon.Protocol;
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<RagonDisconnect> 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(RagonDisconnect.SERVER);
break;
case EventType.Timeout:
OnDisconnected?.Invoke(RagonDisconnect.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,132 @@
/*
* 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;
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<RagonDisconnect> 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(RagonDisconnect.SERVER);
break;
case EventType.Timeout:
OnDisconnected?.Invoke(RagonDisconnect.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>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>10</LangVersion>
<RootNamespace>Ragon.Client.Simulation</RootNamespace>
<Authors>Eduard Kargin (Edmand46)</Authors>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>none</DebugType>
<OutputPath>/Users/edmand46/RagonProjects/ragon-oss-examples/Assets/Ragon/Plugins/</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>/Users/edmand46/RagonProjects/ragon-oss-examples/Assets/Ragon/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));
}
}
+241
View File
@@ -0,0 +1,241 @@
/*
* 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 readonly Dictionary<int, OnEventDelegate> _events = new Dictionary<int, OnEventDelegate>();
private readonly Dictionary<int, Action<RagonPlayer, IRagonEvent>> _localEvents = new Dictionary<int, Action<RagonPlayer, IRagonEvent>>();
public RagonEntity(ushort type = 0, ushort sceneId = 0)
{
State = new RagonEntityState(this);
Type = type;
_spawnPayload = new RagonPayload(0);
_destroyPayload = new RagonPayload(0);
_sceneId = sceneId;
}
internal void Attach(RagonClient client, ushort entityId, ushort entityType, bool hasAuthority, RagonPlayer owner)
{
Type = entityType;
Id = entityId;
Owner = owner;
IsAttached = true;
Replication = true;
HasAuthority = hasAuthority;
_client = client;
Attached?.Invoke(this);
}
internal void Detach(RagonPayload payload)
{
_destroyPayload = payload;
Detached?.Invoke();
}
internal T GetPayload<T>(RagonPayload data) where T : IRagonPayload, new()
{
var payload = new T();
if (data.Size <= 0) return payload;
var buffer = new RagonBuffer();
data.Write(buffer);
payload.Deserialize(buffer);
return payload;
}
public void AttachPayload(RagonPayload payload)
{
_spawnPayload = payload;
}
public T GetAttachPayload<T>() where T : IRagonPayload, new()
{
return GetPayload<T>(_spawnPayload);
}
public T GetDetachPayload<T>() where T : IRagonPayload, new()
{
return GetPayload<T>(_destroyPayload);
}
public void ReplicateEvent<TEvent>(TEvent evnt, RagonPlayer target, RagonReplicationMode replicationMode)
where TEvent : IRagonEvent, new()
{
if (!IsAttached)
{
RagonLog.Error("Entity not attached");
return;
}
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(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 (!IsAttached)
{
RagonLog.Error("Entity not attached");
return;
}
var eventCode = _client.Event.GetEventCode(evnt);
if (target != RagonTarget.ExceptOwner)
{
if (replicationMode == RagonReplicationMode.Local)
{
_localEvents[eventCode].Invoke(_client.Room.Local, evnt);
return;
}
if (replicationMode == RagonReplicationMode.LocalAndServer)
{
_localEvents[eventCode].Invoke(_client.Room.Local, evnt);
}
}
var buffer = _client.Buffer;
buffer.Clear();
buffer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
buffer.WriteUShort(Id);
buffer.WriteUShort(eventCode);
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))
{
_events.Remove(eventCode);
_localEvents.Remove(eventCode);
RagonLog.Warn($"Event already {eventCode} subscribed, removed old one!");
}
_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.TryGetValue(eventCode, out var evnt))
evnt?.Invoke(caller, buffer);
else
RagonLog.Warn($"Handler event on entity {Id} with eventCode {eventCode} not defined");
}
internal void TrackChangedProperty(RagonProperty property)
{
_propertiesChanged = true;
}
public void OnOwnershipChanged(RagonPlayer player)
{
var prevOwner = Owner;
Owner = player;
HasAuthority = player.IsLocal;
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.Read(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,47 @@
/*
* 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 RagonPayload
{
private readonly uint[] _data = new uint[128];
private readonly int _size = 0;
public RagonPayload(int capacity)
{
_size = capacity;
}
public int Size => _size;
public void Read(RagonBuffer buffer)
{
buffer.ReadArray(_data, _size);
}
public void Write(RagonBuffer buffer)
{
buffer.WriteArray(_data, _size);
}
public override string ToString()
{
return $"Payload Size: {_size}";
}
}
@@ -0,0 +1,145 @@
/*
* 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 RagonBuffer _propertyBuffer;
private RagonEntity _entity;
private bool _dirty;
private int _size;
private int _ticks;
private int _priority;
private bool _fixed;
private string _name;
protected bool InvokeLocal;
protected RagonProperty(int priority, bool invokeLocal)
{
_size = 0;
_priority = priority;
_fixed = false;
_propertyBuffer = new RagonBuffer();
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)
{
_propertyBuffer.Clear();
if (_fixed)
{
Serialize(_propertyBuffer);
buffer.FromBuffer(_propertyBuffer, _size);
return;
}
Serialize(_propertyBuffer);
var propertySize = (ushort) _propertyBuffer.WriteOffset;
buffer.WriteUShort(propertySize);;
buffer.FromBuffer(_propertyBuffer, propertySize);
}
internal void Read(RagonBuffer buffer)
{
_propertyBuffer.Clear();
if (_fixed)
{
buffer.ToBuffer(_propertyBuffer, _size);
Deserialize(_propertyBuffer);
return;
}
var propSize = buffer.ReadUShort();
buffer.ToBuffer(_propertyBuffer, propSize);
Deserialize(_propertyBuffer);
}
public virtual void Serialize(RagonBuffer buffer)
{
}
public virtual void 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,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 AuthorizeSuccessHandler: Handler
{
private readonly RagonListenerList _listenerList;
private readonly RagonClient _client;
public AuthorizeSuccessHandler(
RagonClient client,
RagonListenerList listenerList)
{
_client = client;
_listenerList = listenerList;
}
public void Handle(RagonBuffer buffer)
{
var playerId = buffer.ReadString();
var playerName = buffer.ReadString();
var playerPayload = buffer.ReadString();
_client.SetStatus(RagonStatus.LOBBY);
_listenerList.OnAuthorizationSuccess(playerId, playerName, playerPayload);
}
}
@@ -0,0 +1,66 @@
/*
* 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;
private readonly IRagonEntityListener _entityListener;
public EntityCreateHandler(
RagonClient client,
RagonPlayerCache playerCache,
RagonEntityCache entityCache,
IRagonEntityListener entityListener
)
{
_client = client;
_entityCache = entityCache;
_playerCache = playerCache;
_entityListener = entityListener;
}
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.Local.Id == player.Id;
var entity = _entityCache.TryGetEntity(attachId, entityType, 0, entityId, hasAuthority, out var hasCreated);
entity.AttachPayload(payload);
if (hasCreated)
_entityListener.OnEntityCreated(entity);
entity.Attach(_client, entityId, entityType, hasAuthority, player);
}
}
@@ -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.IsLocal && executionMode == RagonReplicationMode.LocalAndServer)
return;
_entityCache.OnEvent(player, entityId, eventCode, buffer);
}
}
@@ -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 Ragon.Protocol;
namespace Ragon.Client;
internal class EntityOwnershipHandler: Handler
{
private readonly RagonListenerList _listenerList;
private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache;
public EntityOwnershipHandler(
RagonListenerList listenerList,
RagonPlayerCache playerCache,
RagonEntityCache entityCache)
{
_listenerList = listenerList;
_playerCache = playerCache;
_entityCache = entityCache;
}
public void Handle(RagonBuffer buffer)
{
var newOwnerId = buffer.ReadUShort();
var entities = buffer.ReadUShort();
var player = _playerCache.GetPlayerByPeer(newOwnerId);
for (var i = 0; i < entities; i++)
{
var entityId = buffer.ReadUShort();
_entityCache.OnOwnershipChanged(player, entityId);
RagonLog.Trace("Entity changed owner: " + entityId);
}
}
}
@@ -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,79 @@
/*
* 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;
public JoinSuccessHandler(
RagonClient client,
RagonBuffer buffer,
RagonListenerList listenerList,
RagonPlayerCache playerCache,
RagonEntityCache entityCache
)
{
_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 sceneName = buffer.ReadString();
var scene = new RagonScene(_client, _playerCache, _entityCache, sceneName);
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.OnSceneRequest(sceneName);
}
}
@@ -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,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.
*/
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();
room.Update(sceneName);
_listenerList.OnSceneRequest(sceneName);
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
internal class OwnershipRoomHandler: Handler
{
private readonly RagonListenerList _listenerList;
private readonly RagonPlayerCache _playerCache;
private readonly RagonEntityCache _entityCache;
public OwnershipRoomHandler(
RagonListenerList listenerList,
RagonPlayerCache playerCache,
RagonEntityCache entityCache)
{
_listenerList = listenerList;
_playerCache = playerCache;
_entityCache = entityCache;
}
public void Handle(RagonBuffer buffer)
{
var newOwnerId = buffer.ReadUShort();
var player = _playerCache.GetPlayerByPeer(newOwnerId);
_playerCache.OnOwnershipChanged(newOwnerId);
_listenerList.OnOwnershipChanged(player);
}
}
@@ -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,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;
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;
}
var emptyPayload = new RagonPayload(0);
foreach (var id in toDeleteIds)
_entityCache.OnDestroy(id, emptyPayload);
}
}
}
@@ -0,0 +1,126 @@
/*
* 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.Diagnostics;
using Ragon.Protocol;
namespace Ragon.Client;
internal class SnapshotHandler : Handler
{
private readonly IRagonEntityListener _entityListener;
private readonly RagonClient _client;
private readonly RagonListenerList _listenerList;
private readonly RagonEntityCache _entityCache;
private readonly RagonPlayerCache _playerCache;
public SnapshotHandler(
RagonClient ragonClient,
RagonListenerList listenerList,
RagonEntityCache entityCache,
RagonPlayerCache playerCache,
IRagonEntityListener entityListener
)
{
_client = ragonClient;
_entityListener = entityListener;
_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);
RagonLog.Trace($"Player {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);
if (player == null)
{
RagonLog.Error($"Player not found with peerId: ${ownerPeerId}");
return;
}
var hasAuthority = _playerCache.Local.Id == player.Id;
var entity = _entityCache.TryGetEntity(0, entityType, 0, entityId, hasAuthority, out _);
if (payloadSize > 0)
{
var payload = new RagonPayload(payloadSize);
payload.Read(buffer);
entity.AttachPayload(payload);
}
_entityListener.OnEntityCreated(entity);
entity.Read(buffer);
entity.Attach(_client, entityId, entityType, hasAuthority, player);
}
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);
if (player == null)
{
RagonLog.Error($"Player not found with peerId: ${ownerPeerId}");
return;
}
var hasAuthority = _playerCache.Local.Id == player.Id;
var entity = _entityCache.TryGetEntity(0, entityType, staticId, entityId, hasAuthority, out _);
entity.Read(buffer);
entity.Attach(_client, entityId, entityType, hasAuthority, player);
}
if (_client.Status == RagonStatus.LOBBY)
{
_client.SetStatus(RagonStatus.ROOM);
_listenerList.OnJoined();
}
_listenerList.OnSceneLoaded();
}
}
@@ -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,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.
*/
using Ragon.Protocol;
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<RagonDisconnect> 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,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 IRagonConnectionListener
{
void OnConnected(RagonClient client);
void OnDisconnected(RagonClient client, RagonDisconnect reason);
}
@@ -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,32 @@
/*
* Copyright 2023 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client
{
public interface IRagonListener :
IRagonAuthorizationListener,
IRagonConnectionListener,
IRagonFailedListener,
IRagonJoinListener,
IRagonLeftListener,
IRagonSceneListener,
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);
}
@@ -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 IRagonSceneListener
{
void OnSceneLoaded(RagonClient client);
}
@@ -0,0 +1,6 @@
namespace Ragon.Client;
public interface IRagonSceneRequestListener
{
void OnRequestScene(RagonClient client, string sceneName);
}
+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,
}
+228
View File
@@ -0,0 +1,228 @@
/*
* 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 NetworkStatistics _stats;
private IRagonEntityListener _entityListener;
private IRagonSceneCollector _sceneCollector;
private Handler[] _handlers;
private RagonBuffer _readBuffer;
private RagonBuffer _writeBuffer;
private RagonRoom _room;
private RagonSession _session;
private RagonListenerList listeners;
private RagonPlayerCache _playerCache;
private RagonEntityCache _entityCache;
private RagonEventCache _eventCache;
private RagonStatus _status;
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, int rate)
{
_connection = connection;
_connection.OnData += OnData;
_connection.OnConnected += OnConnected;
_connection.OnDisconnected += OnDisconnected;
listeners = new RagonListenerList(this);
_replicationRate = (1000.0f / rate) / 1000.0f;
_replicationTime = 0;
_eventCache = new RagonEventCache();
_stats = new NetworkStatistics();
_status = RagonStatus.DISCONNECTED;
}
public void Configure(IRagonSceneCollector sceneCollector)
{
_sceneCollector = sceneCollector;
}
public void Configure(IRagonEntityListener listener)
{
_entityListener = listener;
}
public void Connect(string address, ushort port, string protocol)
{
if (_sceneCollector == null)
{
RagonLog.Error("Scene collector is not defined!");
return;
}
if (_entityListener == null)
{
RagonLog.Error("Entity Listener is not defined!");
return;
}
_writeBuffer = new RagonBuffer();
_readBuffer = new RagonBuffer();
_session = new RagonSession(this, _readBuffer);
_playerCache = new RagonPlayerCache();
_entityCache = new RagonEntityCache(this, _playerCache, _sceneCollector);
_handlers = new Handler[byte.MaxValue];
_handlers[(byte)RagonOperation.AUTHORIZED_SUCCESS] = new AuthorizeSuccessHandler(this, listeners);
_handlers[(byte)RagonOperation.AUTHORIZED_FAILED] = new AuthorizeFailedHandler(listeners);
_handlers[(byte)RagonOperation.JOIN_SUCCESS] = new JoinSuccessHandler(this, _readBuffer, listeners, _playerCache, _entityCache);
_handlers[(byte)RagonOperation.JOIN_FAILED] = new JoinFailedHandler(listeners);
_handlers[(byte)RagonOperation.LEAVE_ROOM] = new LeaveRoomHandler(this, listeners, _entityCache);
_handlers[(byte)RagonOperation.OWNERSHIP_ROOM_CHANGED] = new OwnershipRoomHandler(listeners, _playerCache, _entityCache);
_handlers[(byte)RagonOperation.OWNERSHIP_ENTITY_CHANGED] = new EntityOwnershipHandler(listeners, _playerCache, _entityCache);
_handlers[(byte)RagonOperation.PLAYER_JOINED] = new PlayerJoinHandler(_playerCache, listeners);
_handlers[(byte)RagonOperation.PLAYER_LEAVED] = new PlayerLeftHandler(_entityCache, _playerCache, listeners);
_handlers[(byte)RagonOperation.LOAD_SCENE] = new SceneLoadHandler(this, listeners);
_handlers[(byte)RagonOperation.CREATE_ENTITY] = new EntityCreateHandler(this, _playerCache, _entityCache, _entityListener);
_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, listeners, _entityCache, _playerCache, _entityListener);
var protocolRaw = RagonVersion.Parse(protocol);
_connection.Connect(address, port, protocolRaw);
}
public void Disconnect()
{
_status = RagonStatus.DISCONNECTED;
_room.Cleanup();
_connection.Disconnect();
OnDisconnected(RagonDisconnect.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);
}
listeners.Update();
_connection.Update();
}
public void Dispose()
{
_status = RagonStatus.DISCONNECTED;
_connection.Disconnect();
_connection.Dispose();
}
public void AddListener(IRagonListener listener) => listeners.Add(listener);
public void AddListener(IRagonAuthorizationListener listener) => listeners.Add(listener);
public void AddListener(IRagonConnectionListener listener) => listeners.Add(listener);
public void AddListener(IRagonFailedListener listener) => listeners.Add(listener);
public void AddListener(IRagonJoinListener listener) => listeners.Add(listener);
public void AddListener(IRagonLeftListener listener) => listeners.Add(listener);
public void AddListener(IRagonOwnershipChangedListener listener) => listeners.Add(listener);
public void AddListener(IRagonPlayerJoinListener listener) => listeners.Add(listener);
public void AddListener(IRagonPlayerLeftListener listener) => listeners.Add(listener);
public void AddListener(IRagonSceneListener listener) => listeners.Add(listener);
public void AddListener(IRagonSceneRequestListener listener) => listeners.Add(listener);
public void RemoveListener(IRagonListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonAuthorizationListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonConnectionListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonFailedListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonJoinListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonLeftListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonOwnershipChangedListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonPlayerJoinListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonPlayerLeftListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonSceneListener listener) => listeners.Remove(listener);
public void RemoveListener(IRagonSceneRequestListener listener) => listeners.Remove(listener);
#endregion
#region INTERNAL
internal void AssignRoom(RagonRoom room)
{
_room = room;
}
internal void SetStatus(RagonStatus status)
{
_status = status;
}
#endregion
#region PRIVATE
private void OnConnected()
{
RagonLog.Trace("Connected");
listeners.OnConnected();
_status = RagonStatus.CONNECTED;
}
private void OnDisconnected(RagonDisconnect reason)
{
RagonLog.Trace($"Disconnected: {reason}");
listeners.OnDisconnected(reason);
_status = RagonStatus.DISCONNECTED;
}
public void OnData(byte[] data)
{
_readBuffer.Clear();
_readBuffer.FromArray(data);
var operation = _readBuffer.ReadByte();
_handlers[operation].Handle(_readBuffer);
}
#endregion
}
}
+257
View File
@@ -0,0 +1,257 @@
/*
* 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 IRagonSceneCollector _sceneCollector;
private readonly RagonPlayerCache _playerCache;
private int _localEntitiesCounter = 0;
public RagonEntityCache(
RagonClient client,
RagonPlayerCache playerCache,
IRagonSceneCollector sceneCollector
)
{
_client = client;
_sceneCollector = sceneCollector;
_playerCache = playerCache;
}
public bool TryGetEntity(ushort id, out RagonEntity entity)
{
return _entityMap.TryGetValue(id, out entity);
}
public void Create(RagonEntity entity, RagonPayload spawnPayload)
{
var attachId = (ushort)(_playerCache.Local.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?.Write(buffer);
_pendingEntities.Add(attachId, entity);
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void Transfer(RagonEntity entity, RagonPlayer player)
{
var buffer = _client.Buffer;
buffer.Clear();
buffer.WriteOperation(RagonOperation.TRANSFER_ENTITY_OWNERSHIP);
buffer.WriteUShort(entity.Id);
buffer.WriteUShort(player.PeerId);
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void Destroy(RagonEntity entity, RagonPayload destroyPayload)
{
if (!entity.IsAttached)
{
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?.Write(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(0);
foreach (var ent in _entityList)
ent.Detach(payload);
_entityMap.Clear();
_entityList.Clear();
}
internal RagonEntity TryGetEntity(ushort attachId, ushort entityType, ushort sceneId, ushort entityId, bool hasAuthority, out bool hasCreated)
{
if (sceneId > 0)
{
if (_sceneEntities.TryGetValue(sceneId, out var sceneEntity))
{
_entityMap.Add(entityId, sceneEntity);
if (hasAuthority)
_entityList.Add(sceneEntity);
hasCreated = false;
return sceneEntity;
}
}
if (_pendingEntities.TryGetValue(attachId, out var pendingEntity))
{
_pendingEntities.Remove(attachId);
_entityMap.Add(entityId, pendingEntity);
if (hasAuthority)
_entityList.Add(pendingEntity);
hasCreated = false;
return pendingEntity;
}
var entity = new RagonEntity(entityType, sceneId);
_entityMap.Add(entityId, entity);
if (hasAuthority)
_entityList.Add(entity);
hasCreated = true;
return entity;
}
internal void OnDestroy(ushort entityId, RagonPayload payload)
{
if (_entityMap.TryGetValue(entityId, out var entity))
{
_entityMap.Remove(entityId);
_entityList.Remove(entity);
entity.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))
{
if (player.IsLocal)
_entityList.Add(entity);
else
_entityList.Remove(entity);
entity.OnOwnershipChanged(player);
}
else
{
RagonLog.Warn($"Entity {entityId} not found!");
}
}
}
+75
View File
@@ -0,0 +1,75 @@
/*
* 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);
if (!_eventsRegistryByType.TryGetValue(type, out var eventCode))
{
RagonLog.Error($"Event with type {type} not registered");
return 0;
}
return eventCode;
}
public void Register<T>() where T : IRagonEvent, new()
{
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();
}
}
+252
View File
@@ -0,0 +1,252 @@
/*
* 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 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<IRagonSceneListener> _sceneListeners = new();
private readonly List<IRagonSceneRequestListener> _sceneRequestListeners = new();
private readonly List<IRagonOwnershipChangedListener> _ownershipChangedListeners = new();
private readonly List<IRagonPlayerJoinListener> _playerJoinListeners = new();
private readonly List<IRagonPlayerLeftListener> _playerLeftListeners = new();
private readonly List<Action> _delayedActions = new();
public RagonListenerList(RagonClient client)
{
_client = client;
}
public void Add(IRagonListener listener)
{
_authorizationListeners.Add(listener);
_connectionListeners.Add(listener);
_failedListeners.Add(listener);
_joinListeners.Add(listener);
_leftListeners.Add(listener);
_sceneListeners.Add(listener);
_ownershipChangedListeners.Add(listener);
_playerJoinListeners.Add(listener);
_playerLeftListeners.Add(listener);
}
public void Remove(IRagonListener listener)
{
_delayedActions.Add(() =>
{
_authorizationListeners.Remove(listener);
_connectionListeners.Remove(listener);
_failedListeners.Remove(listener);
_joinListeners.Remove(listener);
_leftListeners.Remove(listener);
_sceneListeners.Remove(listener);
_ownershipChangedListeners.Remove(listener);
_playerJoinListeners.Remove(listener);
_playerLeftListeners.Remove(listener);
});
}
public void Update()
{
foreach (var action in _delayedActions)
action.Invoke();
_delayedActions.Clear();
}
public void Add(IRagonAuthorizationListener listener)
{
_authorizationListeners.Add(listener);
}
public void Add(IRagonSceneRequestListener listener)
{
_sceneRequestListeners.Add(listener);
}
public void Add(IRagonConnectionListener listener)
{
_connectionListeners.Add(listener);
}
public void Add(IRagonFailedListener listener)
{
_failedListeners.Add(listener);
}
public void Add(IRagonJoinListener listener)
{
_joinListeners.Add(listener);
}
public void Add(IRagonLeftListener listener)
{
_leftListeners.Add(listener);
}
public void Add(IRagonSceneListener listener)
{
_sceneListeners.Add(listener);
}
public void Add(IRagonOwnershipChangedListener listener)
{
_ownershipChangedListeners.Add(listener);
}
public void Add(IRagonPlayerJoinListener listener)
{
_playerJoinListeners.Add(listener);
}
public void Add(IRagonPlayerLeftListener listener)
{
_playerLeftListeners.Add(listener);
}
public void Remove(IRagonSceneRequestListener listener)
{
_delayedActions.Add(() => _sceneRequestListeners.Remove(listener));
}
public void Remove(IRagonAuthorizationListener listener)
{
_delayedActions.Add(() => _authorizationListeners.Remove(listener));
}
public void Remove(IRagonConnectionListener listener)
{
_delayedActions.Add(() => _connectionListeners.Remove(listener));
}
public void Remove(IRagonFailedListener listener)
{
_delayedActions.Add(() => _failedListeners.Remove(listener));
}
public void Remove(IRagonJoinListener listener)
{
_delayedActions.Add(() => _joinListeners.Remove(listener));
}
public void Remove(IRagonLeftListener listener)
{
_delayedActions.Add(() => _leftListeners.Remove(listener));
}
public void Remove(IRagonSceneListener listener)
{
_delayedActions.Add(() => _sceneListeners.Remove(listener));
}
public void Remove(IRagonOwnershipChangedListener listener)
{
_delayedActions.Add(() => _ownershipChangedListeners.Remove(listener));
}
public void Remove(IRagonPlayerJoinListener listener)
{
_delayedActions.Add(() => _playerJoinListeners.Remove(listener));
}
public void Remove(IRagonPlayerLeftListener listener)
{
_delayedActions.Add(() => _playerLeftListeners.Remove(listener));
}
public void OnAuthorizationSuccess(string playerId, string playerName, string payload)
{
foreach (var listener in _authorizationListeners)
listener.OnAuthorizationSuccess(_client, playerId, playerName);
}
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 OnSceneLoaded()
{
foreach (var listener in _sceneListeners)
listener.OnSceneLoaded(_client);
}
public void OnSceneRequest(string sceneName)
{
foreach (var listener in _sceneRequestListeners)
listener.OnRequestScene(_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(RagonDisconnect disconnect)
{
foreach (var listener in _connectionListeners)
listener.OnDisconnected(_client, disconnect);
}
}
}
+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 IsLocal { get; set; }
public RagonPlayer(ushort peerId, string playerId, string name, bool isRoomOwner, bool isLocal)
{
PeerId = peerId;
IsRoomOwner = isRoomOwner;
IsLocal = isLocal;
Name = name;
Id = playerId;
}
}
}
+94
View File
@@ -0,0 +1,94 @@
/*
* 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 readonly List<RagonPlayer> _players = new();
private readonly Dictionary<string, RagonPlayer> _playersById = new();
private readonly Dictionary<ushort, RagonPlayer> _playersByConnection = new();
public IReadOnlyList<RagonPlayer> Players => _players;
public RagonPlayer Owner { get; private set; }
public RagonPlayer Local { 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.IsLocal)
Local = 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.TryGetValue(playerId, out var player))
{
_players.Remove(player);
_playersById.Remove(playerId);
_playersByConnection.Remove(player.PeerId);
}
}
public void OnOwnershipChanged(ushort playerPeerId)
{
foreach (var player in _players)
{
if (player.PeerId == playerPeerId)
{
Owner = player;
Owner.IsRoomOwner = true;
}
}
}
public void Cleanup()
{
_players.Clear();
_playersByConnection.Clear();
_playersById.Clear();
}
}
+70
View File
@@ -0,0 +1,70 @@
/*
* 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 string Scene => _scene.Name;
public IReadOnlyList<RagonPlayer> Players => _playerCache.Players;
public RagonPlayer Local => _playerCache.Local;
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();
}
internal void Update(string sceneName)
{
_scene.Update(sceneName);
}
public void LoadScene(string sceneName) => _scene.Load(sceneName);
public void SceneLoaded() => _scene.SceneLoaded();
public void CreateEntity(RagonEntity entity) => CreateEntity(entity, null);
public void CreateEntity(RagonEntity entity, RagonPayload payload) => _entityCache.Create(entity, payload);
public void TransferEntity(RagonEntity entity, RagonPlayer player) => _entityCache.Transfer(entity, player);
public void DestroyEntity(RagonEntity entityId) => DestroyEntity(entityId, null);
public void DestroyEntity(RagonEntity entityId, RagonPayload payload) => _entityCache.Destroy(entityId, payload);
}
}
+70
View File
@@ -0,0 +1,70 @@
/*
* 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
{
public string Name { get; private set; }
private readonly RagonClient _client;
private readonly RagonEntityCache _entityCache;
private readonly RagonPlayerCache _playerCache;
public RagonScene(RagonClient client, RagonPlayerCache playerCache, RagonEntityCache entityCache, string sceneName)
{
Name = sceneName;
_client = client;
_playerCache = playerCache;
_entityCache = entityCache;
}
internal void Update(string scene)
{
Name = scene;
}
internal void Load(string sceneName)
{
var buffer = _client.Buffer;
buffer.Clear();
buffer.WriteOperation(RagonOperation.LOAD_SCENE);
buffer.WriteString(sceneName);
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 sceneName, int minPlayers, int maxPlayers)
{
var parameters = new RagonRoomParameters() {Scene = sceneName, 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 sceneName, int minPlayers, int maxPlayers)
{
Create(null, new RagonRoomParameters() {Scene = sceneName, Min = minPlayers, Max = maxPlayers});
}
public void Create(string roomId, string sceneNa, int minPlayers, int maxPlayers)
{
Create(roomId, new RagonRoomParameters() {Scene = sceneNa, 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,
}
}
@@ -0,0 +1,27 @@
using Ragon.Client.Compressor;
using Ragon.Protocol;
namespace Ragon.Client.Utils;
public static class CompressorExtension
{
public static float Read(this FloatCompressor compressor, RagonBuffer buffer)
{
return compressor.Decompress(buffer.Read(compressor.RequiredBits));
}
public static void Write(this FloatCompressor compressor, RagonBuffer buffer, float value)
{
buffer.Write(compressor.Compress(value), compressor.RequiredBits);
}
public static float Read(this IntCompressor compressor, RagonBuffer buffer)
{
return compressor.Decompress(buffer.Read(compressor.RequiredBits));
}
public static void Write(this IntCompressor compressor, RagonBuffer buffer, int value)
{
buffer.Write(compressor.Compress(value), compressor.RequiredBits);
}
}
-21
View File
@@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>8</LangVersion>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath></OutputPath>
<DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath></OutputPath>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
</PropertyGroup>
</Project>
-9
View File
@@ -1,9 +0,0 @@
namespace Ragon.Common
{
public enum RagonAuthority: byte
{
NONE,
OWNER_ONLY,
ALL,
}
}
-31
View File
@@ -1,31 +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_LOADED,
PLAYER_JOINED,
PLAYER_LEAVED,
CREATE_ENTITY,
DESTROY_ENTITY,
SNAPSHOT,
REPLICATE_ENTITY_STATE,
REPLICATE_ENTITY_EVENT,
REPLICATE_EVENT,
}
}
@@ -1,10 +0,0 @@
namespace Ragon.Common
{
public enum RagonReplicationMode: byte
{
LOCAL,
SERVER,
LOCAL_AND_SERVER,
BUFFERED,
}
}
@@ -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,
}
}
+31
View File
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>8</LangVersion>
<RootNamespace>Ragon.Common</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.2.4-rc</Version>
<Title>Ragon.Protocol</Title>
<Copyright>Eduard Kargin</Copyright>
<PackageProjectUrl>https://ragon-server.com</PackageProjectUrl>
<RepositoryUrl>https://github.com/edmand46/Ragon</RepositoryUrl>
<RepositoryType>Source</RepositoryType>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath></OutputPath>
<DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath></OutputPath>
<DefineConstants>TRACE;</DefineConstants>
<DebugType>none</DebugType>
</PropertyGroup>
</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,
}
}
+436
View File
@@ -0,0 +1,436 @@
/*
* 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.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 ReadArray(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;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteArray(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;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromBuffer(RagonBuffer buffer, int size)
{
WriteArray(buffer._buckets, size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToBuffer(RagonBuffer buffer, int size)
{
ReadArray(buffer._buckets, size);
}
public void FromArray(byte[] data)
{
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;
}
}
}
@@ -0,0 +1,9 @@
namespace Ragon.Protocol
{
public enum RagonDisconnect
{
MANUAL,
TIMEOUT,
SERVER,
}
}
+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 enum RagonOperation: byte
{
AUTHORIZE,
AUTHORIZED_SUCCESS,
AUTHORIZED_FAILED,
JOIN_OR_CREATE_ROOM,
CREATE_ROOM,
JOIN_ROOM,
LEAVE_ROOM,
OWNERSHIP_ENTITY_CHANGED,
OWNERSHIP_ROOM_CHANGED,
JOIN_SUCCESS,
JOIN_FAILED,
LOAD_SCENE,
SCENE_LOADED,
PLAYER_JOINED,
PLAYER_LEAVED,
CREATE_ENTITY,
REMOVE_ENTITY,
SNAPSHOT,
REPLICATE_ENTITY_STATE,
REPLICATE_ENTITY_EVENT,
TRANSFER_ROOM_OWNERSHIP,
TRANSFER_ENTITY_OWNERSHIP,
}
}
@@ -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 Scene { get; set; }
public int Min { get; set; }
public int Max { get; set; }
public void Serialize(RagonBuffer buffer)
{
buffer.WriteString(Scene);
buffer.WriteInt(Min, 1, 32);
buffer.WriteInt(Max, 1, 32);
}
public void Deserialize(RagonBuffer buffer)
{
Scene = 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.WebSocketServer\Ragon.Server.WebSocketServer.csproj" />
<ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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