Compare commits

..

176 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
edmand46 b793ca3e68 chore: update default socket typoe 2022-12-25 07:34:22 -08:00
edmand46 3936e5c95b chore: update version 2022-12-25 03:16:59 -08:00
edmand46 e9418f4b22 fixed: websocket server wrong execution thread 2022-12-25 03:13:01 -08:00
edmand46 f34b05e6ff chore: update version libs 2022-12-24 01:47:49 -08:00
edmand46 a5a67963be feat: websockets 2022-12-20 12:20:52 -08:00
edmand46 ab85578ccf wip 2022-12-17 21:16:02 +04:00
edmand46 13044357a5 refactor: remove some warnings 2022-12-17 18:58:37 +04:00
edmand46 a9be230960 Merge branch 'develop'
# Conflicts:
#	Ragon/Sources/Application.cs
#	Ragon/Sources/Authorization.cs
#	Ragon/Sources/Configuration.cs
#	Ragon/Sources/Entity/Entity.cs
#	Ragon/Sources/GameRoom.cs
#	Ragon/Sources/IApplicationHandler.cs
#	Ragon/Sources/RoomManager.cs
#	Ragon/Sources/Server/ENet/ENetServer.cs
#	Ragon/Sources/Server/Http/WebSocketServer.cs
2022-12-17 14:08:28 +04:00
edmand46 5a4bf0c24e refactor: entity structure changes 2022-12-17 14:05:53 +04:00
edmand46 fa6ace4dc8 wip 2022-12-16 23:36:51 +04:00
edmand46 4d8ed1105a wip 2022-12-16 00:05:46 +04:00
edmand46 e2ef761bd7 fixed: windows server 2022-12-15 23:55:59 +04:00
edmand46 828112855f fixed: crash on send in websocket server 2022-12-04 23:01:05 +04:00
edmand46 06ff76fe0b fixed: second migration 2022-12-01 22:24:03 +04:00
edmand46 c92b5a5bc4 chore: update readme 2022-11-30 23:56:17 +04:00
edmand46 f83d3ea0c7 chore: update version 2022-11-13 18:54:23 +04:00
edmand46 3564eb2adc feat: added except invoker target 2022-11-13 18:53:56 +04:00
edmand46 3531432758 fixed: crash on concurrent authorization 2022-11-13 00:20:41 +04:00
edmand46 6bda468607 v1.0.23-rc 2022-10-24 22:19:50 +04:00
edmand46 7ddd52bf9d feat: added websocket support 2022-10-23 18:50:05 +04:00
edmand46 ecf3631163 Merge branch 'retry'
# Conflicts:
#	Ragon/Sources/Game/GameRoom.cs
2022-10-23 18:49:32 +04:00
edmand46 8a149eb3b4 fixed: added catch error websocket 2022-10-23 18:45:37 +04:00
edmand46 14ae5e8189 fixed: temporary scheduler 2022-10-22 22:58:15 +04:00
edmand46 b7e8327ca8 feat: websocket 2022-10-22 21:34:35 +04:00
edmand46 b9e79af9d8 fixed: player joined opcode 2022-10-21 22:19:21 +04:00
edmand46 545ec02ecc chore: update version 2022-10-21 00:05:01 +04:00
edmand46 73feb77169 fixed: wrong peerId in restored events 2022-10-21 00:04:33 +04:00
edmand46 cbcf1773aa wip 2022-10-16 18:27:40 +04:00
edmand46 5519ae7679 wip 2022-10-16 18:00:32 +04:00
edmand46 1558b5eefb wip 2022-10-16 17:14:32 +04:00
edmand46 aaa0e4a317 wip 2022-10-16 16:50:22 +04:00
edmand46 ff712dc094 wip 2022-10-16 16:22:27 +04:00
edmand46 c2c75cb513 fixed: double authorization request 2022-10-15 18:48:59 +04:00
edmand46 9a22566f79 fixed: additional data missed 2022-10-15 18:48:48 +04:00
edmand46 783d2ce922 chore: bump version 2022-10-02 16:13:53 +04:00
edmand46 5771ec738b fixed: ownership changing 2022-10-02 16:00:34 +04:00
edmand46 85081e1da7 fix: replication entity event 2022-09-11 15:05:32 +04:00
edmand46 dbaa5d9d92 chore: renaming enums 2022-09-10 10:25:45 +04:00
edmand46 54786c065d wip 2022-09-06 22:39:52 +04:00
edmand46 0b3a0dd1ac refactor: spawning entities with properties 2022-09-04 14:43:17 +04:00
edmand46 72ff37e94a bump version 2022-08-30 01:54:50 +04:00
edmand46 75dab9d16f fixed: on scene entities load 2022-08-30 01:53:47 +04:00
edmand46 25b0e54d13 fixed: changed flow of joining 2022-08-29 01:45:47 +04:00
edmand46 e4f664b557 refactor: renamed plugin api 2022-08-28 19:31:07 +04:00
edmand46 174a4c4422 bump version 2022-08-28 11:51:14 +04:00
edmand46 a51d7c73cd fixed: mistype 2022-08-28 11:46:30 +04:00
edmand46 957622a170 fix: on player leave logic 2022-08-28 00:18:26 +04:00
edmand46 4a07424293 feat: added checks for size of payload 2022-08-27 11:21:51 +04:00
edmand46 568a3282c3 fix: restoring properties 2022-08-27 10:51:57 +04:00
edmand46 6a71fe5fe0 feat: restore properties 2022-08-25 23:12:33 +04:00
edmand46 082e183989 chore: move copyright on top of license 2022-08-24 00:06:37 +04:00
edmand46 51c0482a40 fix: leave player logic 2022-08-21 00:15:07 +04:00
edmand46 9e16c53cad fix: gameloop optimization 2022-08-20 22:05:43 +04:00
edmand46 6ec29e5dcd 1.0.11-rc 2022-08-20 12:27:04 +04:00
191 changed files with 8702 additions and 2498 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
+6 -6
View File
@@ -19,7 +19,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tag_name: ${{ github.ref }} tag_name: ${{ github.ref }}
release_name: Ragon.SimpleServer-${{ github.ref }} release_name: Ragon.Relay-${{ github.ref }}
prerelease: true prerelease: true
build: build:
@@ -49,14 +49,14 @@ jobs:
- name: Setup dotnet - name: Setup dotnet
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: 6.0.x dotnet-version: 8.0.x
- name: Build - name: Build
shell: bash shell: bash
run: | run: |
release_name="Ragon.SimpleServer-${{ env.TAG }}-${{ matrix.target }}" release_name="Ragon.Relay-${{ env.TAG }}-${{ matrix.target }}"
# Build everything # Build everything
dotnet publish Ragon.SimpleServer/Ragon.SimpleServer.csproj -c Release --runtime "${{ matrix.target }}" -p:PublishSingleFile=true -o "$release_name" dotnet publish Ragon.Relay/Ragon.Relay.csproj -c Release --runtime "${{ matrix.target }}" -p:PublishSingleFile=true -o "$release_name"
# Pack files # Pack files
7z a -tzip "${release_name}.zip" "./${release_name}/*" 7z a -tzip "${release_name}.zip" "./${release_name}/*"
@@ -69,6 +69,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ needs.release.outputs.upload_url }} upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: Ragon.SimpleServer-${{ env.TAG }}-${{ matrix.target }}.zip asset_path: Ragon.Relay-${{ env.TAG }}-${{ matrix.target }}.zip
asset_name: Ragon.SimpleServer-${{ env.TAG }}-${{ matrix.target }}.zip asset_name: Ragon.Relay-${{ env.TAG }}-${{ matrix.target }}.zip
asset_content_type: application/zip asset_content_type: application/zip
+4 -1
View File
@@ -1,6 +1,9 @@
.DS_Store .DS_Store
.idea .idea
.vs .vs
.vscode
obj obj
bin bin
*.user *.user
*.dylib
*.dll
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+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.
Executable
+31
View File
@@ -0,0 +1,31 @@
<p align="center">
<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="https://www.ragon-server.com/docs/overview">Documentation</a>
### Features:
- Effective
- Free
- Lobby
- Room based architecture
- Сustomizable authorization
- Сustomizable server-side logic via plugins with flexible API*(2)
- No CCU limitations*(1)
- No Room count limitations
- Reliable UDP
### Requirements
- OSX, Windows, Linux(Ubuntu, Debian)
- .NET 6.0
### Dependencies
* ENet-Sharp [v2.4.8]
### 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;
}
}
-21
View File
@@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>8</LangVersion>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath></OutputPath>
<DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath></OutputPath>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
</PropertyGroup>
</Project>
-8
View File
@@ -1,8 +0,0 @@
namespace Ragon.Common
{
public enum RagonAuthority: byte
{
OWNER_ONLY,
ALL,
}
}
-34
View File
@@ -1,34 +0,0 @@
namespace Ragon.Common
{
public enum RagonOperation: byte
{
AUTHORIZE,
AUTHORIZED_SUCCESS,
AUTHORIZED_FAILED,
JOIN_OR_CREATE_ROOM,
CREATE_ROOM,
JOIN_ROOM,
LEAVE_ROOM,
OWNERSHIP_CHANGED,
JOIN_SUCCESS,
JOIN_FAILED,
LOAD_SCENE,
SCENE_IS_LOADED,
PLAYER_JOINED,
PLAYER_LEAVED,
CREATE_STATES,
CREATE_ENTITY,
DESTROY_ENTITY,
CREATE_STATIC_ENTITY,
SNAPSHOT,
REPLICATE_ENTITY_STATE,
REPLICATE_ENTITY_EVENT,
REPLICATE_EVENT,
}
}
@@ -1,24 +0,0 @@
namespace Ragon.Common
{
public class RagonRoomParameters: IRagonSerializable
{
public string Map { get; set; }
public int Min { get; set; }
public int Max { get; set; }
public void Serialize(RagonSerializer buffer)
{
buffer.WriteString(Map);
buffer.WriteInt(Min);
buffer.WriteInt(Max);
}
public void Deserialize(RagonSerializer buffer)
{
Map = buffer.ReadString();
Min = buffer.ReadInt();
Max = buffer.ReadInt();
}
}
}
@@ -1,8 +0,0 @@
namespace Ragon.Common
{
public interface IRagonSerializable
{
public void Serialize(RagonSerializer serializer);
public void Deserialize(RagonSerializer serializer);
}
}
-9
View File
@@ -1,9 +0,0 @@
namespace Ragon.Common
{
public enum RagonTarget: byte
{
OWNER,
EXCEPT_OWNER,
ALL,
}
}
+32
View File
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>8</LangVersion>
<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' ">
<OutputPath></OutputPath>
<DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>TRACE;NETSTACK_SPAN</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath></OutputPath>
<DefineConstants>TRACE;</DefineConstants>
<DebugType>none</DebugType>
</PropertyGroup>
</Project>
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright 2023-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
{
None,
OwnerOnly,
All,
}
}
+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,
}
}
+41
View File
@@ -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.Protocol
{
public enum RagonOperation : byte
{
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,
}
}
@@ -0,0 +1,27 @@
/*
* 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
{
Local,
Server,
LocalAndServer,
Buffered,
}
}
@@ -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.
*/
namespace Ragon.Protocol
{
public class RagonRoomParameters
{
public int Min { get; set; }
public int Max { get; set; }
public void Serialize(RagonStream buffer)
{
buffer.WriteInt(Min);
buffer.WriteInt(Max);
}
public void Deserialize(RagonStream buffer)
{
Min = buffer.ReadInt();
Max = buffer.ReadInt();
}
}
}
@@ -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.
*/
namespace Ragon.Protocol
{
public interface IRagonSerializable
{
public void Serialize(RagonStream buffer);
public void Deserialize(RagonStream buffer);
}
}
@@ -3,8 +3,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
namespace Ragon.Protocol
namespace Ragon.Common
{ {
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
internal struct ValueConverter internal struct ValueConverter
@@ -22,15 +21,16 @@ namespace Ragon.Common
[FieldOffset(7)] public byte Byte7; [FieldOffset(7)] public byte Byte7;
} }
public class RagonSerializer public class RagonStream
{ {
private byte[] _data; private byte[] _data;
private int _offset; private int _offset;
private int _size; private int _size;
public int Lenght => _offset; public int Lenght => _offset;
public int Size => _size - _offset; public int Size => _size - _offset;
public RagonSerializer(int capacity = 256) public RagonStream(int capacity = 256)
{ {
_data = new byte[capacity]; _data = new byte[capacity];
_offset = 0; _offset = 0;
@@ -45,12 +45,18 @@ namespace Ragon.Common
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteByte(byte value) public void AddOffset(int offset)
{
_offset += offset;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteByte(byte value)
{ {
ResizeIfNeed(1); ResizeIfNeed(1);
_data[_offset] = value; _data[_offset] = value;
_offset += 1; _offset += 1;
return 1;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -62,12 +68,12 @@ namespace Ragon.Common
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBool(bool value) public int WriteBool(bool value)
{ {
ResizeIfNeed(1); ResizeIfNeed(1);
_data[_offset] = value ? (byte)1 : (byte)0;
_data[_offset] = value ? (byte) 1 : (byte) 0;
_offset += 1; _offset += 1;
return 1;
} }
@@ -81,21 +87,35 @@ namespace Ragon.Common
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteInt(int value) public int WriteInt(int value)
{ {
ResizeIfNeed(4); ResizeIfNeed(4);
var converter = new ValueConverter() {Int = value}; var converter = new ValueConverter() { Int = value };
_data[_offset] = converter.Byte0; _data[_offset] = converter.Byte0;
_data[_offset + 1] = converter.Byte1; _data[_offset + 1] = converter.Byte1;
_data[_offset + 2] = converter.Byte2; _data[_offset + 2] = converter.Byte2;
_data[_offset + 3] = converter.Byte3; _data[_offset + 3] = converter.Byte3;
_offset += 4; _offset += 4;
return 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int WriteInt(int value, int offset)
{
ResizeIfNeed(4);
var converter = new ValueConverter() { Int = value };
_data[offset] = converter.Byte0;
_data[offset + 1] = converter.Byte1;
_data[offset + 2] = converter.Byte2;
_data[offset + 3] = converter.Byte3;
return 4;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt() 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; _offset += 4;
return converter.Int; return converter.Int;
} }
@@ -107,11 +127,11 @@ namespace Ragon.Common
WriteLong(value, _offset); WriteLong(value, _offset);
_offset += 8; _offset += 8;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLong(long value, int offset) 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] = converter.Byte0;
_data[offset + 1] = converter.Byte1; _data[offset + 1] = converter.Byte1;
_data[offset + 2] = converter.Byte2; _data[offset + 2] = converter.Byte2;
@@ -120,6 +140,7 @@ namespace Ragon.Common
_data[offset + 5] = converter.Byte5; _data[offset + 5] = converter.Byte5;
_data[offset + 6] = converter.Byte6; _data[offset + 6] = converter.Byte6;
_data[offset + 7] = converter.Byte7; _data[offset + 7] = converter.Byte7;
return 8;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -141,114 +162,87 @@ namespace Ragon.Common
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteFloat(float value) public int WriteFloat(float value)
{ {
var converter = new ValueConverter() {Float = value}; var converter = new ValueConverter() { Float = value };
WriteInt(converter.Int); WriteInt(converter.Int);
return 4;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public float ReadFloat() public float ReadFloat()
{ {
var rawValue = ReadInt(); var rawValue = ReadInt();
var converter = new ValueConverter() {Int = rawValue}; var converter = new ValueConverter() { Int = rawValue };
var value = converter.Float; var value = converter.Float;
return value; return value;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteString(string value, ushort size) public int WriteString(string value)
{ {
var stringRaw = Encoding.UTF8.GetBytes(value).AsSpan(); var rawData = Encoding.UTF8.GetBytes(value);
ResizeIfNeed(2 + size); var len = rawData.Length;
ResizeIfNeed(2 + len);
WriteUShort((ushort) stringRaw.Length); WriteUShort((ushort)len);
var data = _data.AsSpan().Slice(_offset, size);
stringRaw.CopyTo(data);
_offset += stringRaw.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] Buffer.BlockCopy(rawData, 0, _data, _offset, len);
public string ReadString(ushort size)
{ _offset += len;
var lenght = ReadUShort();
var stringRaw = _data.AsSpan().Slice(_offset, size);
var strData = stringRaw.Slice(0, lenght);
var str = Encoding.UTF8.GetString(strData);
_offset += size;
return str;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteString(string value)
{
var stringRaw = Encoding.UTF8.GetBytes(value).AsSpan();
ResizeIfNeed(2 + stringRaw.Length);
WriteUShort((ushort) stringRaw.Length); return len + 2;
var data = _data.AsSpan().Slice(_offset, stringRaw.Length);
stringRaw.CopyTo(data);
_offset += stringRaw.Length;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString() public string ReadString()
{ {
var lenght = ReadUShort(); var len = ReadUShort();
var stringRaw = _data.AsSpan().Slice(_offset, lenght); var rawData = new byte[len];
var str = Encoding.UTF8.GetString(stringRaw);
_offset += lenght; Buffer.BlockCopy(_data, _offset, rawData, 0, len);
var str = Encoding.UTF8.GetString(rawData);
_offset += len;
return str; return str;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<byte> ReadData(int lenght) public byte[] ReadBinary(int len)
{ {
var data = _data.AsSpan(); if (len >= _data.Length)
var payloadData = data.Slice(_offset, lenght); return Array.Empty<byte>();
_offset += payloadData.Length; var payload = new byte[len];
return payloadData; Buffer.BlockCopy(_data, _offset, payload, 0, len);
_offset += len;
return payload;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteData(ref ReadOnlySpan<byte> payload) public void WriteBinary(byte[] payload)
{ {
ResizeIfNeed(payload.Length); ResizeIfNeed(payload.Length);
var data = _data.AsSpan(); Array.Copy(payload, 0, _data, _offset, payload.Length);
var payloadData = data.Slice(_offset, payload.Length);
payload.CopyTo(payloadData);
_offset += payload.Length; _offset += payload.Length;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> GetWritableData(int lenght) public void WriteOperation(RagonOperation operation)
{
ResizeIfNeed(lenght);
var data = _data.AsSpan();
var payloadData = data.Slice(_offset, lenght);
_offset += lenght;
return payloadData;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteOperation(RagonOperation ragonOperation)
{ {
ResizeIfNeed(1); ResizeIfNeed(1);
_data[_offset] = (byte) ragonOperation; _data[_offset] = (byte)operation;
_offset += 1; _offset += 1;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public RagonOperation ReadOperation() public RagonOperation ReadOperation()
{ {
var op = (RagonOperation) _data[_offset]; var op = (RagonOperation)_data[_offset];
_offset += 1; _offset += 1;
return op; return op;
} }
@@ -258,15 +252,23 @@ namespace Ragon.Common
{ {
ResizeIfNeed(2); ResizeIfNeed(2);
_data[_offset] = (byte) (value & 0x00FF); _data[_offset] = (byte)(value & 0x00FF);
_data[_offset + 1] = (byte) ((value & 0xFF00) >> 8); _data[_offset + 1] = (byte)((value & 0xFF00) >> 8);
_offset += 2; _offset += 2;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUShort(ushort value, int offset)
{
ResizeIfNeed(2);
_data[offset] = (byte)(value & 0x00FF);
_data[offset + 1] = (byte)((value & 0xFF00) >> 8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort ReadUShort() public ushort ReadUShort()
{ {
var value = (ushort) (_data[_offset] + (_data[_offset + 1] << 8)); var value = (ushort)(_data[_offset] + (_data[_offset + 1] << 8));
_offset += 2; _offset += 2;
return value; return value;
} }
@@ -277,21 +279,6 @@ namespace Ragon.Common
_size = 0; _size = 0;
} }
public void ToSpan(ref Span<byte> data)
{
var span = _data.AsSpan();
var dataSpan = span.Slice(0, _offset);
dataSpan.CopyTo(data);
}
public void FromSpan(ref ReadOnlySpan<byte> data)
{
Clear();
ResizeIfNeed(data.Length);
var dataSpan = _data.AsSpan();
data.CopyTo(dataSpan);
_size = data.Length;
}
public void FromArray(byte[] data) public void FromArray(byte[] data)
{ {
+28
View File
@@ -0,0 +1,28 @@
/*
* 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
{
Owner,
ExceptOwner,
ExceptInvoker,
All,
Player,
}
}
+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;
}
}
}
+45
View File
@@ -0,0 +1,45 @@
/*
* 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('.');
if (strings.Length < 3)
return 0;
var parts = new uint[] {0, 0, 0};
for (int i = 0; i < parts.Length; i++)
{
if (!uint.TryParse(strings[i], out var v))
return 0;
parts[i] = v;
}
return (parts[0] << 16) | (parts[1] << 8) | parts[2];
}
public static string Parse(uint version)
{
return (version >> 16 & 0xFF) + "." + (version >> 8 & 0xFF) + "." + (version & 0xFF);
}
}
}
+16
View File
@@ -0,0 +1,16 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env
WORKDIR /App
COPY . ./
RUN dotnet restore
RUN dotnet publish -c Release -o out
FROM mcr.microsoft.com/dotnet/runtime:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["Ragon.Relay"]
View File
+27
View File
@@ -0,0 +1,27 @@
/*
* Copyright 2023-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
{
class Program
{
static void Main(string[] args)
{
var relay = new Relay();
relay.Start();
}
}
}
+37
View File
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>Ragon.Relay</RootNamespace>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<None Update="NLog.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="relay.config.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="libenet.dylib">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<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>
+40
View File
@@ -0,0 +1,40 @@
using System;
using NLog;
using Ragon.Server.Logging;
namespace Ragon.Relay;
public class RelayLogger: IRagonLogger
{
private Logger _nlogger;
public RelayLogger(string tag)
{
_nlogger = LogManager.GetLogger(tag);
}
public void Warning(string message)
{
_nlogger.Warn(message);
}
public void Info(string message)
{
_nlogger.Info(message);
}
public void Error(string message)
{
_nlogger.Error(message);
}
public void Error(Exception ex)
{
_nlogger.Error(ex);
}
public void Trace(string message)
{
_nlogger.Trace(message);
}
}
@@ -0,0 +1,11 @@
using Ragon.Server.Logging;
namespace Ragon.Relay;
public class RelayLoggerFactory: IRagonLoggerFactory
{
public IRagonLogger GetLogger(string tag)
{
return new RelayLogger(tag);
}
}
@@ -0,0 +1,23 @@
using System;
using Ragon.Server.Plugin;
namespace Ragon.Relay;
public class RelayRoomPlugin: BaseRoomPlugin
{
public void Tick(float dt)
{
}
public void OnAttached()
{
Console.WriteLine("Room attached");
}
public void OnDetached()
{
Console.WriteLine("Room detached");
}
}
@@ -0,0 +1,43 @@
using Ragon.Server;
using Ragon.Server.Lobby;
using Ragon.Server.Plugin;
using Ragon.Server.Time;
namespace Ragon.Relay
{
public class RelayServerPlugin : BaseServerPlugin
{
private RelayConfiguration _relayConfiguration;
private RagonScheduler _scheduler;
private RagonConnectionRegistry _connectionRegistry;
private IRagonLobby _lobby;
private Reporter _reporter;
public RelayServerPlugin(RelayConfiguration config)
{
_relayConfiguration = config;
}
public override void OnAttached(IRagonServer server)
{
base.OnAttached(server);
_lobby = server.Lobby;
_connectionRegistry = server.ConnectionRegistry;
_scheduler = server.Scheduler;
_reporter = new Reporter(_relayConfiguration, server, "127.0.0.1", 5000);
server.Scheduler.Run(new RagonActionTimer(() => _reporter.Done(), 1, -1));
}
public override bool OnCommand(string command, string payload)
{
return true;
}
public override IRoomPlugin CreateRoomPlugin(RoomInformation information)
{
return new RelayRoomPlugin();
}
}
}
+81
View File
@@ -0,0 +1,81 @@
/*
* Copyright 2023-2024 Eduard Kargin <kargin.eduard@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.IO;
using System.Threading;
using Newtonsoft.Json;
using Ragon.Server;
using Ragon.Server.IO;
using Ragon.Server.Logging;
using Ragon.Server.Plugin;
using Ragon.Server.WebSocketServer;
using Ragon.Transport;
namespace Ragon.Relay
{
public class Relay
{
public void Start()
{
LoggerManager.SetLoggerFactory(new RelayLoggerFactory());
var logger = LoggerManager.GetLogger("Relay");
logger.Info("Relay Application");
var data = File.ReadAllText("relay.config.json");
var configuration = JsonConvert.DeserializeObject<RelayConfiguration>(data);
var serverType = RagonServerConfiguration.GetServerType(configuration.ServerType);
INetworkServer networkServer = new ENetServer();
IServerPlugin plugin = new RelayServerPlugin();
switch (serverType)
{
case ServerType.ENET:
networkServer = new ENetServer();
break;
case ServerType.WEBSOCKET:
networkServer = new WebSocketServer();
break;
}
var serverConfiguration = new RagonServerConfiguration()
{
LimitConnections = configuration.LimitConnections,
LimitRooms = configuration.LimitRooms,
LimitBufferedEvents = configuration.LimitBufferedEvents,
LimitPlayersPerRoom = configuration.LimitPlayersPerRoom,
LimitUserDataSize = configuration.LimitUserDataSize,
LimitPropertySize = configuration.LimitPropertySize,
Port = configuration.Port,
Protocol = configuration.Protocol,
ServerKey = configuration.ServerKey,
ServerTickRate = configuration.ServerTickRate,
ServerAddress = configuration.ServerAddress,
};
var relay = new RagonServer(networkServer, plugin, serverConfiguration);
relay.Listen();
while (relay.IsRunning)
{
relay.Tick();
Thread.Sleep(1);
}
relay.Dispose();
}
}
}
+21
View File
@@ -0,0 +1,21 @@
using System;
namespace Ragon.Relay
{
[Serializable]
public struct RelayConfiguration
{
public string ServerKey;
public string ServerType;
public string ServerAddress;
public ushort ServerTickRate;
public string Protocol;
public ushort Port;
public int LimitConnections;
public int LimitPlayersPerRoom;
public int LimitRooms;
public int LimitBufferedEvents;
public int LimitUserDataSize;
public int LimitPropertySize;
}
}
+44
View File
@@ -0,0 +1,44 @@
using System;
using System.Net;
using System.Net.Sockets;
using Ragon.Server.Logging;
namespace Ragon.Relay;
public class Client
{
private readonly UdpClient _udpClient;
private readonly IPEndPoint _endpoint;
private readonly IRagonLogger _logger;
public Client(string host, int port)
{
_logger = LoggerManager.GetLogger("Client");
_udpClient = new UdpClient();
_endpoint = new IPEndPoint(IPAddress.Parse(host), port);
}
public void Send(byte[] data)
{
try
{
_udpClient.BeginSend(data, data.Length, _endpoint, SendCallback, null);
}
catch (Exception ex)
{
_logger.Error(ex.Message);
}
}
private void SendCallback(IAsyncResult ar)
{
try
{
_udpClient.EndSend(ar);
}
catch (Exception ex)
{
_logger.Error(ex.Message);
}
}
}
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
using Google.Protobuf;
using Google.Protobuf.Collections;
using Ragon.Server;
namespace Ragon.Relay;
public class Reporter
{
private readonly Client _client;
private readonly IRagonServer _server;
private readonly RelayConfiguration _configuration;
public Reporter(
RelayConfiguration relayConfiguration,
IRagonServer server,
string host,
int port
)
{
_client = new Client(host, port);
_server = server;
_configuration = relayConfiguration;
}
public void Done()
{
for (var i = 0; i < 10; i++)
{
var message = new Data();
message.Statistics = new Statistics()
{
Connections = _server.ConnectionRegistry.Contexts.Count,
ConnectionsLimit = _configuration.LimitConnections,
Rooms = _server.Lobby.Rooms.Count,
RoomsLimit = _configuration.LimitRooms,
};
var room = new Room()
{
Id = $"Room ID {i}",
};
for (var j = 0; j < 10; j++)
{
room.Players.Add(new Player()
{
Id = $"Player ID {i}",
});
}
message.Room = room;
_client.Send(message.ToByteArray());
}
}
}
@@ -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 ENet;
using Ragon.Server.IO;
namespace Ragon.Transport;
public sealed class ENetConnection: INetworkConnection
{
private static ushort _iterator = 0;
public ushort Id { get; }
public INetworkChannel Reliable { get; private set; }
public INetworkChannel Unreliable { get; private set; }
private Peer _peer;
public ENetConnection(Peer peer)
{
_peer = peer;
// Id = (ushort) peer.ID;
Id = _iterator++;
Reliable = new ENetReliableChannel(peer, NetworkChannel.RELIABLE);
Unreliable = new ENetUnreliableChannel(peer, NetworkChannel.UNRELIABLE);
}
public void Close()
{
_peer.Disconnect(0);
}
}
@@ -0,0 +1,54 @@
/*
* 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 ENet;
using Ragon.Protocol;
using Ragon.Server.IO;
namespace Ragon.Transport;
public sealed class ENetReliableChannel: INetworkChannel
{
private Peer _peer;
private byte _channelId;
private byte[] _data;
public ENetReliableChannel(Peer peer, NetworkChannel channel)
{
_peer = peer;
_data = new byte[1500];
_channelId = (byte) channel;
}
public void Send(byte[] data)
{
var newPacket = new Packet();
newPacket.Create(data, data.Length, PacketFlags.Reliable);
_peer.Send(_channelId, ref newPacket);
}
public void Send(RagonStream buffer)
{
_data = buffer.ToArray();
var newPacket = new Packet();
newPacket.Create(_data, _data.Length, PacketFlags.Reliable);
_peer.Send(_channelId, ref newPacket);
}
}
@@ -0,0 +1,141 @@
/*
* 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;
using ENet;
using Ragon.Protocol;
using Ragon.Server.IO;
using Ragon.Server.Logging;
namespace Ragon.Transport;
public sealed class ENetServer : INetworkServer
{
private readonly Host _host = new();
private readonly IRagonLogger _logger = LoggerManager.GetLogger(nameof(ENetServer));
private ENetConnection[] _connections = Array.Empty<ENetConnection>();
private INetworkListener _listener;
private uint _protocol;
private Event _event;
public void Listen(INetworkListener listener, NetworkConfiguration configuration)
{
Library.Initialize();
_connections = new ENetConnection[configuration.LimitConnections];
_listener = listener;
_protocol = configuration.Protocol;
var address = new Address
{
Port = (ushort)configuration.Port,
};
_host.Create(address, _connections.Length, 2, 0, 0, 1024 * 1024);
var protocolDecoded = RagonVersion.Parse(_protocol);
_logger.Info($"Listen at {configuration.Address}:{configuration.Port}");
_logger.Info($"Protocol: {protocolDecoded}");
}
public void Update()
{
bool polled = false;
while (!polled)
{
if (_host.CheckEvents(out _event) <= 0)
{
if (_host.Service(0, out _event) <= 0)
break;
polled = true;
}
switch (_event.Type)
{
case EventType.None:
{
_logger.Trace("None event");
break;
}
case EventType.Connect:
{
if (!IsValidProtocol(_event.Data))
{
_logger.Warning(
$"Mismatched protocol Server: {RagonVersion.Parse(_protocol)} Client: {RagonVersion.Parse(_event.Data)}, close connection");
_event.Peer.DisconnectNow(0);
break;
}
var connection = new ENetConnection(_event.Peer);
_connections[_event.Peer.ID] = connection;
_listener.OnConnected(connection);
break;
}
case EventType.Disconnect:
{
var connection = _connections[_event.Peer.ID];
_listener.OnDisconnected(connection);
break;
}
case EventType.Timeout:
{
var connection = _connections[_event.Peer.ID];
_listener.OnTimeout(connection);
break;
}
case EventType.Receive:
{
var peerId = (ushort)_event.Peer.ID;
var connection = _connections[peerId];
var dataRaw = new byte[_event.Packet.Length];
_event.Packet.CopyTo(dataRaw);
_event.Packet.Dispose();
_listener.OnData(connection, (NetworkChannel)_event.ChannelID, dataRaw);
break;
}
}
}
}
public void Broadcast(byte[] data, NetworkChannel channel)
{
var packet = new Packet();
var flag = channel == NetworkChannel.RELIABLE ? PacketFlags.Reliable : PacketFlags.None;
packet.Create(data, flag);
_host.Broadcast((byte)channel, ref packet);
}
public void Stop()
{
_host?.Dispose();
Library.Deinitialize();
}
private bool IsValidProtocol(uint protocol)
{
return protocol == _protocol;
}
}
@@ -0,0 +1,52 @@
/*
* 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 ENet;
using Ragon.Protocol;
using Ragon.Server.IO;
namespace Ragon.Transport;
public sealed class ENetUnreliableChannel: INetworkChannel
{
private Peer _peer;
private byte _channelId;
private byte[] _data;
public ENetUnreliableChannel(Peer peer, NetworkChannel channel)
{
_peer = peer;
_channelId = (byte) channel;
}
public void Send(byte[] data)
{
var newPacket = new Packet();
newPacket.Create(data, data.Length, PacketFlags.None);
_peer.Send(_channelId, ref newPacket);
}
public void Send(RagonStream buffer)
{
_data = buffer.ToArray();
var newPacket = new Packet();
newPacket.Create(_data, _data.Length, PacketFlags.None);
_peer.Send(_channelId, ref newPacket);
}
}
@@ -0,0 +1,79 @@
/*
* 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;
using System.Collections.Generic;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace Ragon.Server.IO;
public class Executor : TaskScheduler
{
private readonly ChannelReader<Task> _reader;
private readonly ChannelWriter<Task> _writer;
private readonly Queue<Task> _pendingTasks;
private readonly TaskFactory _taskFactory;
public Task Run(Action action, TaskCreationOptions task)
{
return _taskFactory.StartNew(action, task);
}
public Executor()
{
var channel = Channel.CreateUnbounded<Task>(new UnboundedChannelOptions()
{
SingleReader = true,
SingleWriter = true,
});
_reader = channel.Reader;
_writer = channel.Writer;
_taskFactory = new TaskFactory(this);
_pendingTasks = new Queue<Task>();
}
protected override IEnumerable<Task>? GetScheduledTasks()
{
throw new NotSupportedException();
}
protected override void QueueTask(Task task)
{
_writer.TryWrite(task);
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return false;
}
public void Update()
{
while (_reader.TryRead(out var task))
{
TryExecuteTask(task);
if (task.Status == TaskStatus.Running)
_pendingTasks.Enqueue(task);
}
while (_pendingTasks.TryDequeue(out var task))
_writer.TryWrite(task);
}
}
@@ -0,0 +1,69 @@
/*
* 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;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using Ragon.Server.IO;
using Ragon.Server.Logging;
namespace Ragon.Server.WebSocketServer;
public sealed class WebSocketConnection : INetworkConnection
{
private readonly IRagonLogger _logger = LoggerManager.GetLogger(nameof(WebSocketConnection));
public ushort Id { get; }
public INetworkChannel Reliable { get; private set; }
public INetworkChannel Unreliable { get; private set; }
public WebSocket Socket { get; private set; }
private WebSocketReliableChannel[] _channels;
public WebSocketConnection(WebSocket webSocket, ushort peerId)
{
Id = peerId;
Socket = webSocket;
var reliableChannel = new WebSocketReliableChannel(webSocket);
var unreliableChannel = new WebSocketReliableChannel(webSocket);
_channels = new[] { reliableChannel, unreliableChannel };
Reliable = reliableChannel;
Unreliable = unreliableChannel;
}
public void Close()
{
Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
}
public async Task Flush()
{
foreach (var channel in _channels)
{
try
{
await channel.Flush();
}
catch (Exception ex)
{
_logger.Error(ex);
}
}
}
}
@@ -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 System.Collections.Generic;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using Ragon.Protocol;
using Ragon.Server.IO;
namespace Ragon.Server.WebSocketServer;
public class WebSocketReliableChannel : INetworkChannel
{
private Queue<byte[]> _queue;
private WebSocket _socket;
public WebSocketReliableChannel(WebSocket webSocket)
{
_socket = webSocket;
_queue = new Queue<byte[]>(512);
}
public void Send(byte[] data)
{
_queue.Enqueue(data);
}
public void Send(RagonStream buffer)
{
var sendData = buffer.ToArray();
_queue.Enqueue(sendData);
}
public async Task Flush()
{
while (_queue.TryDequeue(out var sendData) && _socket.State == WebSocketState.Open)
{
await _socket.SendAsync(sendData, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, CancellationToken.None);
}
}
}
@@ -0,0 +1,176 @@
/*
* 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;
using System.Buffers;
using System.Collections.Generic;
using System.Net;
using System.Net.WebSockets;
using System.Threading;
using Ragon.Protocol;
using Ragon.Server.IO;
using Ragon.Server.Logging;
namespace Ragon.Server.WebSocketServer;
public class WebSocketServer : INetworkServer
{
private readonly IRagonLogger _logger = LoggerManager.GetLogger(nameof(WebSocketServer));
private INetworkListener _networkListener;
private Stack<ushort> _sequencer;
private Executor _executor;
private HttpListener _httpListener;
private WebSocketConnection[] _connections;
private List<WebSocketConnection> _activeConnections;
private CancellationTokenSource _cancellationTokenSource;
public WebSocketServer()
{
_sequencer = new Stack<ushort>();
_connections = Array.Empty<WebSocketConnection>();
_activeConnections = new List<WebSocketConnection>();
_executor = new Executor();
}
public async void StartAccept(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
WebSocketConnection connection = null!;
try
{
var context = await _httpListener.GetContextAsync();
if (!context.Request.IsWebSocketRequest)
{
context.Response.StatusCode = 200;
context.Response.ContentLength64 = 0;
context.Response.Close();
continue;
}
var webSocketContext = await context.AcceptWebSocketAsync(null);
var webSocket = webSocketContext.WebSocket;
var peerId = _sequencer.Pop();
connection = new WebSocketConnection(webSocket, peerId);
}
catch (Exception ex)
{
_logger.Error(ex);
continue;
}
_connections[connection.Id] = connection;
StartListen(connection, cancellationToken);
}
}
async void StartListen(WebSocketConnection connection, CancellationToken cancellationToken)
{
_activeConnections.Add(connection);
_networkListener.OnConnected(connection);
var webSocket = connection.Socket;
var rawData = new byte[2048];
var rawDataBuffer = new Memory<byte>(rawData);
var data = new ArrayBufferWriter<byte>();
while (webSocket.State == WebSocketState.Open || !cancellationToken.IsCancellationRequested)
{
try
{
while (true)
{
var result = await webSocket.ReceiveAsync(rawDataBuffer, cancellationToken);
var payload = rawDataBuffer.Slice(0, result.Count);
data.Write(payload.Span);
if (result.EndOfMessage)
break;
}
if (data.WrittenCount > 0)
{
_networkListener.OnData(connection, NetworkChannel.RELIABLE, data.WrittenMemory.ToArray());
data.Clear();
}
}
catch (Exception ex)
{
break;
}
}
_sequencer.Push(connection.Id);
_activeConnections.Remove(connection);
_networkListener.OnDisconnected(connection);
}
public void Update()
{
_executor.Update();
Flush();
}
public void Broadcast(byte[] data, NetworkChannel channel)
{
foreach (var activeConnection in _activeConnections)
activeConnection.Reliable.Send(data);
}
public async void Flush()
{
foreach (var conn in _activeConnections)
await conn.Flush();
}
public void Listen(
INetworkListener listener,
NetworkConfiguration configuration
)
{
_networkListener = listener;
_cancellationTokenSource = new CancellationTokenSource();
var limit = (ushort)configuration.LimitConnections;
for (ushort i = limit; i != 0; i--)
_sequencer.Push(i);
_sequencer.Push(0);
_connections = new WebSocketConnection[configuration.LimitConnections];
_httpListener = new HttpListener();
_httpListener.Prefixes.Add($"http://{configuration.Address}:{configuration.Port}/");
_httpListener.Start();
var protocolDecoded = RagonVersion.Parse(configuration.Protocol);
_logger.Info($"Listen at http://{configuration.Address}:{configuration.Port}/");
_logger.Info($"Protocol: {protocolDecoded}");
}
public void Stop()
{
_cancellationTokenSource.Cancel();
_httpListener.Stop();
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"serverKey": "defaultkey",
"serverType": "enet",
"serverAddress": "*",
"serverTickRate": 30,
"protocol": "1.0.0",
"port": 8000,
"limitConnections": 4095,
"limitPlayersPerRoom": 20,
"limitRooms": 200,
"limitBufferedEvents": 50,
"limitUserDataSize": 1024,
"limitPropertySize": 512
}
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Ragon.WebSockets</RootNamespace>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<Copyright>Eduard Kargin</Copyright>
<Authors>Eduard Kargin</Authors>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.4.0</Version>
<Title>Ragon Server WebSocket</Title>
<Description>Ragon Server WebSocket transport</Description>
<PackageProjectUrl>https://ragon.io</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<RepositoryUrl>https://github.com/edmand46/Ragon</RepositoryUrl>
<RepositoryType>Source</RepositoryType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ragon.Server\Ragon.Server.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Sources\" />
</ItemGroup>
</Project>
+25
View File
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Ragon.Core</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.4.0</Version>
<Title>Ragon.Server</Title>
<Copyright>Eduard Kargin</Copyright>
<Authors>Eduard Kargin</Authors>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://ragon.io</PackageProjectUrl>
<RepositoryUrl>https://github.com/edmand46/Ragon</RepositoryUrl>
<RepositoryType>Source</RepositoryType>
<LangVersion>10</LangVersion>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ragon.Protocol\Ragon.Protocol.csproj" />
</ItemGroup>
</Project>
+66
View File
@@ -0,0 +1,66 @@
using Ragon.Protocol;
namespace Ragon.Server.Data;
public class RagonData
{
private readonly Dictionary<string, byte[]> _data = new Dictionary<string, byte[]>();
public bool IsDirty { get; private set; }
public RagonData()
{
}
public void Read(RagonStream buffer)
{
var len = buffer.ReadUShort();
for (int i = 0; i < len; i++)
{
var key = buffer.ReadString();
var valueSize = buffer.ReadUShort();
if (valueSize > 0)
{
var value = buffer.ReadBinary(valueSize);
_data[key] = value;
}
else
{
_data[key] = Array.Empty<byte>();
}
}
}
public void Write(RagonStream buffer)
{
buffer.WriteUShort((ushort)_data.Count);
foreach (var prop in _data)
{
buffer.WriteString(prop.Key);
buffer.WriteUShort((ushort)prop.Value.Length);
buffer.WriteBinary(prop.Value);
}
var toDelete = _data
.Where(p => p.Value.Length == 0)
.Select(p => p.Key);
foreach (var prop in toDelete)
_data.Remove(prop);
IsDirty = false;
}
public void Snapshot(RagonStream buffer)
{
buffer.WriteUShort((ushort)_data.Count);
foreach (var prop in _data)
{
buffer.WriteString(prop.Key);
buffer.WriteUShort((ushort)prop.Value.Length);
buffer.WriteBinary(prop.Value);
Console.WriteLine($"Key: {prop.Key} Value: {prop.Value.Length}");
}
}
}
+51
View File
@@ -0,0 +1,51 @@
/*
* 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;
using Ragon.Server.Room;
namespace Ragon.Server.Event;
public class RagonEvent
{
public RagonRoomPlayer Invoker { get; private set; }
public ushort EventCode { get; private set; }
public ushort Size => (ushort)_size;
private uint[] _data = new uint[128];
private int _size = 0;
public RagonEvent(
RagonRoomPlayer invoker,
ushort eventCode
)
{
Invoker = invoker;
EventCode = eventCode;
}
public void Read(RagonStream buffer)
{
// _size = buffer.Capacity;
// buffer.ReadArray(_data, _size);
}
public void Write(RagonStream buffer)
{
// if (_size <= 0) return;
// buffer.WriteArray(_data, _size);
}
}
@@ -0,0 +1,121 @@
/*
* 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;
using Ragon.Server.IO;
using Ragon.Server.Lobby;
using Ragon.Server.Logging;
using Ragon.Server.Plugin;
namespace Ragon.Server.Handler
{
public sealed class AuthorizationOperation : BaseOperation
{
private readonly IRagonLogger _logger = LoggerManager.GetLogger(nameof(AuthorizationOperation));
private readonly IServerPlugin _serverPlugin;
private readonly IRagonServer _server;
private readonly RagonContextObserver _observer;
private readonly RagonServerConfiguration _configuration;
private readonly RagonStream _writer;
public AuthorizationOperation(RagonStream reader,
RagonStream writer,
IRagonServer server,
IServerPlugin serverPlugin,
RagonContextObserver observer,
RagonServerConfiguration configuration) : base(reader, writer)
{
_serverPlugin = serverPlugin;
_configuration = configuration;
_observer = observer;
_writer = writer;
_server = server;
}
public override void Handle(RagonContext context, NetworkChannel channel)
{
if (context.ConnectionStatus == ConnectionStatus.Authorized)
{
_logger.Warning("Player already authorized!");
return;
}
if (context.ConnectionStatus == ConnectionStatus.InProcess)
{
_logger.Warning("Player already request authorization!");
return;
}
var configuration = _configuration;
var key = Reader.ReadString();
var name = Reader.ReadString();
var payload = Reader.ReadString();
if (key == configuration.ServerKey)
{
var authorizeViaPlugin = _serverPlugin.OnAuthorize(new ConnectionRequest(_server, context.Connection.Id, payload));
if (authorizeViaPlugin)
return;
var id = Guid.NewGuid().ToString();
Approve(context, new ConnectionResponse(id, name, payload));
}
else
{
_logger.Warning($"Invalid key for connection {context.Connection.Id}");
Reject(context);
}
}
public void Approve(RagonContext context, ConnectionResponse result)
{
var lobbyPlayer = new RagonLobbyPlayer(context.Connection, result.Id, result.Name, result.Payload);
context.SetPlayer(lobbyPlayer);
context.ConnectionStatus = ConnectionStatus.Authorized;
_observer.OnAuthorized(context);
var playerId = context.LobbyPlayer.Id;
var playerName = context.LobbyPlayer.Name;
var playerPayload = context.LobbyPlayer.Payload;
_writer.Clear();
_writer.WriteOperation(RagonOperation.AUTHORIZED_SUCCESS);
_writer.WriteString(playerId);
_writer.WriteString(playerName);
_writer.WriteString(playerPayload);
var sendData = _writer.ToArray();
context.Connection.Reliable.Send(sendData);
_logger.Trace($"Approved {context.Connection.Id} as {playerId}|{context.LobbyPlayer.Name}");
}
public void Reject(RagonContext context)
{
_writer.Clear();
_writer.WriteOperation(RagonOperation.AUTHORIZED_FAILED);
var sendData = _writer.ToArray();
context.Connection.Reliable.Send(sendData);
context.Connection.Close();
_logger.Trace($"Rejected Connectin:{context.Connection.Id}");
}
}
}

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