Compare commits

..

122 Commits

Author SHA1 Message Date
edmand46 2f919e4fd8 wip 2025-01-18 17:30:03 +03:00
edmand46 edf90b39c4 wip 2024-11-03 11:36:58 +03:00
edmand46 672bb1ff6d wip 2024-09-28 20:11:56 +03:00
edmand46 5136f08dab feat: config for size of property
fix: websocket server now can receive large messages
fix: buffer resize on read array
2024-08-17 10:29:27 +03:00
edmand46 f7719e1bca chore: update server version 2024-08-15 23:27:53 +03:00
edmand46 211b24fe2b feat: added server address in relay.config.json 2024-08-15 23:20:23 +03:00
edmand46 bdf7d4f94a fix: connection key is invalid 2024-07-21 09:46:01 +03:00
edmand46 3bec19c2b2 chore: updated copyright 2024-05-19 12:26:42 +03:00
edmand46 0f2d316523 chore: updated projects properties for nuget 2024-05-19 12:25:56 +03:00
edmand46 7cf1353869 feat: improved server plugin api 2024-05-19 11:28:36 +03:00
edmand46 6199af3d1e fix: added checking max players per room from config 2024-05-19 08:59:36 +03:00
edmand46 e9dc45265a refactoring: removed external dependencies from server, removed webhooks from server 2024-05-19 08:56:21 +03:00
edmand46 b84538b238 fix: stop tick room on delete 2024-05-19 08:00:56 +03:00
edmand46 7a2196ff50 feat: improved api for plugins 2024-05-18 17:24:57 +03:00
edmand46 5634a182e6 feat: plugin api for replication of data 2024-05-16 21:28:47 +03:00
edmand46 0ede864f40 refactor: updated order of plugins callbacks 2024-05-12 11:37:44 +03:00
edmand46 646744c9a1 feat: player/room user data now available on joined event 2024-05-12 10:57:46 +03:00
edmand46 a9e6a3e853 feat: Room Properties and Player Properties 2024-05-09 21:26:32 +03:00
edmand46 c74ce0f00f Merge pull request #16 from edmand46/feat/room_property
Feat Room/Player Properties
2024-05-09 10:55:54 +03:00
edmand46 d3ae5a4465 feat: player propeties 2024-05-09 10:50:59 +03:00
edmand46 5bf1881f81 feat: room properties ready, player properties wip 2024-05-07 22:42:45 +03:00
edmand46 6886808132 feat(wip): room properties 2024-05-05 15:45:28 +03:00
edmand46 b4cba20d82 feat(wip): player properties 2024-04-29 09:12:42 +03:00
edmand46 b39bd8bd0a fix: updated workflows 2024-04-14 19:23:08 +03:00
edmand46 12b80c546c fix: updated workflows 2024-04-14 19:14:57 +03:00
edmand46 accd442388 feat(wip) room properties 2024-04-14 19:07:48 +03:00
edmand46 d115c98b79 feat(wip): Room Property and Player Property 2024-04-14 08:49:30 +03:00
edmand46 5c20fbafc1 Merge pull request #15 from edmand46/feat/rooms
feat: Room List
2024-04-13 18:27:28 +03:00
edmand46 bd0c6df5ea fix: timer, providing api for listeners 2024-04-13 18:25:39 +03:00
edmand46 c4811ef052 feat(wip): list rooms 2024-04-13 17:43:23 +03:00
edmand46 d82964526c feat(wip): room list support 2024-04-13 16:17:31 +03:00
edmand46 acedaef270 ♻️ unified event api for room and entities 2024-04-07 17:15:52 +03:00
edmand46 0c4bdb83c9 added new method for RagonBuffer 2024-04-07 17:11:45 +03:00
edmand46 5a79502848 RagonStream 2024-04-06 19:11:16 +03:00
edmand46 b8fe2bb13f 🐛 wrong condition in lobby in memory on finding room by id 2024-02-04 20:06:07 +03:00
edmand46 89d130a193 🐛 fixed double call on invoke local flag in ragon property 2024-01-21 12:20:47 +03:00
edmand46 0cd912a1aa 🎨 added null checks 2024-01-13 15:15:29 +03:00
edmand46 c64cc61c78 🐛 prediction spawning for wrong connection, payload capacity 2024-01-04 23:29:35 +03:00
edmand46 2dcb047014 chore: added logging 2023-11-05 22:19:55 +03:00
edmand46 33f8bba2ed fixed: remove exception on non exists player 2023-11-05 22:08:41 +03:00
edmand46 5aa159ed2f fixed: custom property wrong size 2023-11-05 21:53:57 +03:00
edmand46 892558ab16 chore: update version 2023-10-22 21:10:31 +03:00
edmand46 f55634877a 📝 update readme.md 2023-10-21 23:10:41 +03:00
edmand46 8b3a29a750 📝 update readme.md 2023-10-21 23:09:49 +03:00
edmand46 6a50bffe1e 📝 update readme.md 2023-10-21 23:03:55 +03:00
edmand46 9144ad58ef 📝 update readme.md 2023-10-21 23:03:37 +03:00
edmand46 ee9f3fbe3a 🐛 crash on abnormal disconnecting from websocket server 2023-10-19 20:20:40 +03:00
edmand46 893c73512a feat: allow control replication via code, fix null owner on OnEntityCreated 2023-10-16 23:37:35 +03:00
edmand46 e7dd693147 🐛 Event Payload 2023-10-14 23:50:25 +03:00
edmand46 fef1007c8c 🔖 update version 2023-10-14 21:48:33 +03:00
edmand46 e33c442a18 🐛 WebSocket buffer size 2023-10-14 21:35:51 +03:00
edmand46 09b185a3ad 🐛 checking size event 2023-10-13 12:10:46 +03:00
edmand46 7d154ea4d4 🎨 cleanup 2023-10-13 11:02:56 +03:00
edmand46 d2577e5d1f remove allocation on raw data replication 2023-10-13 10:58:15 +03:00
edmand46 e25f42f9ff Merge pull request #14 from edmand46/v1.3.0
Ragon v1.3.0
2023-10-12 17:41:30 +03:00
edmand46 28cc41c3ad 🎨 update project 2023-10-12 17:40:57 +03:00
edmand46 fc483c0854 🚑 crash on timeout disconnect 2023-10-12 16:09:24 +03:00
edmand46 bcc45f7db8 🐛 lost piece of data 2023-10-12 15:38:34 +03:00
edmand46 689a240e5b 🐛 lost piece of data 2023-10-12 15:35:40 +03:00
edmand46 85b75766a9 🎨 remove $ from logs 2023-10-12 11:21:21 +03:00
edmand46 b90ed974e5 🐛 checking authority on destroy object 2023-10-12 11:18:04 +03:00
edmand46 3da57e086e 🐛 channels more then capacity 2023-10-11 21:21:58 +03:00
edmand46 745d196a8b wip 2023-10-11 20:49:00 +03:00
edmand46 860051777e wip 2023-10-11 19:38:26 +03:00
edmand46 6422db783a wip 2023-10-11 19:37:50 +03:00
edmand46 5d812d7acc 🚧 clean up listeners 2023-10-09 09:17:43 +03:00
edmand46 c214b6ca7f wip 2023-10-08 21:13:31 +03:00
edmand46 64842886d7 multiple subscribers, and unsubscribe 2023-10-07 20:20:02 +03:00
edmand46 c892c2b67a 🐛 operation code for room 2023-10-07 19:33:55 +03:00
edmand46 e1a3ea45e2 🚧 pass-through raw data, refactoring 2023-10-07 19:30:52 +03:00
edmand46 8788cb0fcf wip 2023-10-04 14:42:59 +03:00
edmand46 27db256902 🐛 scene entities not execute buffered events 2023-09-03 13:29:37 +03:00
edmand46 8705e93929 🐛 event size 2023-08-02 22:13:51 +03:00
edmand46 08e931d1bd 🚑 remove static entity spawn 2023-08-01 22:21:21 +03:00
edmand46 fb58dedfaf changed scene loading flow 2023-07-30 21:46:42 +03:00
edmand46 92062cd708 🎨 update naming of fields 2023-07-30 21:19:39 +03:00
edmand46 5fc55eaddc wip 2023-07-30 21:14:14 +03:00
edmand46 4a8aae11e3 🚑 fixed empty state values 2023-07-30 17:44:51 +03:00
edmand46 cd9304e63a Merge pull request #13 from edmand46/fix/payload
Fix/payload
2023-07-30 17:01:08 +03:00
edmand46 5199b5271b 🐛 Remove listener inside callback 2023-07-30 16:56:11 +03:00
edmand46 c01b748031 wip 2023-07-29 10:58:06 +03:00
edmand46 0a8d761cc1 🐛 empty payload 2023-07-23 15:56:08 +03:00
edmand46 f38c7e98de ⬆️ deps 2023-07-23 11:23:38 +03:00
edmand46 0479a21980 feat: added safe get entity by id 2023-07-09 07:40:06 +03:00
edmand46 1406b17d62 🚑 independent buffers in properties 2023-07-01 08:12:40 +03:00
edmand46 105457ffa0 added transfer ownership, limit buffered events 2023-07-01 07:47:57 +03:00
edmand46 20662ae24d wip 2023-06-27 23:41:30 +03:00
edmand46 6c441d9dee feat: Entity.OnEvent now support resubscribing 2023-05-25 11:28:18 +03:00
edmand46 907bd2611e added reason of disconnect at callback 2023-05-08 00:16:43 +03:00
edmand46 91d8516ac9 independent buffers for ragon properties, extended buffer functionality 2023-05-07 23:54:07 +03:00
edmand46 ecdafeab00 ♻️ naming changing 2023-05-07 18:16:46 +03:00
edmand46 88baff9fee compressor extensions for write/read data and reduce boilerplate 2023-05-07 18:07:56 +03:00
edmand46 fdb41649b2 ♻️ added checks in client sdk 2023-05-07 12:46:39 +03:00
edmand46 aa607a7eb9 Update README.md 2023-05-06 15:30:16 +04:00
edmand46 efebf4ceda 🐛 scene entities 2023-04-14 17:42:27 +04:00
edmand46 17d1b7307d ♻️ plugin api 2023-04-14 14:32:24 +04:00
edmand46 fc28f512ba ♻️ plugin api 2023-04-14 14:32:04 +04:00
edmand46 6c4a51534a Merge pull request #12 from edmand46/develop
Plugins
2023-04-13 20:49:31 +04:00
edmand46 c91551ae08 📝 update headers 2023-04-13 20:49:00 +04:00
edmand46 e1a9ad476c http-commands 2023-04-13 20:42:05 +04:00
edmand46 24c9aa2043 🎨 namespaces 2023-04-09 11:06:52 +04:00
edmand46 bfd6c1b54b 🚧 plugin system, webhook system 2023-04-09 10:52:18 +04:00
edmand46 f2edc94958 chore: move sources to Sources folder 2023-04-05 18:53:21 +04:00
edmand46 b8dfc4cf41 chore: removed unused project 2023-04-05 18:48:37 +04:00
edmand46 bd7713bfcb fixed: cache sending on disconnecting 2023-03-31 12:56:23 +04:00
edmand46 043523d712 fixed: checking owner 2023-03-25 20:52:36 +04:00
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
189 changed files with 8488 additions and 2298 deletions
+25
View File
@@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
dotnet-version: 8.0.x
- name: Build
shell: bash
run: |
+3
View File
@@ -1,6 +1,9 @@
.DS_Store
.idea
.vs
.vscode
obj
bin
*.user
*.dylib
*.dll
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+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.
+2 -5
View File
@@ -1,12 +1,12 @@
<p align="center">
<img src="Images/ragon-logo.png" width="200" >
<img src="Images/logo.png">
</p>
## Ragon Server
Ragon is fully free, small and high perfomance room based game server with plugin based architecture.
<a href="http://localhost:3000/docs/installation">Documentation</a>
<a href="https://www.ragon-server.com/docs/overview">Documentation</a>
### Features:
- Effective
@@ -26,9 +26,6 @@ Ragon is fully free, small and high perfomance room based game server with plugi
### Dependencies
* ENet-Sharp [v2.4.8]
### License
MIT
### Tips
\* Limited to 4095 CCU by library ENet-Sharp (1)
\* Non finally (2)
+27
View File
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>10</LangVersion>
<RootNamespace>Ragon.Client.Simulation</RootNamespace>
<Authors>Eduard Kargin</Authors>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>none</DebugType>
<OutputPath></OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath></OutputPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ragon.Protocol\Ragon.Protocol.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,35 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
internal class AuthorizeFailedHandler: IHandler
{
private readonly RagonListenerList _listenerList;
public AuthorizeFailedHandler(RagonListenerList list)
{
_listenerList = list;
}
public void Handle(RagonStream reader)
{
var message = reader.ReadString();
_listenerList.OnAuthorizationFailed(message);
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
internal class AuthorizeSuccessHandler: IHandler
{
private readonly RagonListenerList _listenerList;
private readonly RagonClient _client;
public AuthorizeSuccessHandler(
RagonClient client,
RagonListenerList listenerList)
{
_client = client;
_listenerList = listenerList;
}
public void Handle(RagonStream reader)
{
var playerId = reader.ReadString();
var playerName = reader.ReadString();
var playerPayload = reader.ReadString();
_client.UpdateState(RagonState.LOBBY);
_listenerList.OnAuthorizationSuccess(playerId, playerName, playerPayload);
}
}
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
public interface IHandler
{
public void Handle(RagonStream reader);
}
@@ -0,0 +1,36 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
internal class JoinFailedHandler: IHandler
{
private readonly RagonListenerList _listenerList;
public JoinFailedHandler(RagonListenerList listenerList)
{
_listenerList = listenerList;
}
public void Handle(RagonStream reader)
{
var message = reader.ReadString();
_listenerList.OnFailed(message);
}
}
@@ -0,0 +1,88 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
public struct RoomParameters
{
public RoomParameters(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 : IHandler
{
private readonly RagonListenerList _listenerList;
private readonly RagonPlayerCache _playerCache;
private readonly RagonClient _client;
private readonly RagonRoom _room;
public JoinSuccessHandler(
RagonClient client,
RagonRoom room
)
{
_client = client;
_room = room;
}
public void Handle(RagonStream reader)
{
var roomId = reader.ReadString();
var min = reader.ReadUShort();
var max = reader.ReadUShort();
var localId = reader.ReadString();
var ownerId = reader.ReadString();
var roomInfo = new RoomParameters(roomId, localId, ownerId, min, max);
_playerCache.SetOwnerAndLocal(ownerId, localId);
_room.Reset(roomInfo);
_room.UserData.Read(reader);
var playersCount = reader.ReadUShort();
RagonLog.Trace("Players: " + playersCount);
for (var i = 0; i < playersCount; i++)
{
var playerPeerId = reader.ReadUShort();
var playerId = reader.ReadString();
var playerName = reader.ReadString();
var player = _playerCache.AddPlayer(playerPeerId, playerId, playerName);
player.UserData.Read(reader);
RagonLog.Trace($"Player {playerPeerId} - {playerId} - {playerName}");
}
_client.UpdateState(RagonState.ROOM);
_listenerList.OnJoined();
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
internal class LeaveRoomHandler : IHandler
{
private readonly RagonClient _client;
private readonly RagonListenerList _listenerList;
public LeaveRoomHandler(
RagonClient client,
RagonListenerList listenerList)
{
_client = client;
_listenerList = listenerList;
}
public void Handle(RagonStream reader)
{
_listenerList.OnLeft();
_client.Room.Clear();
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
internal class OwnershipRoomHandler : IHandler
{
private readonly RagonListenerList _listenerList;
private readonly RagonPlayerCache _playerCache;
public OwnershipRoomHandler(
RagonListenerList listenerList,
RagonPlayerCache playerCache)
{
_listenerList = listenerList;
_playerCache = playerCache;
}
public void Handle(RagonStream reader)
{
var newOwnerId = reader.ReadUShort();
var player = _playerCache.GetPlayerByPeer(newOwnerId);
if (player == null)
{
RagonLog.Warn($"Player with peerId:{newOwnerId} not found in cache");
_playerCache.Dump();
return;
}
_playerCache.OnOwnershipChanged(newOwnerId);
_listenerList.OnOwnershipChanged(player);
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
internal class PlayerJoinHandler : IHandler
{
private RagonPlayerCache _playerCache;
private RagonListenerList _listenerList;
public PlayerJoinHandler(
RagonPlayerCache playerCache,
RagonListenerList listenerList
)
{
_playerCache = playerCache;
_listenerList = listenerList;
}
public void Handle(RagonStream reader)
{
var playerPeerId = reader.ReadUShort();
var playerId = reader.ReadString();
var playerName = reader.ReadString();
_playerCache.AddPlayer(playerPeerId, playerId, playerName);
var player = _playerCache.GetPlayerById(playerId);
if (player != null)
_listenerList.OnPlayerJoined(player);
else
RagonLog.Warn($"Player with Id:{playerId} not found in cache");
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
internal class PlayerLeftHandler : IHandler
{
private RagonPlayerCache _playerCache;
private RagonListenerList _listenerList;
public PlayerLeftHandler(
RagonPlayerCache playerCache,
RagonListenerList listenerList
)
{
_playerCache = playerCache;
_listenerList = listenerList;
}
public void Handle(RagonStream reader)
{
var playerId = reader.ReadString();
var player = _playerCache.GetPlayerById(playerId);
if (player != null)
{
_playerCache.RemovePlayer(playerId);
_listenerList.OnPlayerLeft(player);
var entities = reader.ReadUShort();
var toDeleteIds = new ushort[entities];
for (var i = 0; i < entities; i++)
{
var entityId = reader.ReadUShort();
toDeleteIds[i] = entityId;
}
// var emptyPayload = new RagonPayload(0);
// foreach (var id in toDeleteIds)
// _entityCache.OnDestroy(id, emptyPayload);
}
else
{
RagonLog.Warn($"Player with Id:{playerId} not found in cache");
}
}
}
@@ -0,0 +1,52 @@
/*
* Copyright 2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client
{
internal class PlayerUserDataHandler: IHandler
{
private RagonPlayerCache _playerCache;
private RagonListenerList _listenerList;
public PlayerUserDataHandler(
RagonPlayerCache playerCache,
RagonListenerList listenerList
)
{
_playerCache = playerCache;
_listenerList = listenerList;
}
public void Handle(RagonStream reader)
{
var playerPeerId = reader.ReadUShort();
var player = _playerCache.GetPlayerByPeer(playerPeerId);
if (player != null)
{
var changes = player.UserData.Read(reader);
_listenerList.OnPlayerUserData(player, changes);
return;
}
RagonLog.Warn("Received user data for unknown player.");
}
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
public class RoomEventHandler : IHandler
{
private readonly RagonClient _client;
private readonly RagonPlayerCache _playerCache;
public RoomEventHandler(
RagonClient client,
RagonPlayerCache playerCache
)
{
_client = client;
_playerCache = playerCache;
}
public void Handle(RagonStream buffer)
{
var eventCode = buffer.ReadUShort();
var peerId = buffer.ReadUShort();
var executionMode = (RagonReplicationMode) buffer.ReadByte();
var player = _playerCache.GetPlayerByPeer(peerId);
if (player == null)
{
RagonLog.Error($"Player with peerId:{peerId} not found as owner of event with code:{eventCode}");
_playerCache.Dump();
return;
}
if (player.IsLocal && executionMode == RagonReplicationMode.LocalAndServer)
return;
_client.Room.HandleEvent(eventCode, player, buffer);
}
}
@@ -0,0 +1,43 @@
using Ragon.Protocol;
namespace Ragon.Client;
internal class RoomListHandler: IHandler
{
private RagonListenerList _listenerList;
private RagonSession _session;
public RoomListHandler(RagonSession session, RagonListenerList list)
{
_session = session;
_listenerList = list;
}
public void Handle(RagonStream reader)
{
var roomCount = reader.ReadUShort();
var roomList = new RagonRoomInformation[roomCount];
for (int i = 0; i < roomCount; i++)
{
var id = reader.ReadString();
var scene = reader.ReadString();
var maxPlayers = reader.ReadUShort();
var minPlayers = reader.ReadUShort();
var players = reader.ReadUShort();
var roomInfo = new RagonRoomInformation()
{
Id = id,
Scene = scene,
PlayerCount = players,
PlayerMax = maxPlayers,
PlayerMin = minPlayers,
Properties = new Dictionary<string, byte[]>()
};
roomList[i] = roomInfo;
}
_listenerList.OnRoomList(roomList);
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client
{
internal class RoomUserDataHandler : IHandler
{
private readonly RagonClient _client;
private readonly RagonListenerList _listenerList;
public RoomUserDataHandler(RagonClient client, RagonListenerList listenerList)
{
_client = client;
_listenerList = listenerList;
}
public void Handle(RagonStream reader)
{
var changes = _client.Room?.UserData.Read(reader);
_listenerList.OnRoomUserData(changes);
}
}
}
@@ -0,0 +1,21 @@
using Ragon.Protocol;
namespace Ragon.Client;
public class TimestampHandler: IHandler
{
private readonly RagonClient _client;
public TimestampHandler(RagonClient client)
{
_client = client;
}
public void Handle(RagonStream buffer)
{
var timestamp0 = (uint)buffer.ReadInt();
var timestamp1 = (uint)buffer.ReadInt();
var value = new DoubleToUInt { Int0 = timestamp0, Int1 = timestamp1 };
_client.UpdateTimestamp(value.Double);
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client;
public interface INetworkChannel
{
void Send(byte[] data);
}
@@ -0,0 +1,37 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
public 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-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client;
public interface IRagonConnection
{
public void Close();
}
+24
View File
@@ -0,0 +1,24 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client
{
public interface IRagonEvent: IRagonSerializable
{
}
}
+27
View File
@@ -0,0 +1,27 @@
/*
* Copyright 2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
public interface IUserData
{
public byte[] this[string key] { get; set; }
bool Dirty { get; }
IReadOnlyList<string> Read(RagonStream buffer);
void Write(RagonStream buffer);
}
@@ -0,0 +1,24 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
public interface IRagonConnectionListener
{
void OnConnected(RagonClient client);
void OnDisconnected(RagonClient client, RagonDisconnect reason);
}
@@ -0,0 +1,22 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client;
public interface IRagonDataListener
{
public void OnData(RagonClient client, RagonPlayer player, byte[] data);
}
@@ -0,0 +1,22 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client;
public interface IRagonFailedListener
{
void OnFailed(RagonClient client, string message);
}
@@ -0,0 +1,22 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client;
public interface IRagonJoinListener
{
void OnJoined(RagonClient client);
}
@@ -0,0 +1,22 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client;
public interface IRagonLeftListener
{
void OnLeft(RagonClient client);
}
@@ -0,0 +1,34 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client
{
public interface IRagonListener :
IRagonAuthorizationListener,
IRagonConnectionListener,
IRagonFailedListener,
IRagonJoinListener,
IRagonLeftListener,
IRagonOwnershipChangedListener,
IRagonPlayerJoinListener,
IRagonPlayerLeftListener,
IRagonRoomListListener,
IRagonRoomUserDataListener,
IRagonPlayerUserDataListener
{
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client;
public interface IRagonOwnershipChangedListener
{
void OnOwnershipChanged(RagonClient client, RagonPlayer player);
}
@@ -0,0 +1,22 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client;
public interface IRagonPlayerJoinListener
{
void OnPlayerJoined(RagonClient client, RagonPlayer player);
}
@@ -0,0 +1,22 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client;
public interface IRagonPlayerLeftListener
{
void OnPlayerLeft(RagonClient client, RagonPlayer player);
}
@@ -0,0 +1,22 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client;
public interface IRagonPlayerUserDataListener
{
void OnPlayerUserDataUpdated(RagonClient client, RagonPlayer player, IReadOnlyList<string> changes);
}
@@ -0,0 +1,6 @@
namespace Ragon.Client;
public interface IRagonRoomListListener
{
public void OnRoomListUpdate(RagonClient client, IReadOnlyList<RagonRoomInformation> roomsInfos);
}
@@ -0,0 +1,6 @@
namespace Ragon.Client;
public interface IRagonRoomUserDataListener
{
public void OnUserDataUpdated(RagonClient client, IReadOnlyList<string> changes);
}
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client;
public enum RagonLogLevel
{
ALL,
CONNECTION,
STATE,
EVENT,
ROOM,
}
+255
View File
@@ -0,0 +1,255 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client
{
public sealed class RagonClient
{
private readonly INetworkConnection _connection;
private readonly NetworkStatistics _stats;
private IHandler[] _handlers;
private RagonStream _readBuffer;
private RagonStream _writeBuffer;
private RagonRoom _room;
private RagonSession _session;
private RagonListenerList _listeners;
private RagonPlayerCache _playerCache;
private RagonEventCache _eventCache;
private RagonState _state;
private double _serverTimestamp;
private float _replicationRate = 0;
private float _replicationTime = 0;
public double ServerTimestamp => _serverTimestamp;
public IRagonConnection Connection => _connection;
public RagonState State => _state;
public RagonSession Session => _session;
public RagonEventCache Event => _eventCache;
public NetworkStatistics Statistics => _stats;
public RagonRoom Room => _room;
internal RagonStream Buffer => _writeBuffer;
internal INetworkChannel Reliable => _connection.Reliable;
internal INetworkChannel Unreliable => _connection.Unreliable;
#region PUBLIC
public RagonClient(INetworkConnection connection, int rate)
{
_listeners = new RagonListenerList(this);
_connection = connection;
_connection.OnData += OnData;
_connection.OnConnected += OnConnected;
_connection.OnDisconnected += OnDisconnected;
_replicationRate = (1000.0f / rate) / 1000.0f;
_replicationTime = 0;
_eventCache = new RagonEventCache();
_writeBuffer = new RagonStream();
_readBuffer = new RagonStream();
_playerCache = new RagonPlayerCache();
_session = new RagonSession(this, _writeBuffer);
_room = new RagonRoom(this, _playerCache);
_stats = new NetworkStatistics();
_state = RagonState.DISCONNECTED;
_handlers = new IHandler[byte.MaxValue];
_handlers[(byte)RagonOperation.AUTHORIZED_SUCCESS] = new AuthorizeSuccessHandler(this, _listeners);
_handlers[(byte)RagonOperation.AUTHORIZED_FAILED] = new AuthorizeFailedHandler(_listeners);
_handlers[(byte)RagonOperation.JOIN_SUCCESS] = new JoinSuccessHandler(this, _room);
_handlers[(byte)RagonOperation.JOIN_FAILED] = new JoinFailedHandler(_listeners);
_handlers[(byte)RagonOperation.LEAVE_ROOM] = new LeaveRoomHandler(this, _listeners);
_handlers[(byte)RagonOperation.OWNERSHIP_ROOM_CHANGED] = new OwnershipRoomHandler(_listeners, _playerCache);
_handlers[(byte)RagonOperation.PLAYER_JOINED] = new PlayerJoinHandler(_playerCache, _listeners);
_handlers[(byte)RagonOperation.PLAYER_LEAVED] = new PlayerLeftHandler(_playerCache, _listeners);
_handlers[(byte)RagonOperation.REPLICATE_ROOM_EVENT] = new RoomEventHandler(this, _playerCache);
_handlers[(byte)RagonOperation.TIMESTAMP_SYNCHRONIZATION] = new TimestampHandler(this);
_handlers[(byte)RagonOperation.ROOM_LIST_UPDATED] = new RoomListHandler(_session, _listeners);
_handlers[(byte)RagonOperation.ROOM_DATA_UPDATED] = new RoomUserDataHandler(this, _listeners);
_handlers[(byte)RagonOperation.PLAYER_DATA_UPDATED] = new PlayerUserDataHandler(_playerCache, _listeners);
}
public void Connect(string address, ushort port, string protocol)
{
var protocolRaw = RagonVersion.Parse(protocol);
_connection.Connect(address, port, protocolRaw);
}
public void Disconnect()
{
_state = RagonState.DISCONNECTED;
_room.Clear();
_connection.Disconnect();
OnDisconnected(RagonDisconnect.MANUAL);
}
public void Update(float dt)
{
if (_state != RagonState.DISCONNECTED)
{
_replicationTime += dt;
if (_replicationTime >= _replicationRate)
{
_replicationTime = 0;
SendTimestamp();
SendRoomUserData();
SendPlayerUserData();
}
_stats.Update(_connection.BytesSent, _connection.BytesReceived, _connection.Ping, dt);
}
_listeners.Update();
_connection.Update();
}
public void Dispose()
{
if (_state != RagonState.DISCONNECTED)
{
_state = RagonState.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(IRagonRoomListListener listener) => _listeners.Add(listener);
public void AddListener(IRagonPlayerUserDataListener listener) => _listeners.Add(listener);
public void AddListener(IRagonRoomUserDataListener 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(IRagonRoomListListener listener) => _listeners.Remove(listener);
public void RemoveListener(IRagonRoomUserDataListener listener) => _listeners.Remove(listener);
public void RemoveListener(IRagonPlayerUserDataListener listener) => _listeners.Remove(listener);
#endregion
#region INTERNAL
internal void UpdateState(RagonState state)
{
_state = state;
}
internal void UpdateTimestamp(double time)
{
_serverTimestamp = time;
}
#endregion
#region PRIVATE
private void SendTimestamp()
{
var timestamp = RagonTime.CurrentTimestamp();
var value = new DoubleToUInt()
{
Double = timestamp,
};
_writeBuffer.Clear();
_writeBuffer.WriteOperation(RagonOperation.TIMESTAMP_SYNCHRONIZATION);
_writeBuffer.WriteInt((int)value.Int0);
_writeBuffer.WriteInt((int)value.Int1);
}
private void SendRoomUserData()
{
if (_room == null) return;
var props = _room.UserData;
if (!props.Dirty) return;
_writeBuffer.Clear();
_writeBuffer.WriteOperation(RagonOperation.ROOM_DATA_UPDATED);
props.Write(_writeBuffer);
var sendData = _writeBuffer.ToArray();
_connection.Reliable.Send(sendData);
}
private void SendPlayerUserData()
{
if (_playerCache.Local == null) return;
var props = _playerCache.Local.UserData;
if (!props.Dirty) return;
_writeBuffer.Clear();
_writeBuffer.WriteOperation(RagonOperation.PLAYER_DATA_UPDATED);
props.Write(_writeBuffer);
var sendData = _writeBuffer.ToArray();
_connection.Reliable.Send(sendData);
}
private void OnConnected()
{
RagonLog.Trace("Connected");
_listeners.OnConnected();
_state = RagonState.CONNECTED;
}
private void OnDisconnected(RagonDisconnect reason)
{
RagonLog.Trace($"Disconnected: {reason}");
_listeners.OnDisconnected(reason);
_state = RagonState.DISCONNECTED;
}
private void OnData(byte[] data)
{
_readBuffer.Clear();
_readBuffer.FromArray(data);
var operation = _readBuffer.ReadByte();
_handlers[operation].Handle(_readBuffer);
}
#endregion
}
}
+77
View File
@@ -0,0 +1,77 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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();
}
}
+269
View File
@@ -0,0 +1,269 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client
{
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<IRagonOwnershipChangedListener> _ownershipChangedListeners = new();
private readonly List<IRagonPlayerJoinListener> _playerJoinListeners = new();
private readonly List<IRagonPlayerLeftListener> _playerLeftListeners = new();
private readonly List<IRagonRoomListListener> _roomListListeners = new();
private readonly List<IRagonRoomUserDataListener> _roomUserDataListeners = new();
private readonly List<IRagonPlayerUserDataListener> _playerUserDataListeners = new();
private readonly List<Action> _delayedActions = new();
public RagonListenerList(RagonClient client)
{
_client = client;
}
public void Add(IRagonListener listener)
{
_authorizationListeners.Add(listener);
_connectionListeners.Add(listener);
_failedListeners.Add(listener);
_joinListeners.Add(listener);
_leftListeners.Add(listener);
_ownershipChangedListeners.Add(listener);
_playerJoinListeners.Add(listener);
_playerLeftListeners.Add(listener);
_roomUserDataListeners.Add(listener);
_playerUserDataListeners.Add(listener);
}
public void Remove(IRagonListener listener)
{
_delayedActions.Add(() =>
{
_authorizationListeners.Remove(listener);
_connectionListeners.Remove(listener);
_failedListeners.Remove(listener);
_joinListeners.Remove(listener);
_leftListeners.Remove(listener);
_ownershipChangedListeners.Remove(listener);
_playerJoinListeners.Remove(listener);
_playerLeftListeners.Remove(listener);
_roomUserDataListeners.Remove(listener);
_playerUserDataListeners.Remove(listener);
});
}
public void Update()
{
foreach (var action in _delayedActions)
action.Invoke();
_delayedActions.Clear();
}
public void Add(IRagonAuthorizationListener listener)
{
_authorizationListeners.Add(listener);
}
public void Add(IRagonConnectionListener listener)
{
_connectionListeners.Add(listener);
}
public void Add(IRagonFailedListener listener)
{
_failedListeners.Add(listener);
}
public void Add(IRagonJoinListener listener)
{
_joinListeners.Add(listener);
}
public void Add(IRagonLeftListener listener)
{
_leftListeners.Add(listener);
}
public void Add(IRagonOwnershipChangedListener listener)
{
_ownershipChangedListeners.Add(listener);
}
public void Add(IRagonPlayerJoinListener listener)
{
_playerJoinListeners.Add(listener);
}
public void Add(IRagonPlayerLeftListener listener)
{
_playerLeftListeners.Add(listener);
}
public void Add(IRagonRoomListListener listener)
{
_roomListListeners.Add(listener);
}
public void Add(IRagonRoomUserDataListener listener)
{
_roomUserDataListeners.Add(listener);
}
public void Add(IRagonPlayerUserDataListener listener)
{
_playerUserDataListeners.Add(listener);
}
public void Remove(IRagonAuthorizationListener listener)
{
_delayedActions.Add(() => _authorizationListeners.Remove(listener));
}
public void Remove(IRagonConnectionListener listener)
{
_delayedActions.Add(() => _connectionListeners.Remove(listener));
}
public void Remove(IRagonFailedListener listener)
{
_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(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 Remove(IRagonRoomListListener listener)
{
_delayedActions.Add(() => _roomListListeners.Remove(listener));
}
public void Remove(IRagonRoomUserDataListener listener)
{
_delayedActions.Add(() => _roomUserDataListeners.Remove(listener));
}
public void Remove(IRagonPlayerUserDataListener listener)
{
_delayedActions.Add(() => _playerUserDataListeners.Remove(listener));
}
public void OnAuthorizationSuccess(string playerId, string playerName, string payload)
{
foreach (var listener in _authorizationListeners)
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 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);
}
public void OnRoomList(RagonRoomInformation[] roomInfos)
{
foreach (var listListener in _roomListListeners)
listListener.OnRoomListUpdate(_client, roomInfos);
}
public void OnRoomUserData(IReadOnlyList<string> changes)
{
foreach (var userDataListener in _roomUserDataListeners)
userDataListener.OnUserDataUpdated(_client, changes);
}
public void OnPlayerUserData(RagonPlayer player, IReadOnlyList<string> changes)
{
foreach(var playerUserDataListener in _playerUserDataListeners)
playerUserDataListener.OnPlayerUserDataUpdated(_client, player, changes);
}
}
}
+39
View File
@@ -0,0 +1,39 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 IUserData UserData { get; private set; }
public RagonPlayer(ushort peerId, string playerId, string name, bool isRoomOwner, bool isLocal)
{
PeerId = peerId;
IsRoomOwner = isRoomOwner;
IsLocal = isLocal;
Name = name;
Id = playerId;
UserData = isLocal ? new RagonUserData() : new RagonUserDataReadOnly();
}
}
}
+119
View File
@@ -0,0 +1,119 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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)
{
if (_playersById.TryGetValue(playerId, out var player))
return player;
return null;
}
public RagonPlayer? GetPlayerByPeer(ushort peerId)
{
if (_playersByConnection.TryGetValue(peerId, out var player))
return player;
return null;
}
private string _ownerId;
private string _localId;
public void SetOwnerAndLocal(string ownerId, string localId)
{
_ownerId = ownerId;
_localId = localId;
}
public RagonPlayer AddPlayer(ushort peerId, string playerId, string playerName)
{
if (_playersById.ContainsKey(playerId))
return null;
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);
return 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();
}
public void Dump()
{
RagonLog.Trace("Players: ");
RagonLog.Trace("[Connection] [ID] [Name]");
foreach (var player in _players)
{
RagonLog.Trace($"[{player.PeerId}] {player.Id} {player.Name}");
}
}
}
+199
View File
@@ -0,0 +1,199 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client
{
public class RagonRoom
{
private class EventSubscription : IDisposable
{
private List<Action<RagonPlayer, IRagonEvent>> _callbacks;
private List<Action<RagonPlayer, IRagonEvent>> _localCallbacks;
private Action<RagonPlayer, IRagonEvent> _callback;
public EventSubscription(
List<Action<RagonPlayer, IRagonEvent>> callbacks,
List<Action<RagonPlayer, IRagonEvent>> localCallbacks,
Action<RagonPlayer, IRagonEvent> callback)
{
_callbacks = callbacks;
_localCallbacks = localCallbacks;
_callback = callback;
}
public void Dispose()
{
_callbacks?.Remove(_callback);
_localCallbacks?.Remove(_callback);
_callbacks = null!;
_localCallbacks = null!;
_callback = null!;
}
}
private delegate void OnEventDelegate(RagonPlayer player, RagonStream serializer);
private readonly RagonClient _client;
private readonly RagonPlayerCache _playerCache;
private RoomParameters _parameters;
private RagonUserData _userData;
public string Id => _parameters.RoomId;
public int MinPlayers => _parameters.Min;
public int MaxPlayers => _parameters.Max;
public IReadOnlyList<RagonPlayer> Players => _playerCache.Players;
public RagonPlayer Local => _playerCache.Local;
public RagonPlayer Owner => _playerCache.Owner;
public RagonUserData UserData => _userData;
private readonly Dictionary<int, OnEventDelegate> _events = new();
private readonly Dictionary<int, List<Action<RagonPlayer, IRagonEvent>>> _localListeners = new();
private readonly Dictionary<int, List<Action<RagonPlayer, IRagonEvent>>> _listeners = new();
public RagonRoom(RagonClient client, RagonPlayerCache playerCache)
{
_client = client;
_playerCache = playerCache;
}
public void Reset(RoomParameters parameters)
{
Clear();
_userData = new RagonUserData();
_parameters = parameters;
_playerCache.Cleanup();
}
internal void HandleEvent(ushort eventCode, RagonPlayer caller, RagonStream buffer)
{
if (_events.TryGetValue(eventCode, out var evnt))
evnt?.Invoke(caller, buffer);
else
RagonLog.Warn($"Handler event {Id} with eventCode {eventCode} not defined");
}
internal void HandleUserData(RagonStream buffer)
{
_userData.Read(buffer);
}
public IDisposable OnEvent<TEvent>(Action<RagonPlayer, TEvent> callback) where TEvent : IRagonEvent, new()
{
var t = new TEvent();
var eventCode = _client.Event.GetEventCode(t);
var action = (RagonPlayer player, IRagonEvent eventData) => callback.Invoke(player, (TEvent)eventData);
if (!_listeners.TryGetValue(eventCode, out var callbacks))
{
callbacks = new List<Action<RagonPlayer, IRagonEvent>>();
_listeners.Add(eventCode, callbacks);
}
if (!_localListeners.TryGetValue(eventCode, out var localCallbacks))
{
localCallbacks = new List<Action<RagonPlayer, IRagonEvent>>();
_localListeners.Add(eventCode, localCallbacks);
}
callbacks.Add(action);
localCallbacks.Add(action);
if (!_events.ContainsKey(eventCode))
{
_events.Add(eventCode, (player, serializer) =>
{
t.Deserialize(serializer);
foreach (var callbackListener in callbacks)
callbackListener.Invoke(player, t);
});
}
return new EventSubscription(callbacks, localCallbacks, action);
}
public void ReplicateEvent<TEvent>(TEvent evnt, RagonTarget target, RagonReplicationMode replicationMode)
where TEvent : IRagonEvent, new()
{
var evntId = _client.Event.GetEventCode(evnt);
var buffer = _client.Buffer;
{
if (replicationMode == RagonReplicationMode.Local &&
_localListeners.TryGetValue(evntId, out var localListeners))
{
foreach (var listener in localListeners)
listener.Invoke(_client.Room.Local, evnt);
return;
}
}
{
if (replicationMode == RagonReplicationMode.LocalAndServer &&
_localListeners.TryGetValue(evntId, out var localListeners))
{
foreach (var listener in localListeners)
listener.Invoke(_client.Room.Local, evnt);
}
}
buffer.Clear();
buffer.WriteOperation(RagonOperation.REPLICATE_ROOM_EVENT);
buffer.WriteUShort(evntId);
buffer.WriteByte((byte)replicationMode);
buffer.WriteByte((byte)target);
evnt.Serialize(buffer);
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void ReplicateEvent<TEvent>(TEvent evnt, RagonPlayer target, RagonReplicationMode replicationMode)
where TEvent : IRagonEvent, new()
{
var evntId = _client.Event.GetEventCode(evnt);
var buffer = _client.Buffer;
buffer.Clear();
buffer.WriteOperation(RagonOperation.REPLICATE_ROOM_EVENT);
buffer.WriteUShort(evntId);
buffer.WriteByte((byte)replicationMode);
buffer.WriteByte((byte)RagonTarget.Player);
buffer.WriteUShort(target.PeerId);
evnt.Serialize(buffer);
var sendData = buffer.ToArray();
_client.Reliable.Send(sendData);
}
public void Clear()
{
_events.Clear();
_listeners.Clear();
_localListeners.Clear();
}
}
}
@@ -0,0 +1,12 @@
namespace Ragon.Client
{
public struct RagonRoomInformation
{
public string Id;
public string Scene;
public int PlayerMax;
public int PlayerMin;
public int PlayerCount;
public Dictionary<string, byte[]> Properties;
}
}
+109
View File
@@ -0,0 +1,109 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client
{
public class RagonSession
{
private readonly RagonClient _client;
private readonly RagonStream _buffer;
public RagonSession(RagonClient client, RagonStream buffer)
{
_client = client;
_buffer = buffer;
}
public void CreateOrJoin(string sessionName, int minPlayers, int maxPlayers)
{
var parameters = new RagonRoomParameters() { 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 sessionName, int minPlayers, int maxPlayers)
{
Create(null, new RagonRoomParameters() { Min = minPlayers, Max = maxPlayers });
}
public void Create(string roomId, string sessionName, int minPlayers, int maxPlayers)
{
Create(roomId, new RagonRoomParameters() { 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-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Client
{
public enum RagonState
{
DISCONNECTED,
CONNECTED,
ROOM,
LOBBY,
}
}
+104
View File
@@ -0,0 +1,104 @@
/*
* Copyright 2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client
{
public class RagonUserData : IUserData
{
public byte[] this[string key]
{
get => _properties[key];
set
{
_properties[key] = value;
if (!_changesCache.ContainsKey(key))
{
_localChanges.Add(key);
_changesCache.Add(key, true);
}
}
}
public void Remove(string key)
{
if (_properties.Remove(key))
{
if (!_changesCache.ContainsKey(key))
{
_localChanges.Add(key);
_changesCache.Add(key, true);
}
}
}
public bool Dirty => _localChanges.Count > 0;
private readonly List<string> _localChanges = new();
private readonly Dictionary<string, bool> _changesCache = new();
private readonly Dictionary<string, byte[]> _properties = new();
public RagonUserData()
{
}
public IReadOnlyList<string> Read(RagonStream buffer)
{
var len = buffer.ReadUShort();
var changes = new List<string>(len);
for (int i = 0; i < len; i++)
{
var key = buffer.ReadString();
var valueSize = buffer.ReadUShort();
if (valueSize > 0)
{
var value = buffer.ReadBinary(valueSize);
_properties[key] = value;
}
else
{
_properties.Remove(key);
}
changes.Add(key);
}
return changes;
}
public void Write(RagonStream buffer)
{
buffer.WriteUShort((ushort)_localChanges.Count);
foreach (var propertyChanged in _localChanges)
{
buffer.WriteString(propertyChanged);
if (_properties.TryGetValue(propertyChanged, out var property))
{
buffer.WriteUShort((ushort)property.Length);
buffer.WriteBinary(property);
}
else
{
buffer.WriteUShort(0);
}
}
_localChanges.Clear();
}
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Ragon.Protocol;
namespace Ragon.Client;
public class RagonUserDataReadOnly : IUserData
{
public byte[] this[string key]
{
get => _properties[key];
set { }
}
public bool Dirty => _dirty;
private bool _dirty = false;
private readonly Dictionary<string, byte[]> _properties = new();
public RagonUserDataReadOnly()
{
}
public void Write(RagonStream buffer)
{
}
public IReadOnlyList<string> Read(RagonStream buffer)
{
var len = buffer.ReadUShort();
var changes = new List<string>(len);
for (int i = 0; i < len; i++)
{
var key = buffer.ReadString();
var valueSize = buffer.ReadUShort();
if (valueSize > 0)
{
var value = buffer.ReadBinary(valueSize);
_properties[key] = value;
}
else
{
_properties.Remove(key);
}
changes.Add(key);
}
return changes;
}
}
-6
View File
@@ -1,6 +0,0 @@
namespace Ragon.Core.Time;
public interface IAction
{
public void Tick();
}
-28
View File
@@ -1,28 +0,0 @@
namespace Ragon.Core.Time;
public class Loop
{
private List<IAction> _tasks;
public Loop()
{
_tasks = new List<IAction>(35);
}
public void Run(IAction task)
{
_tasks.Add(task);
}
public void Stop(IAction task)
{
_tasks.Remove(task);
}
public void Tick()
{
foreach (var task in _tasks)
task.Tick();
}
}
-115
View File
@@ -1,115 +0,0 @@
using System.Diagnostics;
using NLog;
using Ragon.Common;
using Ragon.Core.Lobby;
using Ragon.Core.Server;
using Ragon.Core.Time;
using Ragon.Server;
using Ragon.Server.ENet;
namespace Ragon.Core;
public class Application : INetworkListener
{
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly INetworkServer _server;
private readonly Thread _dedicatedThread;
private readonly Executor _executor;
private readonly Configuration _configuration;
private readonly HandlerRegistry _handlerRegistry;
private readonly ILobby _lobby;
private readonly Loop _loop;
private readonly Dictionary<ushort, PlayerContext> _contexts;
public Application(Configuration configuration)
{
_configuration = configuration;
_executor = new Executor();
_dedicatedThread = new Thread(Execute);
_dedicatedThread.IsBackground = true;
_contexts = new Dictionary<ushort, PlayerContext>();
_handlerRegistry = new HandlerRegistry();
_lobby = new LobbyInMemory();
_loop = new Loop();
if (configuration.ServerType == "enet")
_server = new ENetServer();
if (configuration.ServerType == "websocket")
_server = new NativeWebSocketServer(_executor);
Debug.Assert(_server != null, $"Socket type not supported: {configuration.ServerType}. Supported: [enet, websocket]");
}
public void Execute()
{
while (true)
{
_executor.Execute();
_loop.Tick();
_server.Poll();
Thread.Sleep((int)1000.0f / _configuration.ServerTickRate);
}
}
public void Start()
{
var networkConfiguration = new NetworkConfiguration()
{
LimitConnections = _configuration.LimitConnections,
Protocol = RagonVersion.Parse(_configuration.GameProtocol),
Address = "0.0.0.0",
Port = _configuration.Port,
};
_server.Start(this, networkConfiguration);
_dedicatedThread.Start();
}
public void Stop()
{
_server.Stop();
_dedicatedThread.Interrupt();
}
public void OnConnected(INetworkConnection connection)
{
var context = new PlayerContext(connection, new LobbyPlayer(connection));
context.Lobby = _lobby;
context.Loop = _loop;
_logger.Trace($"Connected {connection.Id}");
_contexts.Add(connection.Id, context);
}
public void OnDisconnected(INetworkConnection connection)
{
_logger.Trace($"Disconnected {connection.Id}");
if (_contexts.Remove(connection.Id, out var context))
{
var room = context.Room;
if (room != null)
{
room.RemovePlayer(context.RoomPlayer);
_lobby.RemoveIfEmpty(room);
}
context.Dispose();
}
}
public void OnTimeout(INetworkConnection connection)
{
if (_contexts.Remove(connection.Id, out var context))
context.Dispose();
}
public void OnData(INetworkConnection connection, byte[] data)
{
if (_contexts.TryGetValue(connection.Id, out var context))
_handlerRegistry.Handle(context, data);
}
}
-43
View File
@@ -1,43 +0,0 @@
using Newtonsoft.Json;
using NLog;
namespace Ragon.Core;
[Serializable]
public struct Configuration
{
public string ServerKey;
public string ServerType;
public ushort ServerTickRate;
public string GameProtocol;
public ushort Port;
public int LimitConnections;
public int LimitPlayersPerRoom;
public int LimitRooms;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static readonly string ServerVersion = "1.0.31-rc";
private static void CopyrightInfo()
{
Logger.Info($"Server Version: {ServerVersion}");
Logger.Info($"Machine Name: {Environment.MachineName}");
Logger.Info($"OS: {Environment.OSVersion}");
Logger.Info($"Processors: {Environment.ProcessorCount}");
Logger.Info($"Runtime Version: {Environment.Version}");
Logger.Info("==================================");
Logger.Info("| |");
Logger.Info("| Ragon |");
Logger.Info("| |");
Logger.Info("==================================");
}
public static Configuration Load(string filePath)
{
CopyrightInfo();
var data = File.ReadAllText(filePath);
var configuration = JsonConvert.DeserializeObject<Configuration>(data);
return configuration;
}
}
-188
View File
@@ -1,188 +0,0 @@
using Ragon.Common;
namespace Ragon.Core.Game;
public class Entity
{
private static ushort _idGenerator = 0;
public ushort Id { get; private set; }
public ushort Type { get; private set; }
public ushort StaticId { get; private set; }
public RoomPlayer Owner { get; private set; }
public RagonAuthority Authority { get; private set; }
public EntityState State { get; private set; }
public byte[] Payload { get; private set; }
private readonly List<EntityEvent> _bufferedEvents;
public Entity(RoomPlayer owner, ushort type, ushort staticId, RagonAuthority eventAuthority)
{
Owner = owner;
StaticId = staticId;
Type = type;
Id = _idGenerator++;
Payload = Array.Empty<byte>();
Authority = eventAuthority;
State = new EntityState(this);
_bufferedEvents = new List<EntityEvent>();
}
public void SetPayload(byte[] payload)
{
Payload = payload;
}
public void SetOwner(RoomPlayer owner)
{
Owner = owner;
}
public void RestoreBufferedEvents(RoomPlayer roomPlayer, RagonSerializer writer)
{
foreach (var bufferedEvent in _bufferedEvents)
{
writer.Clear();
writer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
writer.WriteUShort(bufferedEvent.EventId);
writer.WriteUShort(bufferedEvent.Invoker.Connection.Id);
writer.WriteByte((byte)RagonReplicationMode.Server);
writer.WriteUShort(Id);
ReadOnlySpan<byte> data = bufferedEvent.EventData.AsSpan();
writer.WriteData(ref data);
var sendData = writer.ToArray();
roomPlayer.Connection.Reliable.Send(sendData);
}
}
public void Create()
{
var room = Owner.Room;
var serializer = room.Writer;
serializer.Clear();
serializer.WriteOperation(RagonOperation.CREATE_ENTITY);
serializer.WriteUShort(Type);
serializer.WriteUShort(Id);
serializer.WriteUShort(Owner.Connection.Id);
ReadOnlySpan<byte> entityPayload = Payload.AsSpan();
serializer.WriteUShort((ushort)entityPayload.Length);
serializer.WriteData(ref entityPayload);
var sendData = serializer.ToArray();
foreach (var player in room.ReadyPlayersList)
player.Connection.Reliable.Send(sendData);
}
public void Destroy(byte[] payload)
{
var room = Owner.Room;
var serializer = room.Writer;
serializer.Clear();
serializer.WriteOperation(RagonOperation.DESTROY_ENTITY);
serializer.WriteInt(Id);
serializer.WriteUShort(0);
// serializer.WriteData(ref Payload);
var sendData = serializer.ToArray();
foreach (var player in room.ReadyPlayersList)
player.Connection.Reliable.Send(sendData);
}
public void ReplicateEvent(
RoomPlayer caller,
ushort eventId,
ReadOnlySpan<byte> payload,
RagonReplicationMode eventMode,
RoomPlayer targetPlayer
)
{
var room = Owner.Room;
var serializer = room.Writer;
serializer.Clear();
serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
serializer.WriteUShort(eventId);
serializer.WriteUShort(caller.Connection.Id);
serializer.WriteByte((byte)eventMode);
serializer.WriteUShort(Id);
serializer.WriteData(ref payload);
var sendData = serializer.ToArray();
targetPlayer.Connection.Reliable.Send(sendData);
}
public void ReplicateEvent(
RoomPlayer caller,
ushort eventId,
ReadOnlySpan<byte> payload,
RagonReplicationMode eventMode,
RagonTarget targetMode
)
{
if (Authority == RagonAuthority.OwnerOnly &&
Owner.Connection.Id != caller.Connection.Id)
{
Console.WriteLine($"Player have not enought authority for event with Id {eventId}");
return;
}
if (eventMode == RagonReplicationMode.Buffered && targetMode != RagonTarget.Owner)
{
var bufferedEvent = new EntityEvent(caller, eventId, payload.ToArray(), targetMode);
_bufferedEvents.Add(bufferedEvent);
}
var room = Owner.Room;
var serializer = room.Writer;
serializer.Clear();
serializer.WriteOperation(RagonOperation.REPLICATE_ENTITY_EVENT);
serializer.WriteUShort(eventId);
serializer.WriteUShort(caller.Connection.Id);
serializer.WriteByte((byte)eventMode);
serializer.WriteUShort(Id);
serializer.WriteData(ref payload);
var sendData = serializer.ToArray();
switch (targetMode)
{
case RagonTarget.Owner:
{
Owner.Connection.Reliable.Send(sendData);
break;
}
case RagonTarget.ExceptOwner:
{
foreach (var roomPlayer in room.ReadyPlayersList)
{
if (roomPlayer.Connection.Id != Owner.Connection.Id)
roomPlayer.Connection.Reliable.Send(sendData);
}
break;
}
case RagonTarget.ExceptInvoker:
{
foreach (var roomPlayer in room.ReadyPlayersList)
{
if (roomPlayer.Connection.Id != caller.Connection.Id)
roomPlayer.Connection.Reliable.Send(sendData);
}
break;
}
case RagonTarget.All:
{
foreach (var roomPlayer in room.ReadyPlayersList)
roomPlayer.Connection.Reliable.Send(sendData);
break;
}
}
}
}
-24
View File
@@ -1,24 +0,0 @@
using Ragon.Common;
namespace Ragon.Core.Game;
public class EntityEvent
{
public RoomPlayer Invoker { get; private set; }
public ushort EventId { get; private set; }
public byte[] EventData { get; private set; }
public RagonTarget Target { set; private get; }
public EntityEvent(
RoomPlayer invoker,
ushort eventId,
byte[] payload,
RagonTarget target
)
{
Invoker = invoker;
EventId = eventId;
EventData = payload;
Target = target;
}
}
-34
View File
@@ -1,34 +0,0 @@
namespace Ragon.Core.Game;
public class EntityList
{
private readonly List<Entity> _dynamicEntitiesList = new List<Entity>();
private readonly List<Entity> _staticEntitiesList = new List<Entity>();
private readonly Dictionary<ushort, Entity> _entitiesMap = new Dictionary<ushort, Entity>();
public IReadOnlyList<Entity> StaticList => _staticEntitiesList;
public IReadOnlyList<Entity> DynamicList => _dynamicEntitiesList;
public IReadOnlyDictionary<ushort, Entity> Map => _entitiesMap;
public void Add(Entity entity)
{
if (entity.StaticId != 0)
_staticEntitiesList.Add(entity);
else
_dynamicEntitiesList.Add(entity);
_entitiesMap.Add(entity.Id, entity);
}
public bool Remove(Entity entity)
{
if (_entitiesMap.Remove(entity.Id, out var existEntity))
{
_staticEntitiesList.Remove(entity);
_dynamicEntitiesList.Remove(entity);
return true;
}
return false;
}
}
-99
View File
@@ -1,99 +0,0 @@
using NLog;
using Ragon.Common;
namespace Ragon.Core.Game;
public class EntityState
{
private List<EntityStateProperty> _properties;
private Entity _entity;
public EntityState(Entity entity, int capacity = 10)
{
_entity = entity;
_properties = new List<EntityStateProperty>(10);
}
public void AddProperty(EntityStateProperty property)
{
_properties.Add(property);
}
public void Write(RagonSerializer serializer)
{
serializer.WriteUShort(_entity.Id);
for (int propertyIndex = 0; propertyIndex < _properties.Count; propertyIndex++)
{
var property = _properties[propertyIndex];
if (property.IsDirty)
{
serializer.WriteBool(true);
var span = serializer.GetWritableData(property.Size);
var data = property.Read();
data.CopyTo(span);
property.Clear();
}
else
{
serializer.WriteBool(false);
}
}
}
public void Read(RagonSerializer serializer)
{
for (var i = 0; i < _properties.Count; i++)
{
if (serializer.ReadBool())
{
var property = _properties[i];
var size = property.Size;
if (!property.IsFixed)
size = serializer.ReadUShort();
if (size > property.Capacity)
{
Console.WriteLine($"Property {i} payload too large, size: {size}");
continue;
}
var propertyPayload = serializer.ReadData(size);
property.Write(ref propertyPayload);
property.Size = size;
}
}
}
public void Snapshot(RagonSerializer serializer)
{
ReadOnlySpan<byte> payload = _entity.Payload.AsSpan();
serializer.WriteUShort(_entity.Type);
serializer.WriteUShort(_entity.Id);
if (_entity.StaticId != 0)
serializer.WriteUShort(_entity.StaticId);
serializer.WriteUShort(_entity.Owner.Connection.Id);
serializer.WriteUShort((ushort) payload.Length);
serializer.WriteData(ref payload);
for (int propertyIndex = 0; propertyIndex < _properties.Count; propertyIndex++)
{
var property = _properties[propertyIndex];
var hasPayload = property.IsFixed || property.Size > 0 && !property.IsFixed;
if (hasPayload)
{
serializer.WriteBool(true);
var span = serializer.GetWritableData(property.Size);
var data = property.Read();
data.CopyTo(span);
}
else
{
serializer.WriteBool(false);
}
}
}
}
-41
View File
@@ -1,41 +0,0 @@
using System;
using Ragon.Common;
namespace Ragon.Core.Game;
public class EntityStateProperty
{
public int Size { get; set; }
public int Capacity { get; set; }
public bool IsDirty { get; private set; }
public bool IsFixed { get; private set; }
private byte[] _data;
public EntityStateProperty(int size, bool isFixed)
{
Capacity = 512;
Size = size;
IsFixed = isFixed;
IsDirty = true;
_data = new byte[Capacity];
}
public ReadOnlySpan<byte> Read()
{
var dataSpan = _data.AsSpan();
var src = dataSpan.Slice(0, Size);
return src;
}
public void Write(ref ReadOnlySpan<byte> src)
{
src.CopyTo(_data);
IsDirty = true;
}
public void Clear()
{
IsDirty = false;
}
}
-166
View File
@@ -1,166 +0,0 @@
using Ragon.Common;
using Ragon.Core.Time;
namespace Ragon.Core.Game;
public class Room: IAction
{
public string Id { get; private set; }
public RoomInformation Info { get; private set; }
public RoomPlayer Owner { get; private set; }
public RagonSerializer Writer { get; }
public Dictionary<ushort, RoomPlayer> Players { get; private set; }
public List<RoomPlayer> WaitPlayersList { get; private set; }
public List<RoomPlayer> ReadyPlayersList { get; private set; }
public List<RoomPlayer> PlayerList { get; private set; }
public Dictionary<ushort, Entity> Entities { get; private set; }
public List<Entity> DynamicEntitiesList { get; private set; }
public List<Entity> StaticEntitiesList { get; private set; }
public List<Entity> EntityList { get; private set; }
private readonly HashSet<Entity> _entitiesDirtySet;
public Room(string roomId, RoomInformation info)
{
Id = roomId;
Info = info;
Players = new Dictionary<ushort, RoomPlayer>(info.Max);
WaitPlayersList = new List<RoomPlayer>(info.Max);
ReadyPlayersList = new List<RoomPlayer>(info.Max);
PlayerList = new List<RoomPlayer>(info.Max);
Entities = new Dictionary<ushort, Entity>();
DynamicEntitiesList = new List<Entity>();
StaticEntitiesList = new List<Entity>();
EntityList = new List<Entity>();
_entitiesDirtySet = new HashSet<Entity>();
Writer = new RagonSerializer(512);
}
public void AttachEntity(RoomPlayer newOwner, Entity entity)
{
Entities.Add(entity.Id, entity);
EntityList.Add(entity);
if (entity.StaticId == 0)
DynamicEntitiesList.Add(entity);
else
StaticEntitiesList.Add(entity);
entity.Create();
newOwner.Entities.Add(entity);
}
public void DetachEntity(RoomPlayer currentOwner, Entity entity, byte[] payload)
{
Entities.Remove(entity.Id);
EntityList.Remove(entity);
StaticEntitiesList.Remove(entity);
DynamicEntitiesList.Remove(entity);
_entitiesDirtySet.Remove(entity);
entity.Destroy(payload);
currentOwner.Entities.Remove(entity);
}
public void Tick()
{
var entities = (ushort) _entitiesDirtySet.Count;
if (entities > 0)
{
Writer.Clear();
Writer.WriteOperation(RagonOperation.REPLICATE_ENTITY_STATE);
Writer.WriteUShort(entities);
foreach (var entity in _entitiesDirtySet)
entity.State.Write(Writer);
_entitiesDirtySet.Clear();
var sendData = Writer.ToArray();
foreach (var roomPlayer in ReadyPlayersList)
roomPlayer.Connection.Unreliable.Send(sendData);
}
}
public void AddPlayer(RoomPlayer player)
{
if (Players.Count == 0)
Owner = player;
player.Attach(this);
PlayerList.Add(player);
Players.Add(player.Connection.Id, player);
}
public void RemovePlayer(RoomPlayer roomPlayer)
{
if (Players.Remove(roomPlayer.Connection.Id, out var player))
{
PlayerList.Remove(player);
{
Writer.Clear();
Writer.WriteOperation(RagonOperation.PLAYER_LEAVED);
Writer.WriteString(player.Id);
var entitiesToDelete = player.Entities.DynamicList;
Writer.WriteUShort((ushort) entitiesToDelete.Count);
foreach (var entity in entitiesToDelete)
{
Writer.WriteUShort(entity.Id);
EntityList.Remove(entity);
}
var sendData = Writer.ToArray();
Broadcast(sendData);
}
if (roomPlayer.Connection.Id == Owner.Connection.Id && PlayerList.Count > 0)
{
var nextOwner = PlayerList[0];
Owner = nextOwner;
var entitiesToUpdate = roomPlayer.Entities.StaticList;
Writer.Clear();
Writer.WriteOperation(RagonOperation.OWNERSHIP_CHANGED);
Writer.WriteString(Owner.Id);
Writer.WriteUShort((ushort) entitiesToUpdate.Count);
foreach (var entity in entitiesToUpdate)
{
Writer.WriteUShort(entity.Id);
entity.SetOwner(nextOwner);
nextOwner.Entities.Add(entity);
}
var sendData = Writer.ToArray();
Broadcast(sendData);
}
}
}
public void UpdateReadyPlayerList()
{
ReadyPlayersList = PlayerList.Where(p => p.IsLoaded).ToList();
}
public void Track(Entity entity)
{
_entitiesDirtySet.Add(entity);
}
public void Broadcast(byte[] data)
{
foreach (var readyPlayer in ReadyPlayersList)
readyPlayer.Connection.Reliable.Send(data);
}
}
-13
View File
@@ -1,13 +0,0 @@
namespace Ragon.Core.Game;
public class RoomInformation
{
public string Map { get; init; } = "none";
public int Min { get; init; }
public int Max { get; init; }
public override string ToString()
{
return $"Map: {Map} Count: {Min}/{Max}";
}
}
-36
View File
@@ -1,36 +0,0 @@
using Ragon.Server;
namespace Ragon.Core.Game;
public class RoomPlayer
{
public INetworkConnection Connection { get; }
public string Id { get; }
public string Name { get; }
public bool IsLoaded { get; private set; }
public Room Room { get; private set; }
public EntityList Entities { get; private set; }
public RoomPlayer(INetworkConnection connection, string id, string name)
{
Id = id;
Name = name;
Connection = connection;
Entities = new EntityList();
}
public void Attach(Room room)
{
Room = room;
}
public void Detach()
{
Room = null!;
}
public void SetReady()
{
IsLoaded = true;
}
}
-109
View File
@@ -1,109 +0,0 @@
using NLog;
using Ragon.Common;
using Ragon.Core.Handlers;
namespace Ragon.Core;
public sealed class HandlerRegistry
{
private IHandler _entityEventHandler;
private IHandler _entityCreateHandler;
private IHandler _entityDestroyHandler;
private IHandler _entityStateHandler;
private IHandler _sceneLoadedHandler;
private IHandler _authorizationHandler;
private IHandler _joinOrCreateHandler;
private IHandler _createHandler;
private IHandler _joinHandler;
private IHandler _leaveHandler;
private Logger _logger = LogManager.GetCurrentClassLogger();
private RagonSerializer _reader;
private RagonSerializer _writer;
public HandlerRegistry()
{
_reader = new RagonSerializer(2048);
_writer = new RagonSerializer(2048);
_authorizationHandler = new AuthorizationHandler();
_joinOrCreateHandler = new JoinOrCreateHandler();
_sceneLoadedHandler = new SceneLoadedHandler();
_createHandler = new CreateHandler();
_joinHandler = new JoinHandler();
_leaveHandler = new LeaveHandler();
_entityEventHandler = new EntityEventHandler();
_entityCreateHandler = new EntityCreateHandler();
_entityDestroyHandler = new EntityDestroyHandler();
_entityStateHandler = new EntityStateHandler();
}
public void Handle(PlayerContext context, byte[] data)
{
_writer.Clear();
_reader.Clear();
_reader.FromArray(data);
var operation = _reader.ReadOperation();
switch (operation)
{
case RagonOperation.REPLICATE_ENTITY_EVENT:
{
if (context.RoomPlayer != null)
_entityEventHandler.Handle(context, _reader, _writer);
break;
}
case RagonOperation.REPLICATE_ENTITY_STATE:
{
if (context.RoomPlayer != null)
_entityStateHandler.Handle(context, _reader, _writer);
break;
}
case RagonOperation.CREATE_ENTITY:
{
if (context.RoomPlayer != null)
_entityCreateHandler.Handle(context, _reader, _writer);
break;
}
case RagonOperation.DESTROY_ENTITY:
{
if (context.RoomPlayer != null)
_entityDestroyHandler.Handle(context, _reader, _writer);
break;
}
case RagonOperation.SCENE_LOADED:
{
if (context.RoomPlayer != null)
_sceneLoadedHandler.Handle(context, _reader, _writer);
break;
}
case RagonOperation.JOIN_OR_CREATE_ROOM:
{
_joinOrCreateHandler.Handle(context, _reader, _writer);
break;
}
case RagonOperation.CREATE_ROOM:
{
_createHandler.Handle(context, _reader, _writer);
break;
}
case RagonOperation.JOIN_ROOM:
{
_joinHandler.Handle(context, _reader, _writer);
break;
}
case RagonOperation.LEAVE_ROOM:
{
_leaveHandler.Handle(context, _reader, _writer);
break;
}
case RagonOperation.AUTHORIZE:
{
_authorizationHandler.Handle(context, _reader, _writer);
break;
}
}
}
}
-8
View File
@@ -1,8 +0,0 @@
using Ragon.Common;
namespace Ragon.Core;
public interface IHandler
{
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer);
}
@@ -1,39 +0,0 @@
using NLog;
using Ragon.Common;
using Ragon.Core.Lobby;
namespace Ragon.Core.Handlers;
public sealed class AuthorizationHandler: IHandler
{
private Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
{
if (context.LobbyPlayer.Status == LobbyPlayerStatus.Authorized)
{
_logger.Warn("Player already authorized");
return;
}
var key = reader.ReadString();
var playerName = reader.ReadString();
var additionalData = reader.ReadData(reader.Size);
context.LobbyPlayer.Name = playerName;
context.LobbyPlayer.AdditionalData = additionalData.ToArray();
context.LobbyPlayer.Status = LobbyPlayerStatus.Authorized;
var playerId = context.LobbyPlayer.Id;
writer.Clear();
writer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS);
writer.WriteString(playerId);
writer.WriteString(playerName);
var sendData = writer.ToArray();
context.Connection.Reliable.Send(sendData);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} authorized");
}
}
@@ -1,32 +0,0 @@
using NLog;
using Ragon.Common;
using Ragon.Core.Game;
namespace Ragon.Core.Handlers;
public sealed class EntityCreateHandler: IHandler
{
private Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
{
var entityType = reader.ReadUShort();
var eventAuthority = (RagonAuthority) reader.ReadByte();
var propertiesCount = reader.ReadUShort();
var entity = new Entity(context.RoomPlayer, entityType, 0, eventAuthority);
for (var i = 0; i < propertiesCount; i++)
{
var propertyType = reader.ReadBool();
var propertySize = reader.ReadUShort();
entity.State.AddProperty(new EntityStateProperty(propertySize, propertyType));
}
var entityPayload = reader.ReadData(reader.Size);
entity.SetPayload(entityPayload.ToArray());
context.Room.AttachEntity(context.RoomPlayer, entity);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} created entity {entity.Id}:{entity.Type}");
}
}
@@ -1,21 +0,0 @@
using NLog;
using Ragon.Common;
namespace Ragon.Core.Handlers;
public sealed class EntityDestroyHandler: IHandler
{
private Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
{
var entityId = reader.ReadUShort();
if (context.Room.Entities.TryGetValue(entityId, out var entity))
{
var player = context.RoomPlayer;
var payload = reader.ReadData(reader.Size);
context.Room.DetachEntity(player, entity, Array.Empty<byte>());
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} destoyed entity {entity.Id}");
}
}
}
-47
View File
@@ -1,47 +0,0 @@
using NLog;
using Ragon.Common;
namespace Ragon.Core.Handlers;
public sealed class EntityEventHandler: IHandler
{
private Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
{
var player = context.RoomPlayer;
var room = context.Room;
var entityId = reader.ReadUShort();
if (!room.Entities.TryGetValue(entityId, out var ent))
{
_logger.Warn($"Entity not found for event with Id {entityId}");
return;
}
var eventId = reader.ReadUShort();
var eventMode = (RagonReplicationMode) reader.ReadByte();
var targetMode = (RagonTarget) reader.ReadByte();
var payloadData = reader.ReadData(reader.Size);
var targetPlayerPeerId = reader.ReadUShort();
if (targetMode == RagonTarget.Player && context.Room.Players.TryGetValue(targetPlayerPeerId, out var targetPlayer))
{
Span<byte> payloadRaw = stackalloc byte[payloadData.Length];
ReadOnlySpan<byte> payload = payloadRaw;
payloadData.CopyTo(payloadRaw);
_logger.Trace($"Event {eventId} Payload: {payloadData.Length} to {targetMode}");
ent.ReplicateEvent(player, eventId, payload, eventMode, targetPlayer);
}
else
{
Span<byte> payloadRaw = stackalloc byte[payloadData.Length];
ReadOnlySpan<byte> payload = payloadRaw;
payloadData.CopyTo(payloadRaw);
_logger.Trace($"Event {eventId} Payload: {payloadData.Length} to {targetMode}");
ent.ReplicateEvent(player, eventId, payload, eventMode, targetMode);
}
}
}
-29
View File
@@ -1,29 +0,0 @@
using NLog;
using Ragon.Common;
namespace Ragon.Core.Handlers;
public sealed class EntityStateHandler: IHandler
{
private ILogger _logger = LogManager.GetCurrentClassLogger();
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
{
var room = context.Room;
var entitiesCount = reader.ReadUShort();
for (var entityIndex = 0; entityIndex < entitiesCount; entityIndex++)
{
var entityId = reader.ReadUShort();
if (room.Entities.TryGetValue(entityId, out var entity))
{
entity.State.Read(reader);
room.Track(entity);
}
else
{
_logger.Error($"Entity with Id {entityId} not found, replication interrupted");
}
}
}
}
-84
View File
@@ -1,84 +0,0 @@
using NLog;
using Ragon.Common;
using Ragon.Core.Game;
using Ragon.Core.Lobby;
namespace Ragon.Core.Handlers;
public sealed class CreateHandler: IHandler
{
private RagonRoomParameters _roomParameters = new();
private Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
{
if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized)
{
_logger.Warn($"Player {context.Connection.Id} not authorized for this request");
return;
}
var custom = reader.ReadBool();
var roomId = Guid.NewGuid().ToString();
if (custom)
{
roomId = reader.ReadString();
if (context.Lobby.FindRoomById(roomId, out _))
{
writer.Clear();
writer.WriteOperation(RagonOperation.JOIN_FAILED);
writer.WriteString($"Room with id {roomId} already exists");
var sendData = writer.ToArray();
context.Connection.Reliable.Send(sendData);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} join failed to room {roomId}, room already exist");
return;
}
}
_roomParameters.Deserialize(reader);
var information = new RoomInformation()
{
Map = _roomParameters.Map,
Max = _roomParameters.Max,
Min = _roomParameters.Min,
};
var lobbyPlayer = context.LobbyPlayer;
var roomPlayer = new RoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
var room = new Room(roomId, information);
room.AddPlayer(roomPlayer);
context.Room?.RemovePlayer(context.RoomPlayer);
context.Room = room;
context.RoomPlayer = roomPlayer;
context.Lobby.Persist(room);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} {information}");
JoinSuccess(roomPlayer, room, writer);
context.Loop.Run(room);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} joined to room {room.Id}");
}
private void JoinSuccess(RoomPlayer player, Room room, RagonSerializer writer)
{
writer.Clear();
writer.WriteOperation(RagonOperation.JOIN_SUCCESS);
writer.WriteString(room.Id);
writer.WriteString(player.Id);
writer.WriteString(room.Owner.Id);
writer.WriteUShort((ushort) room.Info.Min);
writer.WriteUShort((ushort) room.Info.Max);
writer.WriteString(room.Info.Map);
var sendData = writer.ToArray();
player.Connection.Reliable.Send(sendData);
}
}
-62
View File
@@ -1,62 +0,0 @@
using NLog;
using Ragon.Common;
using Ragon.Core.Game;
using Ragon.Core.Lobby;
namespace Ragon.Core.Handlers;
public sealed class JoinHandler : IHandler
{
private Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
{
var roomId = reader.ReadString();
var lobbyPlayer = context.LobbyPlayer;
if (!context.Lobby.FindRoomById(roomId, out var existsRoom))
{
JoinFailed(lobbyPlayer, writer);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} failed to join room {roomId}");
return;
}
var roomPlayer = new RoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
context.Room?.RemovePlayer(context.RoomPlayer);
context.Room = existsRoom;
context.RoomPlayer = roomPlayer;
existsRoom.AddPlayer(roomPlayer);
JoinSuccess(roomPlayer, existsRoom, writer);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} joined to {existsRoom.Id}");
}
private void JoinSuccess(RoomPlayer player, Room room, RagonSerializer writer)
{
writer.Clear();
writer.WriteOperation(RagonOperation.JOIN_SUCCESS);
writer.WriteString(room.Id);
writer.WriteString(player.Id);
writer.WriteString(room.Owner.Id);
writer.WriteUShort((ushort) room.Info.Min);
writer.WriteUShort((ushort) room.Info.Max);
writer.WriteString(room.Info.Map);
var sendData = writer.ToArray();
player.Connection.Reliable.Send(sendData);
}
private void JoinFailed(LobbyPlayer player, RagonSerializer writer)
{
writer.Clear();
writer.WriteOperation(RagonOperation.JOIN_FAILED);
writer.WriteString($"Room not exists");
var sendData = writer.ToArray();
player.Connection.Reliable.Send(sendData);
}
}
@@ -1,81 +0,0 @@
using NLog;
using Ragon.Common;
using Ragon.Core.Game;
using Ragon.Core.Lobby;
namespace Ragon.Core.Handlers;
public sealed class JoinOrCreateHandler : IHandler
{
private RagonRoomParameters _roomParameters = new();
private Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
{
if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized)
{
_logger.Warn("Player not authorized for this request");
return;
}
var roomId = Guid.NewGuid().ToString();
var lobbyPlayer = context.LobbyPlayer;
_roomParameters.Deserialize(reader);
if (context.Lobby.FindRoomByMap(_roomParameters.Map, out var existsRoom))
{
var roomPlayer = new RoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
context.Room?.RemovePlayer(context.RoomPlayer);
context.Room = existsRoom;
context.RoomPlayer = roomPlayer;
existsRoom.AddPlayer(roomPlayer);
JoinSuccess(roomPlayer, existsRoom, writer);
}
else
{
var information = new RoomInformation()
{
Map = _roomParameters.Map,
Max = _roomParameters.Max,
Min = _roomParameters.Min,
};
var room = new Room(roomId, information);
context.Lobby.Persist(room);
var roomPlayer = new RoomPlayer(lobbyPlayer.Connection, lobbyPlayer.Id, lobbyPlayer.Name);
room.AddPlayer(roomPlayer);
context.Room?.RemovePlayer(context.RoomPlayer);
context.Room = room;
context.RoomPlayer = roomPlayer;
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} create room {room.Id} {information}");
JoinSuccess(roomPlayer, room, writer);
context.Loop.Run(room);
}
}
private void JoinSuccess(RoomPlayer player, Room room, RagonSerializer writer)
{
writer.Clear();
writer.WriteOperation(RagonOperation.JOIN_SUCCESS);
writer.WriteString(room.Id);
writer.WriteString(player.Id);
writer.WriteString(room.Owner.Id);
writer.WriteUShort((ushort) room.Info.Min);
writer.WriteUShort((ushort) room.Info.Max);
writer.WriteString(room.Info.Map);
var sendData = writer.ToArray();
player.Connection.Reliable.Send(sendData);
_logger.Trace($"{player.Connection.Id}|{player.Name} joined to room {room.Id}");
}
}
-19
View File
@@ -1,19 +0,0 @@
using NLog;
using Ragon.Common;
namespace Ragon.Core.Handlers;
public sealed class LeaveHandler: IHandler
{
private Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
{
var room = context.Room;
var roomPlayer = context.RoomPlayer;
if (room != null)
{
context.Room?.RemovePlayer(roomPlayer);
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} leaved from {room.Id}");
}
}
}
-124
View File
@@ -1,124 +0,0 @@
using NLog;
using Ragon.Common;
using Ragon.Core.Game;
using Ragon.Core.Lobby;
namespace Ragon.Core.Handlers;
public sealed class SceneLoadedHandler : IHandler
{
private Logger _logger = LogManager.GetCurrentClassLogger();
public void Handle(PlayerContext context, RagonSerializer reader, RagonSerializer writer)
{
if (context.LobbyPlayer.Status == LobbyPlayerStatus.Unauthorized)
return;
var owner = context.Room.Owner;
var player = context.RoomPlayer;
var room = context.Room;
if (player == owner)
{
var statics = reader.ReadUShort();
for (var staticIndex = 0; staticIndex < statics; staticIndex++)
{
var entityType = reader.ReadUShort();
var eventAuthority = (RagonAuthority) reader.ReadByte();
var staticId = reader.ReadUShort();
var propertiesCount = reader.ReadUShort();
var entity = new Entity(player, entityType, staticId, eventAuthority);
for (var propertyIndex = 0; propertyIndex < propertiesCount; propertyIndex++)
{
var propertyType = reader.ReadBool();
var propertySize = reader.ReadUShort();
entity.State.AddProperty(new EntityStateProperty(propertySize, propertyType));
}
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} created entity {entity.Id}:{entity.Type}");
room.AttachEntity(player, entity);
}
_logger.Trace($"Player {context.Connection.Id}|{context.LobbyPlayer.Name} loaded");
room.WaitPlayersList.Add(player);
foreach (var roomPlayer in room.WaitPlayersList)
{
DispatchPlayerJoinExcludePlayer(room, roomPlayer, writer);
roomPlayer.SetReady();
}
room.UpdateReadyPlayerList();
DispatchSnapshot(room, room.WaitPlayersList, writer);
room.WaitPlayersList.Clear();
}
else if (owner.IsLoaded)
{
player.SetReady();
DispatchPlayerJoinExcludePlayer(room, player, writer);
room.UpdateReadyPlayerList();
DispatchSnapshot(room, new List<RoomPlayer>() { player }, writer);
foreach (var entity in room.EntityList)
entity.RestoreBufferedEvents(player, writer);
}
else
{
_logger.Trace($"Player {player.Connection.Id}|{context.LobbyPlayer.Name} waiting owner of room");
room.WaitPlayersList.Add(player);
}
}
private void DispatchPlayerJoinExcludePlayer(Room room, RoomPlayer roomPlayer, RagonSerializer writer)
{
writer.Clear();
writer.WriteOperation(RagonOperation.PLAYER_JOINED);
writer.WriteUShort(roomPlayer.Connection.Id);
writer.WriteString(roomPlayer.Id);
writer.WriteString(roomPlayer.Name);
var sendData = writer.ToArray();
foreach (var awaiter in room.ReadyPlayersList)
{
if (awaiter != roomPlayer)
awaiter.Connection.Reliable.Send(sendData);
}
}
private void DispatchSnapshot(Room room, List<RoomPlayer> receviersList, RagonSerializer writer)
{
writer.Clear();
writer.WriteOperation(RagonOperation.SNAPSHOT);
writer.WriteUShort((ushort) room.ReadyPlayersList.Count);
foreach (var roomPlayer in room.ReadyPlayersList)
{
writer.WriteUShort(roomPlayer.Connection.Id);
writer.WriteString(roomPlayer.Id);
writer.WriteString(roomPlayer.Name);
}
var dynamicEntities = room.DynamicEntitiesList;
var dynamicEntitiesCount = (ushort) dynamicEntities.Count;
writer.WriteUShort(dynamicEntitiesCount);
foreach (var entity in dynamicEntities)
entity.State.Snapshot(writer);
var staticEntities = room.StaticEntitiesList;
var staticEntitiesCount = (ushort) staticEntities.Count;
writer.WriteUShort(staticEntitiesCount);
foreach (var entity in staticEntities)
entity.State.Snapshot(writer);
var sendData = writer.ToArray();
foreach (var player in receviersList)
player.Connection.Reliable.Send(sendData);
}
}
-12
View File
@@ -1,12 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using Ragon.Core.Game;
namespace Ragon.Core.Lobby;
public interface ILobby
{
public bool FindRoomById(string roomId, [MaybeNullWhen(false)] out Room room);
public bool FindRoomByMap(string map, [MaybeNullWhen(false)] out Room room);
public void Persist(Room room);
public void RemoveIfEmpty(Room room);
}
-61
View File
@@ -1,61 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using NLog;
using Ragon.Core.Game;
namespace Ragon.Core.Lobby;
public class LobbyInMemory : ILobby
{
private readonly List<Room> _rooms = new();
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
public bool FindRoomById(string roomId, [MaybeNullWhen(false)] out Room room)
{
foreach (var existRoom in _rooms)
{
var info = existRoom.Info;
if (existRoom.Id == roomId && info.Min < info.Max)
{
room = existRoom;
return true;
}
}
room = default;
return false;
}
public bool FindRoomByMap(string map, [MaybeNullWhen(false)] out Room room)
{
foreach (var existRoom in _rooms)
{
var info = existRoom.Info;
if (info.Map == map && existRoom.Players.Count < info.Max)
{
room = existRoom;
return true;
}
}
room = default;
return false;
}
public void Persist(Room room)
{
_rooms.Add(room);
foreach (var r in _rooms)
_logger.Trace($"Room: {r.Id} {r.Info} Players: {r.Players.Count}");
}
public void RemoveIfEmpty(Room room)
{
if (room.Players.Count == 0)
_rooms.Remove(room);
foreach (var r in _rooms)
_logger.Trace($"Room: {r.Id} {r.Info} Players: {r.Players.Count}");
}
}
-27
View File
@@ -1,27 +0,0 @@
using Ragon.Server;
namespace Ragon.Core.Lobby;
public enum LobbyPlayerStatus
{
Unauthorized,
Authorized,
}
public class LobbyPlayer
{
public string Id { get; private set; }
public string Name { get; set; }
public byte[] AdditionalData { get; set; }
public LobbyPlayerStatus Status { get; set; }
public INetworkConnection Connection { get; private set; }
public LobbyPlayer(INetworkConnection connection)
{
Id = Guid.NewGuid().ToString();
Connection = connection;
Status = LobbyPlayerStatus.Unauthorized;
Name = "None";
AdditionalData = Array.Empty<byte>();
}
}
-28
View File
@@ -1,28 +0,0 @@
using NLog;
using Ragon.Core.Game;
using Ragon.Core.Lobby;
using Ragon.Core.Time;
using Ragon.Server;
namespace Ragon.Core;
public class PlayerContext: IDisposable
{
public INetworkConnection Connection { get; }
public Loop Loop;
public ILobby Lobby { get; set; }
public LobbyPlayer LobbyPlayer { private set; get; }
public Room? Room { get; set; }
public RoomPlayer? RoomPlayer { get; set; }
public PlayerContext(INetworkConnection conn, LobbyPlayer player)
{
Connection = conn;
LobbyPlayer = player;
}
public void Dispose()
{
RoomPlayer?.Room.RemovePlayer(RoomPlayer);
}
}
-21
View File
@@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2-beta2" />
<PackageReference Include="NLog" Version="5.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ragon.Protocol\Ragon.Protocol.csproj" />
<ProjectReference Include="..\Ragon.Server.ENet\Ragon.Server.ENet.csproj" />
<ProjectReference Include="..\Ragon.Server.NativeWebSockets\Ragon.Server.NativeWebSockets.csproj" />
<ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" />
</ItemGroup>
</Project>
+12 -2
View File
@@ -4,8 +4,17 @@
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>8</LangVersion>
<TargetFramework>netstandard2.1</TargetFramework>
<RootNamespace>Ragon.Common</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Title>Ragon.Protocol</Title>
<Copyright>Eduard Kargin</Copyright>
<Version>1.4.0</Version>
<Authors>Eduard Kargin</Authors>
<PackageProjectUrl>https://ragon.io</PackageProjectUrl>
<RepositoryUrl>https://github.com/edmand46/Ragon</RepositoryUrl>
<RepositoryType>Source</RepositoryType>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
@@ -17,6 +26,7 @@
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath></OutputPath>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
<DefineConstants>TRACE;</DefineConstants>
<DebugType>none</DebugType>
</PropertyGroup>
</Project>
+18 -1
View File
@@ -1,4 +1,21 @@
namespace Ragon.Common
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Protocol
{
public enum RagonAuthority: byte
{
+501
View File
@@ -0,0 +1,501 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* 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 byte[] _rawData;
private readonly UTF8Encoding _utf8Encoding = new UTF8Encoding(false, true);
public byte[] RawData => _rawData;
public int ReadOffset => _read;
public int WriteOffset => _write;
public int Length => ((_write - 1) >> 3) + 1;
public int Capacity => _write - _read - 1;
public RagonBuffer(int capacity = 128)
{
_buckets = new uint[capacity];
_rawData = Array.Empty<byte>();
_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)
{
var bytes = BitConverter.GetBytes(value);
WriteBytes(bytes);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float ReadFloat()
{
var bytes = ReadBytes(4);
var value = BitConverter.ToSingle(bytes, 0);
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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)
{
var bytes = BitConverter.GetBytes(value);
WriteBytes(bytes);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt()
{
var bytes = ReadBytes(4);
var value = BitConverter.ToInt32(bytes, 0);
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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)
{
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 len)
{
var data = new byte[len];
for (int i = 0; i < len; 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;
if (index + limit >= _buckets.Length)
Resize(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 CopyFrom(RagonBuffer buffer, int size)
{
WriteArray(buffer._buckets, size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ToBuffer(RagonBuffer buffer, int size)
{
ReadArray(buffer._buckets, size);
}
public void FromArray(byte[] data)
{
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;
_rawData = data;
}
public byte[] ToArray()
{
Write(1, 1);
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;
}
public void ToArray(byte[] outData)
{
Write(1, 1);
var bucketsCount = (_write >> 5) + 1;
var length = Length;
for (int i = 0; i < bucketsCount; i++)
{
var dataIdx = i * 4;
var bucket = _buckets[i];
if (dataIdx < length)
outData[dataIdx] = (byte)bucket;
if (dataIdx + 1 < length)
outData[dataIdx + 1] = (byte)(bucket >> 8);
if (dataIdx + 2 < length)
outData[dataIdx + 2] = (byte)(bucket >> 16);
if (dataIdx + 3 < length)
outData[dataIdx + 3] = (byte)(bucket >> 24);
}
}
private void Resize(int capacity)
{
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,
}
}
+37 -21
View File
@@ -1,25 +1,41 @@
namespace Ragon.Common
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Protocol
{
public enum RagonOperation: byte
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,
AUTHORIZE = 1,
AUTHORIZED_SUCCESS = 2,
AUTHORIZED_FAILED = 3,
JOIN_OR_CREATE_ROOM = 4,
CREATE_ROOM = 5,
JOIN_ROOM = 6,
LEAVE_ROOM = 7,
OWNERSHIP_ROOM_CHANGED = 9,
JOIN_SUCCESS = 10,
JOIN_FAILED = 11,
PLAYER_JOINED = 14,
PLAYER_LEAVED = 15,
REPLICATE_ROOM_EVENT = 22,
TRANSFER_ROOM_OWNERSHIP = 23,
TIMESTAMP_SYNCHRONIZATION = 25,
ROOM_LIST_UPDATED = 26,
PLAYER_DATA_UPDATED = 27,
ROOM_DATA_UPDATED = 28,
}
}
+18 -1
View File
@@ -1,4 +1,21 @@
namespace Ragon.Common
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Protocol
{
public enum RagonReplicationMode: byte
{
+20 -7
View File
@@ -1,22 +1,35 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Common
namespace Ragon.Protocol
{
public class RagonRoomParameters: IRagonSerializable
public class RagonRoomParameters
{
public string Map { get; set; }
public int Min { get; set; }
public int Max { get; set; }
public void Serialize(RagonSerializer buffer)
public void Serialize(RagonStream buffer)
{
buffer.WriteString(Map);
buffer.WriteInt(Min);
buffer.WriteInt(Max);
}
public void Deserialize(RagonSerializer buffer)
public void Deserialize(RagonStream buffer)
{
Map = buffer.ReadString();
Min = buffer.ReadInt();
Max = buffer.ReadInt();
}
+20 -3
View File
@@ -1,8 +1,25 @@
namespace Ragon.Common
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Protocol
{
public interface IRagonSerializable
{
public void Serialize(RagonSerializer serializer);
public void Deserialize(RagonSerializer serializer);
public void Serialize(RagonStream buffer);
public void Deserialize(RagonStream buffer);
}
}
@@ -3,8 +3,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Ragon.Common
namespace Ragon.Protocol
{
[StructLayout(LayoutKind.Explicit)]
internal struct ValueConverter
@@ -22,7 +21,7 @@ namespace Ragon.Common
[FieldOffset(7)] public byte Byte7;
}
public class RagonSerializer
public class RagonStream
{
private byte[] _data;
private int _offset;
@@ -31,7 +30,7 @@ namespace Ragon.Common
public int Lenght => _offset;
public int Size => _size - _offset;
public RagonSerializer(int capacity = 256)
public RagonStream(int capacity = 256)
{
_data = new byte[capacity];
_offset = 0;
@@ -72,7 +71,7 @@ namespace Ragon.Common
public int WriteBool(bool value)
{
ResizeIfNeed(1);
_data[_offset] = value ? (byte) 1 : (byte) 0;
_data[_offset] = value ? (byte)1 : (byte)0;
_offset += 1;
return 1;
}
@@ -91,7 +90,7 @@ namespace Ragon.Common
public int WriteInt(int value)
{
ResizeIfNeed(4);
var converter = new ValueConverter() {Int = value};
var converter = new ValueConverter() { Int = value };
_data[_offset] = converter.Byte0;
_data[_offset + 1] = converter.Byte1;
_data[_offset + 2] = converter.Byte2;
@@ -104,7 +103,7 @@ namespace Ragon.Common
public int WriteInt(int value, int offset)
{
ResizeIfNeed(4);
var converter = new ValueConverter() {Int = value};
var converter = new ValueConverter() { Int = value };
_data[offset] = converter.Byte0;
_data[offset + 1] = converter.Byte1;
_data[offset + 2] = converter.Byte2;
@@ -115,7 +114,8 @@ namespace Ragon.Common
[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]};
var converter = new ValueConverter
{ Byte0 = _data[_offset], Byte1 = _data[_offset + 1], Byte2 = _data[_offset + 2], Byte3 = _data[_offset + 3] };
_offset += 4;
return converter.Int;
}
@@ -131,7 +131,7 @@ namespace Ragon.Common
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteLong(long value, int offset)
{
var converter = new ValueConverter() {Long = value};
var converter = new ValueConverter() { Long = value };
_data[offset] = converter.Byte0;
_data[offset + 1] = converter.Byte1;
_data[offset + 2] = converter.Byte2;
@@ -164,7 +164,7 @@ namespace Ragon.Common
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteFloat(float value)
{
var converter = new ValueConverter() {Float = value};
var converter = new ValueConverter() { Float = value };
WriteInt(converter.Int);
return 4;
}
@@ -173,7 +173,7 @@ namespace Ragon.Common
public float ReadFloat()
{
var rawValue = ReadInt();
var converter = new ValueConverter() {Int = rawValue};
var converter = new ValueConverter() { Int = rawValue };
var value = converter.Float;
return value;
}
@@ -181,105 +181,94 @@ namespace Ragon.Common
[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;
var rawData = Encoding.UTF8.GetBytes(value);
var len = rawData.Length;
ResizeIfNeed(2 + len);
WriteUShort((ushort)len);
return rawData.Length + 2;
Buffer.BlockCopy(rawData, 0, _data, _offset, len);
_offset += len;
return len + 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;
var len = ReadUShort();
var rawData = new byte[len];
Buffer.BlockCopy(_data, _offset, rawData, 0, len);
var str = Encoding.UTF8.GetString(rawData);
_offset += len;
return str;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<byte> ReadData(int lenght)
public byte[] ReadBinary(int len)
{
var data = _data.AsSpan();
var payloadData = data.Slice(_offset, lenght);
if (len >= _data.Length)
return Array.Empty<byte>();
_offset += payloadData.Length;
return payloadData;
var payload = new byte[len];
Buffer.BlockCopy(_data, _offset, payload, 0, len);
_offset += len;
return payload;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteData(ref ReadOnlySpan<byte> payload)
public void WriteBinary(byte[] payload)
{
ResizeIfNeed(payload.Length);
var data = _data.AsSpan();
var payloadData = data.Slice(_offset, payload.Length);
Array.Copy(payload, 0, _data, _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)
public void WriteOperation(RagonOperation operation)
{
ResizeIfNeed(1);
_data[_offset] = (byte) ragonOperation;
_data[_offset] = (byte)operation;
_offset += 1;
return 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RagonOperation ReadOperation()
{
var op = (RagonOperation) _data[_offset];
var op = (RagonOperation)_data[_offset];
_offset += 1;
return op;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteUShort(ushort value)
public void WriteUShort(ushort value)
{
ResizeIfNeed(2);
_data[_offset] = (byte) (value & 0x00FF);
_data[_offset + 1] = (byte) ((value & 0xFF00) >> 8);
_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)
public void WriteUShort(ushort value, int offset)
{
ResizeIfNeed(2);
_data[offset] = (byte) (value & 0x00FF);
_data[offset + 1] = (byte) ((value & 0xFF00) >> 8);
return 2;
_data[offset] = (byte)(value & 0x00FF);
_data[offset + 1] = (byte)((value & 0xFF00) >> 8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort ReadUShort()
{
var value = (ushort) (_data[_offset] + (_data[_offset + 1] << 8));
var value = (ushort)(_data[_offset] + (_data[_offset + 1] << 8));
_offset += 2;
return value;
}
@@ -290,21 +279,6 @@ namespace Ragon.Common
_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)
{
+18 -1
View File
@@ -1,4 +1,21 @@
namespace Ragon.Common
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Protocol
{
public enum RagonTarget: byte
{
+105
View File
@@ -0,0 +1,105 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ragon.Protocol
{
[StructLayout(LayoutKind.Explicit)]
public struct DoubleToUInt
{
[FieldOffset(0)]
public double Double;
[FieldOffset(0)]
public uint Int0;
[FieldOffset(4)]
public uint Int1;
}
public static class RagonTime {
public static double CurrentTimestamp()
{
var currentTime = System.DateTime.UtcNow.ToUniversalTime().Subtract(
new System.DateTime(1970, 1, 1, 0, 0, 0, System.DateTimeKind.Utc)
).TotalMilliseconds;
return currentTime;
}
}
public static class DeBruijn
{
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;
}
}
}
+19 -2
View File
@@ -1,10 +1,27 @@
namespace Ragon.Common
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Protocol
{
public static class RagonVersion
{
public static uint Parse(string version)
{
var strings = version.Split(".");
var strings = version.Split('.');
if (strings.Length < 3)
return 0;
+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"]
Executable → Regular
View File
Executable → Regular
+16 -17
View File
@@ -1,7 +1,18 @@
using System;
using System.Runtime.InteropServices;
using NLog;
using Ragon.Core;
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Ragon.Relay
{
@@ -9,20 +20,8 @@ namespace Ragon.Relay
{
static void Main(string[] args)
{
var logger = LogManager.GetLogger("Ragon.Relay");
logger.Info("Relay Application");
var configuration = Configuration.Load("relay.config.json");
var relay = new Application(configuration);
logger.Info("Started");
var relay = new Relay();
relay.Start();
Console.ReadKey();
relay.Stop();
logger.Info("Stopped");
}
}
}
Executable → Regular
+14 -3
View File
@@ -2,8 +2,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>Game</RootNamespace>
<RootNamespace>Ragon.Relay</RootNamespace>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@@ -17,10 +17,21 @@
<None Update="relay.config.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="libenet.dylib">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ragon.Core\Ragon.Core.csproj" />
<ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ENet-CSharp" Version="2.4.8" />
<PackageReference Include="Google.Protobuf" Version="3.29.0" />
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.3.2" />
</ItemGroup>
</Project>

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