Compare commits

...

305 Commits

Author SHA1 Message Date
s809 7cd1a37914 Restore ordering of session config entries 2024-09-08 21:21:38 +05:00
s809 8ca2052748 Refactor session config 2024-09-08 19:05:42 +05:00
s809 576d0f5666 Fix crash on F3 when commit hash was failed to retrieve 2024-09-07 21:47:43 +05:00
s809 877588ebed Fix rare NPE when receiving LODs 2024-09-07 21:47:02 +05:00
s809 875b5fffcc Fix client crashing 2024-09-05 23:45:04 +05:00
s809 6293cc8c27 Prefill levelKeyPrefix in new worlds 2024-09-05 14:22:00 +05:00
s809 0361e5c69b Fix compilation 2024-09-04 22:53:02 +05:00
s809 654355c991 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into serverside 2024-09-04 18:22:47 +05:00
James Seibel 4712a77d00 Up version number 2.2.1 -> 2.2.2-dev 2024-09-04 06:58:59 -05:00
James Seibel 57c5b2d5fc Up version 2.2.1-dev -> 2.2.1 and API 3.0.0 -> 3.0.1 2024-09-04 06:58:20 -05:00
s809 a2949b8124 Change some string & remove unused config item 2024-09-03 16:52:10 +05:00
James Seibel 50c5701836 Fix referencing unavailable GLFW methods for MC 1.18.2 and lower 2024-09-02 08:00:43 -05:00
s809 6fe0477ca7 Limit number of retries on request errors
Add a delay if rate limit is hit
2024-09-02 15:31:39 +05:00
James Seibel 18e075538d Fix LODs flashing when moving and improve LodQuadTree code 2024-09-01 21:10:22 -05:00
James Seibel b00e8a08e9 add annotations to QuadTree 2024-09-01 21:09:50 -05:00
James Seibel ac4ab11a74 Fix LodRenderSection and QuadNode toString methods 2024-09-01 21:09:17 -05:00
James Seibel c26631db57 Cull beacons based on X/Z distance instead of 3D distance 2024-09-01 17:28:08 -05:00
James Seibel 1daa06fff4 Disable instanced rendering on Mac when Sodium is present
Closes !793 (Generic Rendering crashes with Sodium on M1 Mac)
2024-09-01 17:02:43 -05:00
James Seibel f3ef6f25f4 Fix some beacon rendering/updating issues 2024-09-01 16:36:37 -05:00
James Seibel ec012d9fd6 Fix glass panes not affecting beacon colors 2024-09-01 15:04:24 -05:00
James Seibel fc90cf3377 add disableUnchangedChunkCheck config 2024-08-31 22:11:25 -05:00
James Seibel e1e42d1caf Fix cloud color not matching MC 2024-08-31 21:55:54 -05:00
James Seibel 95ce29e355 Update all pos objects to use getters to match with DhBlockPos
I'd prefer not to need getters/setters since it's cleaner to just call pos.x, but that doesn't allow for immutable/mutable distinction.
2024-08-31 20:50:22 -05:00
James Seibel 0fd818b077 Allow users to re-activate DH rendering if a rendering error is thrown 2024-08-31 20:20:51 -05:00
James Seibel ba59daf747 Add beacon nearby culling 2024-08-31 20:20:24 -05:00
James Seibel b7d94c2ed1 Fix RenderableBoxGroup not supporting clear() 2024-08-31 20:06:04 -05:00
James Seibel 7a057a8d53 fix Lod builder not showing the correct stacktrace in Intellij 2024-08-31 15:04:30 -05:00
James Seibel 49c6ab97a9 Remove unused threadpool 2024-08-31 15:04:12 -05:00
James Seibel ed0d80b37e Reduce deplayed LOD modified save from 2 sec -> 500 ms
This should make some LOD update operations appear faster
2024-08-31 15:01:37 -05:00
James Seibel 9768728c92 Add RollingAverage object 2024-08-31 14:58:08 -05:00
James Seibel 0c68544f2f Fix DhLightingEngine putting lights at relative (0,0) and add debug logic 2024-08-31 12:45:39 -05:00
s809 048b36f80d Add comment 2024-08-31 20:50:50 +05:00
s809 40f902e2f5 Delay loading first level when on server 2024-08-31 20:46:58 +05:00
James Seibel b1f154a0ea Add DhBlockPosMutable and make the original immutable
This is to prevent issues with some methods accidentally mutating shared positions
2024-08-30 07:35:59 -05:00
James Seibel 628c9b071f DhBlockPos cleanup 2024-08-29 20:05:26 -05:00
James Seibel ed39b6181f Fix DH beacon detection logic mutating input block pos
alternate title: Fix DH beacon detection logic breaking the lighting engine
2024-08-29 19:54:36 -05:00
James Seibel 1d6d712483 Fix LODs not updating in the nether or when blocks are changed underground 2024-08-29 07:32:55 -05:00
s809 62ddb46674 Restore LOD fetching by distance 2024-08-29 17:09:08 +05:00
James Seibel 73c4f0ffcd Add a quick DH cloud UI config 2024-08-28 07:16:20 -05:00
s809 375cd44cbd Do not start generator until server responds with config 2024-08-27 16:35:22 +05:00
s809 ce057525d3 Show incompatible protocol version in F3 2024-08-27 16:02:15 +05:00
s809 01c879951c Fix sync on login 2024-08-26 16:53:59 +05:00
s809 4f19f05f3b Change naming of rate limit related items 2024-08-26 00:39:28 +05:00
s809 47f68c7ed3 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into serverside 2024-08-24 02:58:17 +05:00
s809 39db421aa1 Remove mentions of generation task priorities 2024-08-23 17:28:15 +05:00
s809 f4b0c08822 Fix network compression pool not shutting down 2024-08-23 17:11:32 +05:00
s809 f17c3fa267 "Fix" buffer release errors in FullDataPayload 2024-08-23 14:16:20 +05:00
James Seibel 48d1005be6 Up version number 2.2.0 -> 2.2.1-dev 2024-08-20 19:16:18 -05:00
s809 81e6f55dbf Use same packet resource for all versions 2024-08-19 17:55:59 +05:00
s809 b5e2019d28 Add logging of request group lifecycle 2024-08-15 15:46:24 +05:00
s809 84dca85447 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into serverside 2024-08-11 20:59:23 +05:00
s809 8c3e8136be Do not clear keyed level on close event 2024-08-10 23:53:22 +05:00
s809 606c157958 Make encode/decode error handling work correctly 2024-08-10 23:26:04 +05:00
s809 ba3677b641 Make error handling somewhat work 2024-08-10 19:49:33 +05:00
s809 dee13a4ec4 Merge branch 'serverside-experimental/prevent-disconnects' into serverside 2024-08-07 22:20:29 +05:00
s809 71e4cd6272 Fix real-time updates 2024-08-06 23:12:13 +05:00
s809 82c5de7dfe Fix Neoforge not being able to connect to vanilla servers 2024-08-05 14:51:17 +05:00
s809 9d11733444 Fix dimension switching (untested) 2024-08-03 15:42:43 +05:00
s809 e62c6a5c55 [skip ci] Prevent disconnects on encode/decode/handle errors 2024-08-01 22:19:10 +05:00
s809 93b57ae2e1 Increase defaults for network compression threads 2024-07-30 10:52:27 +05:00
s809 e5033a0c0f Use FullDataPayload instead of reusing messages 2024-07-29 13:10:04 +05:00
s809 32abe15b09 Add build number on F3 screen 2024-07-28 20:19:38 +05:00
s809 2b65e33aa7 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into serverside 2024-07-28 19:44:25 +05:00
s809 bc6ab6c840 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into serverside 2024-07-28 19:01:28 +05:00
s809 ccbb071704 Reapply "Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into serverside"
This reverts commit d26cb41048.
2024-07-28 17:05:58 +05:00
s809 d26cb41048 Revert "Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into serverside"
This reverts commit a15aaa573d, reversing
changes made to 83c1a2fd63.
2024-07-27 23:06:34 +05:00
s809 a15aaa573d Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into serverside 2024-07-27 21:43:21 +05:00
s809 83c1a2fd63 Fix handler registration 2024-07-20 23:56:24 +05:00
s809 0e904a388c Raise full data chunk size limit 2024-07-16 23:56:48 +05:00
s809 3723137fea Remove unused method 2024-07-16 23:52:28 +05:00
s809 71e54cc9ab Up protocol version 2024-07-16 23:51:30 +05:00
s809 b4cf962a85 Fix event handler registration 2024-07-16 23:40:20 +05:00
s809 1a30f240ef Client-side chunk handling 2024-07-16 17:49:09 +05:00
s809 b3b5ac63c8 Server-side logic is pretty much finished 2024-07-16 00:27:22 +05:00
s809 16abc0faa1 [skip ci] Initial chunked full data message payloads 2024-07-15 07:57:20 +05:00
s809 778c2f894e Fix level handling for real time updates 2024-07-12 23:11:36 +05:00
s809 2617cd294d Add cache for ignoring responses for cancelled requests 2024-07-11 23:06:23 +05:00
s809 43b421042d Add a comment to semaphores 2024-07-11 22:08:10 +05:00
s809 af69678545 Fix player tracking on server 2024-07-10 23:51:39 +05:00
s809 85341f7a6d Add missing semaphore release 2024-07-09 18:06:16 +05:00
s809 9e0edd0cf3 Prevent request cancellation deadlock 2024-07-09 14:47:45 +05:00
s809 84e90a7a9b Use dedicated thread pool for data compression 2024-07-08 23:07:58 +05:00
s809 48a8cdc365 Disable parent update propagation 2024-07-07 19:46:47 +05:00
s809 171e6b9bcd Reset state of level detection on world exit 2024-07-07 01:49:44 +05:00
s809 7e48c49e33 Make data source encoding lazy and move it off server thread 2024-07-01 00:15:54 +05:00
s809 1b48d61d3f Rename CloseEvent 2024-06-30 22:35:41 +05:00
s809 47541fa99c Move fixing the dimension name into even more correct place 2024-06-28 23:18:24 +05:00
s809 13638cedee Move fixing the dimension name into correct place 2024-06-28 19:28:36 +05:00
s809 4cdf31cfe8 Fix paths unable to be created on Windows 2024-06-28 17:25:09 +05:00
s809 3ff9a93066 Fix test compilation failing 2024-06-28 15:52:39 +05:00
s809 083a036666 Use level's ResourceLocation instead of dimension type's 2024-06-28 15:28:33 +05:00
s809 a0efe44e8d Clean up package structure 2024-06-26 23:57:37 +05:00
s809 8d110e22dd Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into refactor/remove-tcp-connection 2024-06-26 15:13:03 +05:00
s809 4d6e11fdeb Fix compilation 2024-06-26 14:55:21 +05:00
s809 454b15ff60 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into refactor/remove-tcp-connection 2024-06-26 14:54:00 +05:00
s809 d2f4972693 Put messages received before player joining into queue 2024-06-16 00:45:16 +05:00
s809 acb299530d Avoid reloading levels when unnecessary 2024-06-14 16:25:20 +05:00
s809 7c705015e6 Use level keys 2024-06-13 16:44:37 +05:00
s809 bcb21be848 Show dimension names 2024-06-10 23:39:57 +05:00
s809 562594de2f Better error on invalid dimension 2024-06-09 22:01:30 +05:00
s809 03a00bb7ca Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into refactor/remove-tcp-connection 2024-06-09 21:18:08 +05:00
s809 dc3aa939db Fix logging errors 2024-06-08 00:07:24 +05:00
s809 39391b944f Add toString to messages & fix incorrect call 2024-06-06 21:25:19 +05:00
s809 a6fc8f1702 Fix compilation 2024-06-02 20:00:09 +05:00
s809 b0777789fd Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into refactor/remove-tcp-connection 2024-06-02 19:58:53 +05:00
s809 24a2a48deb Increase section reload delay 2024-06-01 19:49:23 +05:00
s809 e08faa7943 Fix requests breaking on rejoining 2024-06-01 12:17:04 +05:00
s809 a000afbc60 Kinda works, rejoining is broken 2024-05-29 23:36:34 +05:00
s809 a3e34c9738 [skip ci] Still a mess 2024-05-27 22:01:57 +05:00
s809 1f63bdf124 [skip ci] Somewhere 2024-05-22 23:18:18 +05:00
s809 44205664b5 [skip ci] Incomplete 2024-05-21 22:54:57 +05:00
s809 40d019d7e8 Merge remote-tracking branch 'origin/main' 2024-05-18 22:14:19 +05:00
s809 96755e6174 Move overrides in LAN to config 2024-05-18 22:09:19 +05:00
s809 bd5866787f Update Config.java 2024-05-17 12:41:09 +00:00
s809 d9651bbd27 Merge branch 'main' into 'main'
rewrite config comments

See merge request s809/distant-horizons-core!1
2024-05-17 12:39:54 +00:00
Yeshi0 a1f4442a53 rewrite config comments 2024-05-14 20:16:44 +02:00
s809 e6b140245b Fix awt dependency error 2024-05-12 17:34:58 +05:00
s809 08336027b2 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into feature/2.0.4 2024-05-11 19:16:01 +05:00
s809 543b5ed49a Fix dimension switching (at cost of breaking immersive portals) 2024-05-10 23:56:07 +05:00
s809 3d86c5c5ee Fix generated sections not appearing 2024-05-10 21:13:40 +05:00
s809 8e1009ab3f Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into feature/2.0.4 2024-05-05 19:51:21 +05:00
s809 551a5f1a72 Fix updates 2024-05-05 16:55:15 +05:00
s809 2664f96e0f Generation works, updates don't 2024-05-03 22:56:01 +05:00
s809 dde2bcc947 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2024-05-02 21:47:35 +05:00
s809 3e3e97385b Fix 1.16.5 and 1.17.1 builds 2024-04-17 21:02:14 +05:00
s809 05fba12038 Prevent generation when config is not acknowledged by server 2024-04-16 21:55:02 +05:00
s809 c1f2803c56 Add missing newlines for override configs 2024-04-16 21:39:56 +05:00
s809 7006c669f0 More verbose invalid level error 2024-04-16 21:38:37 +05:00
s809 7cd0c956f6 Fix IPv6 addresses not being properly handled 2024-04-16 21:27:26 +05:00
s809 7c33dda11b Add FlushConsolidationHandler 2024-04-16 20:32:50 +05:00
s809 5f9f0f23b8 Fix integer overflows 2024-04-12 23:14:58 +05:00
s809 284191a904 Fix Neoforge 2024-04-12 22:36:04 +05:00
s809 0ad60cd5d9 Merge branch 'feature/plugin-channel' 2024-04-02 22:34:45 +05:00
s809 8485d1585a Remove localhost from check 2024-04-02 22:27:36 +05:00
s809 1cd2f75dd2 Fix compilation 2024-04-01 21:52:20 +05:00
s809 bdc4fa4477 Fix errors when on vanilla server 2024-04-01 00:55:15 +05:00
s809 ff7720a8d6 Adjust server port change behavior 2024-04-01 00:07:32 +05:00
s809 6829709123 Fix reconnection logic 2024-03-30 23:40:05 +05:00
s809 0d355f675a Incomplete 2024-03-26 00:30:57 +05:00
s809 af2f0e8582 Fix Forge 2024-03-18 21:37:58 +05:00
s809 3362570123 Add thread pool task buildup limiter 2024-03-16 12:28:38 +05:00
s809 e008eb8a13 Server side plugin networking
Untested port for Forge
2024-03-14 21:46:23 +05:00
s809 1f438d8f87 Fix world setting 2024-03-10 21:09:52 +05:00
s809 278ae04532 Initial buggy plugin channel support 2024-03-05 22:09:30 +05:00
s809 2d4f033891 Do not wait for world gen tasks to stop while shutting down queue 2024-02-25 18:05:45 +05:00
s809 1fd85f2249 Show server side messages in F3 on disconnect 2024-02-24 22:48:24 +05:00
s809 d966697ecb Change some wording 2024-02-24 21:37:54 +05:00
s809 2e6c0ba356 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2024-02-22 23:06:21 +05:00
s809 ebb47990f4 Fix immersive portals 2024-02-15 23:15:01 +05:00
s809 15fe39c1bb Fix connection exception handling 2024-02-13 22:03:55 +05:00
s809 7bb0ec3148 Fix compilation 2024-02-12 21:39:30 +05:00
s809 6fcfacd346 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2024-02-12 21:38:58 +05:00
s809 48212ba746 Add another config option
Fix rate limiting issues (kinda)
2024-02-11 19:39:35 +05:00
s809 7bf9ce141a Avoid doing requests on join 2024-02-10 17:06:49 +05:00
s809 2353bd7545 Fix compilation 2024-02-07 21:03:37 +05:00
s809 02ddf5773c Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2024-02-07 20:49:49 +05:00
s809 3e37e9ee08 Remove unused packets 2024-02-04 21:57:57 +05:00
s809 b1b907bfe5 Silence a warning 2024-02-04 21:33:21 +05:00
s809 d347dbd222 Fix typo 2024-02-04 21:29:21 +05:00
s809 44527630da Fix queue not filled when generation is toggled 2024-02-04 21:00:02 +05:00
s809 df6ac6d740 Add connection state on F3 screen
Hide refresh queue from debug when disabled
2024-02-04 20:20:57 +05:00
s809 2ce08f8957 Merge remote-tracking branch 'origin/main' 2024-02-04 02:36:04 +05:00
s809 1baa666d23 Add priority distance limiting by ratio 2024-02-04 02:34:53 +05:00
s809 e5e4551038 Some logging changes 2024-02-03 23:02:31 +05:00
s809 0cfbe09558 Use ConfigBasedLogger
Add sub-sectioning to server networking section
2024-02-03 22:24:34 +05:00
s809 7d72e82325 Change a comment in Config 2024-02-03 20:17:21 +05:00
s809 3f823c58cc Fix exception while saving new data 2024-02-02 05:48:36 +00:00
s809 3932ea21c2 Limit rate+concurrency instead of only concurrency
Rename post-relog update to Login sync
2024-01-29 22:36:13 +05:00
s809 c9d426e581 Refactor ILevelRelatedMessage and RemotePlayerConnectionHandler 2024-01-28 22:37:50 +05:00
s809 61c516df1d Post-relog updates 2024-01-27 19:45:30 +05:00
s809 7e2019abd4 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2024-01-21 17:20:49 +05:00
s809 f0f3614b9d Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2024-01-16 20:17:43 +05:00
s809 573a284580 Fix generation 2024-01-13 19:44:48 +05:00
s809 310c890474 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2024-01-13 16:51:12 +05:00
s809 8a309b56d9 Fix crash when changing config while disconnected 2024-01-13 16:08:49 +05:00
s809 adc3f15c97 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2024-01-08 17:50:00 +05:00
s809 962f523af9 Fix 1.16.5 and 1.17.1 builds 2024-01-05 22:37:27 +05:00
s809 dbc1ad4cb7 Add names to network threads 2024-01-05 22:08:37 +05:00
s809 8b0f6a4414 Daemonize timers 2024-01-05 22:08:16 +05:00
s809 502aaf6a8a Add a config to delay generation requests, for cases when gen tasks are not filled properly yet 2023-12-28 18:58:50 +05:00
s809 5c644fbf5b Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-12-24 19:16:03 +05:00
s809 da60ca7560 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-12-21 19:17:51 +05:00
s809 a5a56c7eb7 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-12-21 11:47:51 +05:00
s809 ff8b1f24d0 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-12-20 23:18:57 +05:00
s809 904c1a7d71 Add protection against hang on shutdown 2023-12-19 23:20:23 +05:00
s809 02aca6f044 Allow section state requests to be controlled separately 2023-12-19 22:52:19 +05:00
s809 b4ea632b93 Add kick trigger on repeated rate limit hitting
Remove some unused code
2023-12-18 23:10:39 +05:00
s809 a2cc1c2913 Revert "Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core"
This reverts commit 522a799516, reversing
changes made to 04e43ebec8.
2023-12-18 22:15:34 +05:00
s809 522a799516 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-12-18 19:17:14 +05:00
s809 04e43ebec8 Remove current post-relog update feature 2023-12-18 19:13:57 +05:00
s809 077d72c23b Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-12-15 20:29:15 +05:00
s809 cb95c45d76 Fix compilation 2023-12-06 22:06:37 +05:00
s809 a9de079132 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-12-06 21:09:19 +05:00
s809 166cc55e3c Abstract away ChannelHandlerContext from user code 2023-11-27 23:46:05 +05:00
s809 f36bffa4b7 Add missing config entries in chat commands 2023-11-26 21:32:50 +05:00
s809 6da1e75dad Fix server side generation setting being ignored 2023-11-25 18:04:25 +05:00
s809 82d66ca392 Basic chat commands 2023-11-25 17:44:51 +05:00
s809 e98cf1f2b5 Hide all unrelated config entries 2023-11-23 20:58:09 +05:00
s809 77bd333fff Fix RejectedExecutionException when leaving server 2023-11-20 17:18:05 +05:00
s809 b791a185a2 Deduplicate code in MultiplayerConfigChangeListener 2023-11-20 16:54:17 +05:00
s809 c64d7fedd2 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-11-19 22:56:56 +05:00
s809 d2f5c02238 Bandaid fix to prevent duplicate section requests 2023-11-12 17:17:15 +05:00
s809 9cfcf37fb3 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-11-11 19:20:33 +05:00
s809 84d36df388 Do not clean up rendering on a dedicated server 2023-11-04 16:56:15 +05:00
s809 880abd0124 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-11-03 21:05:27 +05:00
s809 72274bfd7d Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-10-30 15:26:29 +05:00
s809 9640169be9 Make post-relog update independent from generation toggle 2023-10-23 23:18:52 +05:00
Steveplays28 9daf0c7317 chore: Add missing language entries for the server networking config 2023-10-20 14:07:42 +02:00
Steveplays28 a4df5a8ed8 style: Rename renderDistance to renderDistanceRadius to stay in sync with the main config 2023-10-20 14:03:19 +02:00
Steveplays28 df3d20f94e fix: Fix getting LOD render distance radius from the config
This config option was renamed recently, to fix LODs not rendering out to the LOD render distance border. Also removed an unused import.
2023-10-20 13:49:55 +02:00
Steveplays28 f3b40f51a2 Merge branch 'main-upstream'
# Conflicts:
#	core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataFileHandler.java
#	core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java
#	core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java
2023-10-20 13:41:33 +02:00
s809 e3d7598501 Fix compilation 2023-10-14 22:13:01 +05:00
s809 596b822a5d Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-10-14 22:09:15 +05:00
s809 e9e7ac48b1 Fix compilation 2023-09-28 23:28:18 +05:00
s809 2e2254fbbd Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-09-28 22:54:35 +05:00
s809 8bda3351b8 Make worldGenLoopingQueue lock free 2023-09-27 21:40:59 +05:00
Steveplays28 532f3adba8 Merge branch 'main-upstream' 2023-09-27 14:09:04 +02:00
s809 cc8b97f2fe Improve management of frequent real time updates 2023-09-26 22:03:04 +05:00
s809 db31c54309 Fix Enable Distant Generation config being ignored 2023-09-26 13:21:37 +05:00
s809 375e81b17d Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-09-26 11:40:38 +05:00
s809 4fb9126d49 Fix unloaded file check 2023-09-24 18:48:14 +05:00
Steveplays28 ed2fca240e fix: Fix compiling after merge 2023-09-24 11:33:49 +02:00
Steveplays28 f5549f2ebe Merge remote-tracking branch 'origin-core/main' 2023-09-24 11:28:38 +02:00
Steveplays28 18e9cfe2a5 Merge branch 'main-upstream' 2023-09-24 11:24:55 +02:00
s809 e4e6753a02 Add gen task rate display 2023-09-24 14:24:31 +05:00
Steveplays28 8e7855405d Merge remote-tracking branch 'origin-core/main' 2023-09-23 16:34:07 +02:00
Steveplays28 381cae7307 feat: Add config option for server port 2023-09-23 16:33:43 +02:00
s809 e94d67916d Fix compiling
Remove unused config category
Use pooling when receiving sections
Fix use of real time update config
Fix debug renderer not unregistered on queue close
2023-09-23 15:32:24 +05:00
Steveplays28 c98f9476cb Merge remote-tracking branch 'upstream-core/main'
# Conflicts:
#	core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiDebuggingConfig.java
#	core/src/main/java/com/seibel/distanthorizons/core/config/Config.java
#	core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java
#	core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/HighDetailIncompleteFullDataSource.java
#	core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java
#	core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderDataMetaFile.java
#	core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java
#	core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java
#	core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java
#	core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java
2023-09-22 15:40:31 +02:00
s809 6c36f3c7e9 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-09-21 21:04:58 +05:00
s809 877a89d17a Add config for a few slow features 2023-09-21 21:00:23 +05:00
s809 d325a69e3f Move context levels to requests 2023-09-21 10:16:34 +05:00
s809 08704aad2c Allow selecting specific IDebugRenderable's for rendering 2023-09-19 21:32:59 +05:00
s809 a80bb082b3 Validate response types 2023-09-19 20:19:25 +05:00
s809 fccd197cd5 Fix chunk updates sent to incomplete connections
Hide useless warns, for cases when listener is not configured yet
2023-09-19 18:53:10 +05:00
s809 2cfc2c81c8 Fix future id collisions between c<->s
(cause of occasional hangs on disconnection)
Add packet trace logging
2023-09-19 17:28:26 +05:00
s809 6f4e105542 Fix compiling 2023-09-18 13:44:19 +05:00
s809 633b3f2033 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-09-18 13:24:43 +05:00
Steveplays28 f0a62c813a Merge remote-tracking branch 'upstream-core/main' 2023-09-12 17:35:29 +02:00
s809 21f03526f8 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-09-10 12:03:29 +05:00
Steveplays28 0e7a96d308 fix: Fix missing IServerKeyedClientLevel import 2023-09-09 21:03:17 +02:00
Steveplays28 87572246a6 Merge remote-tracking branch 'upstream-core/main' 2023-09-09 20:53:44 +02:00
Steveplays28 025484d5b4 Merge remote-tracking branch 'upstream-core/main' 2023-09-09 14:47:07 +02:00
Steveplays28 15e4b1316e Merge remote-tracking branch 'upstream-core/main' 2023-09-08 19:54:43 +02:00
s809 e02eddc60e Fix compiling 2023-09-07 21:07:16 +05:00
s809 1e1ddd505b Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-09-07 20:07:30 +05:00
s809 415f16507b Add post-rejoin updates 2023-09-02 01:23:56 +05:00
s809 cb95978502 Fix compilation 2023-08-27 19:56:12 +05:00
s809 1b729f3fe7 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-08-27 19:10:25 +05:00
s809 d84e097fa2 Merge branch 'client-level-error' of https://gitlab.com/s809/distant-horizons-core into client-level-error 2023-08-27 18:43:06 +05:00
s809 51ad901206 Fix loading ClientLevel on server 2023-08-27 18:41:47 +05:00
s809 517925a207 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-08-26 21:33:44 +05:00
s809 c5abc22c58 Merge branch 'feat/server-updates' 2023-08-24 00:01:07 +05:00
s809 2330377212 Send updates at chunk level instead of sections 2023-08-23 23:55:16 +05:00
s809 9db56bbf87 test 2023-08-22 20:41:42 +05:00
s809 52a90fec6c Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into feat/server-updates 2023-08-22 19:12:43 +05:00
Steveplays28 3630dc724d fix: Add null check to config GUI when adding a screen change listener 2023-08-22 10:52:19 +02:00
s809 627bfbc007 Incomplete 2023-08-21 22:08:35 +05:00
Steveplays28 d9283e938b feat: Update rendering block ignores
Barrier blocks, structure void blocks, light blocks, and air blocks now share 2 `HashMap`s that define blocks that should be ignored by the LOD builder.
2023-08-21 03:41:27 +02:00
s809 ce4d50654d Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-08-19 22:28:13 +05:00
s809 7568ab56f6 Fix hang when near completion of loading from server
Replace total value with sum of pending+finished requests
2023-08-19 22:21:01 +05:00
s809 bf58654a10 Fix prioritization not working properly 2023-08-19 21:22:51 +05:00
Steveplays28 e4576c7d51 Merge remote-tracking branch 'upstream-core/main' 2023-08-19 11:41:38 +02:00
Steveplays28 a1f07c075e Merge remote-tracking branch 'upstream-core/main' 2023-08-18 15:12:11 +02:00
s809 84c4ad6cdd Add missing level wrapper argument (and a bunch of null checks) 2023-08-18 15:04:59 +02:00
Steveplays28 47aef1f349 Merge remote-tracking branch 'upstream-core/main'
# Conflicts:
#	core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java
#	core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java
#	core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/FullDataPointIdMap.java
#	core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java
#	core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/HighDetailIncompleteFullDataSource.java
#	core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/LowDetailIncompleteFullDataSource.java
#	core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataFileHandler.java
#	core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/RemoteFullDataFileHandler.java
#	core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java
#	core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java
#	core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java
#	core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java
#	core/src/main/java/com/seibel/distanthorizons/core/level/IDhServerLevel.java
#	core/src/main/java/com/seibel/distanthorizons/core/level/ServerLevelModule.java
#	core/src/main/java/com/seibel/distanthorizons/core/network/NetworkEventSource.java
#	core/src/main/java/com/seibel/distanthorizons/core/network/messages/AckMessage.java
#	core/src/main/java/com/seibel/distanthorizons/core/network/messages/CloseReasonMessage.java
#	core/src/main/java/com/seibel/distanthorizons/core/network/messages/HelloMessage.java
#	core/src/main/java/com/seibel/distanthorizons/core/network/messages/PlayerUUIDMessage.java
#	core/src/main/java/com/seibel/distanthorizons/core/network/messages/RemotePlayerConfigMessage.java
#	core/src/main/java/com/seibel/distanthorizons/core/network/objects/RemotePlayer.java
#	core/src/main/java/com/seibel/distanthorizons/core/network/protocol/EMessageHandlerSide.java
#	core/src/main/java/com/seibel/distanthorizons/core/network/protocol/INetworkMessage.java
#	core/src/main/java/com/seibel/distanthorizons/core/network/protocol/MessageRegistry.java
#	core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkChannelInitializer.java
#	core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkExceptionHandler.java
#	core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java
#	core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java
#	core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/block/IBlockStateWrapper.java
#	core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/misc/IServerPlayerWrapper.java
#	core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/IBiomeWrapper.java
2023-08-16 11:36:00 +02:00
Steveplays28 1e7a25046a fix: Fix BlockStateWrapper and Biome serializing to empty strings 2023-08-15 00:03:13 +02:00
Steveplays28 87a9e93278 fix: Fix server trying to access client instance 2023-08-14 19:23:39 +02:00
Steveplays28 2d3062008e Merge remote-tracking branch 'upstream-core/main' 2023-08-14 15:40:41 +02:00
Steveplays28 8dcec7a1bd refactor: Re-add old serialization method
This is used by some override methods that don't have access to the level.
These old serialization methods have `FIXME`s so they're easy to find in the future.
2023-08-13 23:18:55 +02:00
Steveplays28 d839b6e4bd Merge remote-tracking branch 'upstream-core/main'
# Conflicts:
#	core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/FullDataPointIdMap.java
#	core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java
#	core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/HighDetailIncompleteFullDataSource.java
#	core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/LowDetailIncompleteFullDataSource.java
#	core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java
2023-08-13 22:59:43 +02:00
s809 0c155ac8cd Generation task prioritization (loaded > unloaded > ungenerated) 2023-08-13 18:59:08 +05:00
s809 271b193543 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-08-13 01:53:06 +05:00
s809 e4ac25f4ce Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-08-08 20:57:30 +05:00
s809 52f9e3e9e4 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-08-08 12:35:21 +05:00
s809 6bf32ff85c Add debug renderer for remote generation queue 2023-08-05 22:30:33 +05:00
s809 9bd432ad7f Fix memory leak related to requests 2023-08-04 22:12:09 +05:00
s809 04fc4aa676 Remove unnecessary logging 2023-08-04 21:51:53 +05:00
s809 8ee4b8b4c9 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-08-04 21:10:41 +05:00
s809 5767668efa Somewhat proper request cancellation 2023-08-04 21:04:34 +05:00
s809 76b226b865 Fix switching dimensions (request cancellation is broken) 2023-08-04 13:06:56 +05:00
s809 1788c18d59 Everything I've done so far (not working/finished) 2023-08-01 22:31:16 +05:00
s809 2251cd4c25 Attempt to fix dimension switching 2023-08-01 17:04:40 +05:00
s809 781b588980 Implement server-side rate limiting 2023-08-01 12:55:36 +05:00
s809 74ac4fe64f Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-07-31 17:43:16 +05:00
s809 6d1f9803ce Avoid losing requests due to concurrency 2023-07-31 17:42:14 +05:00
s809 95d721e1a3 World generation 2023-07-31 15:23:56 +05:00
s809 d2f2a3b8aa Attempt to use GeneratedFullDataFileHandler 2023-07-24 19:59:16 +05:00
s809 768ce5b8fd something 2023-07-24 19:16:05 +05:00
s809 3e9f741d62 Implement transferring of missing full data source types 2023-07-24 14:15:03 +05:00
s809 133f007bde Fix mistake 2023-07-24 11:01:10 +05:00
s809 a623a3bfab Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-07-24 10:41:17 +05:00
s809 4157a39589 Somewhat working generation 2023-07-24 01:46:09 +05:00
s809 10e2873497 Downloads already visited chunks 2023-07-23 23:48:42 +05:00
s809 d7ef6c8a72 Got chunks to generate on server 2023-07-23 20:34:40 +05:00
s809 d29ba9d423 Requests now work properly
Merge request tracker into event source
2023-07-23 14:39:29 +05:00
s809 0c60395426 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-07-23 11:08:31 +05:00
s809 4ac1c0d4b3 Sending some responses
Rejoin is horribly broken
2023-07-19 22:29:33 +05:00
Steveplays28 9a0c9e9b7d feat: Implement syncing of DhSectionPos for chunk requests/responses 2023-07-19 16:48:43 +02:00
s809 79408c081a Close network request tacker properly 2023-07-19 17:40:38 +05:00
s809 218411902e Incomplete something 2023-07-19 15:54:17 +05:00
s809 a42f8367a4 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-07-19 12:20:11 +05:00
s809 02acfaa3ed Finer control over message handling (untested) 2023-07-19 12:19:39 +05:00
s809 9a2799e83b Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2023-07-16 12:47:44 +05:00
s809 73e6ce75b0 Replace "tell version to client" part with simple ack because YAGNI 2023-07-16 12:38:32 +05:00
155 changed files with 5729 additions and 2544 deletions
+1 -1
View File
@@ -537,7 +537,7 @@ ij_groovy_wrap_chain_calls_after_dot = false
ij_groovy_wrap_long_lines = false
[{*.har,*.json,*.png.mcmeta,mcmod.info,pack.mcmeta}]
indent_size = 4
indent_size = 2
ij_json_array_wrapping = split_into_lines
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
@@ -180,7 +180,7 @@ public class DhApi
* This version should be updated whenever non-breaking fixes are added to the Distant Horizons API.
* @since API 1.0.0
*/
public static int getApiPatchVersion() { return ModInfo.API_PATH_VERSION; }
public static int getApiPatchVersion() { return ModInfo.API_PATCH_VERSION; }
/**
* Returns the mod's semantic version number in the format: Major.Minor.Patch
@@ -210,4 +210,4 @@ public class DhApi
*/
public static boolean isDhThread() { return Thread.currentThread().getName().startsWith(ModInfo.THREAD_NAME_PREFIX); }
}
}
@@ -28,10 +28,8 @@ import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
*/
public interface IDhApiDimensionTypeWrapper extends IDhApiUnsafeWrapper
{
String getDimensionName();
boolean hasCeiling();
boolean hasSkyLight();
}
}
@@ -35,6 +35,8 @@ public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
{
IDhApiDimensionTypeWrapper getDimensionType();
String getDimensionName();
EDhApiLevelType getLevelType();
boolean hasCeiling();
@@ -69,4 +71,4 @@ public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
*/
IDhApiCustomRenderRegister getRenderRegister();
}
}
@@ -26,15 +26,22 @@ package com.seibel.distanthorizons.coreapi;
public final class ModInfo
{
public static final String ID = "distanthorizons";
/** The internal protocol version used for networking */
public static final int PROTOCOL_VERSION = 1;
/** The protocol version used for multiverse networking */
public static final int MULTIVERSE_PLUGIN_PROTOCOL_VERSION = 1;
public static final String RESOURCE_NAMESPACE = "distant_horizons";
public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial";
// region Protocol versions
// Incremented every time any packets are added, changed or removed, with a few exceptions.
public static final int PROTOCOL_VERSION = 3;
public static final String WRAPPER_PACKET_PATH = "message";
// endregion
/** The internal mod name */
public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.2.0-a";
public static final String VERSION = "2.2.2-a-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */
public static boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
@@ -43,12 +50,9 @@ public final class ModInfo
/** This version should be updated whenever new methods are added to the DH API */
public static final int API_MINOR_VERSION = 0;
/** This version should be updated whenever non-breaking fixes are added to the DH API */
public static final int API_PATH_VERSION = 0;
public static final String NETWORKING_RESOURCE_NAMESPACE = "distant_horizons";
public static final String MULTIVERSE_PLUGIN_NAMESPACE = "world_control";
public static final int API_PATCH_VERSION = 1;
/** All DH owned threads should start with this string to allow for easier debugging and profiling. */
public static final String THREAD_NAME_PREFIX = "DH-";
}
}
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
import com.seibel.distanthorizons.core.api.external.methods.config.DhApiConfig;
@@ -77,16 +78,18 @@ public class Initializer
throw new RuntimeException(e);
}
// attempt to setup Swing so we can display dialogs (popup windows)
System.setProperty("java.awt.headless", "false");
if (GraphicsEnvironment.isHeadless())
if (SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class) != null)
{
LOGGER.warn("Java.awt.headless is false. This means Distant Horizons can't display error and info dialog windows.");
}
else
{
LOGGER.info("Java.awt.headless set to true. Distant Horizons can correctly display error and info dialog windows.");
// attempt to setup Swing so we can display dialogs (popup windows)
System.setProperty("java.awt.headless", "false");
if (GraphicsEnvironment.isHeadless())
{
LOGGER.warn("Java.awt.headless is false. This means Distant Horizons can't display error and info dialog windows.");
}
else
{
LOGGER.info("Java.awt.headless set to true. Distant Horizons can correctly display error and info dialog windows.");
}
}
// link Core's config to the API
@@ -103,4 +106,4 @@ public class Initializer
}
}
}
@@ -24,18 +24,20 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.world.*;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import com.seibel.distanthorizons.core.network.session.Session;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
import com.seibel.distanthorizons.core.logging.SpamReducedLogger;
@@ -43,21 +45,21 @@ import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.renderer.TestRenderer;
import com.seibel.distanthorizons.core.util.RenderUtil;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientServerWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld;
import com.seibel.distanthorizons.core.world.IDhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
//import io.netty.buffer.ByteBuf;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Queue;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -76,8 +78,6 @@ public class ClientApi
public static final TestRenderer TEST_RENDERER = new TestRenderer();
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
@@ -90,10 +90,11 @@ public class ClientApi
private long lastFlushNanoTime = 0;
private boolean isServerCommunicationEnabled = false;
private final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi(this::clientLevelLoadEvent, this::clientLevelUnloadEvent);
/** set to true if any unexpected responses are received from the server */
private boolean serverNetworkingIsMalformed = false;
// Delay loading the first level to give server some time to respond with level to actually load
private Timer firstLevelLoadTimer;
private static final long FIRST_LEVEL_LOAD_DELAY = 1000;
/** Holds any levels that were loaded before the {@link ClientApi#onClientOnlyConnected} was fired. */
public final HashSet<IClientLevelWrapper> waitingClientLevels = new HashSet<>();
@@ -152,8 +153,11 @@ public class ClientApi
// firing after clientLevelLoadEvent
// TODO if level has prepped to load it should fire level load event
SharedApi.setDhWorld(new DhClientWorld());
DhClientWorld world = new DhClientWorld();
SharedApi.setDhWorld(world);
this.pluginChannelApi.onJoin(world.networkState.getSession());
world.networkState.sendConfigMessage();
LOGGER.info("Loading [" + this.waitingClientLevels.size() + "] waiting client level wrappers.");
for (IClientLevelWrapper level : this.waitingClientLevels)
@@ -168,6 +172,12 @@ public class ClientApi
/** Synchronized to prevent a rare issue where multiple disconnect events are triggered on top of each other. */
public synchronized void onClientOnlyDisconnected()
{
if (this.firstLevelLoadTimer != null)
{
this.firstLevelLoadTimer.cancel();
this.firstLevelLoadTimer = null;
}
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
{
@@ -177,11 +187,7 @@ public class ClientApi
SharedApi.setDhWorld(null);
}
// clear the previous server's information
this.isServerCommunicationEnabled = false;
this.serverNetworkingIsMalformed = false;
KEYED_CLIENT_LEVEL_MANAGER.setUseOverrideWrapper(false);
KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(null);
this.pluginChannelApi.reset();
// remove any waiting items
this.waitingChunkByClientLevelAndPos.clear();
@@ -194,16 +200,21 @@ public class ClientApi
// level events //
//==============//
public void clientLevelUnloadEvent(@Nullable IClientLevelWrapper level)
public void clientLevelUnloadEvent(IClientLevelWrapper level)
{
this.clientLevelUnloadEvent(level, false);
}
public void clientLevelUnloadEvent(IClientLevelWrapper level, boolean respawn)
{
try
{
if (level == null)
LOGGER.info("Unloading client level [" + level + "]-["+level.getDimensionName()+"].");
if (level instanceof IServerKeyedClientLevel && !respawn)
{
// can happen on certain multiverse servers
return;
this.pluginChannelApi.onClientLevelUnload();
}
LOGGER.info("Unloading client level [" + level + "]-["+level.getDimensionType().getDimensionName()+"].");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
@@ -223,30 +234,42 @@ public class ClientApi
}
}
public void clientLevelLoadEvent(@Nullable IClientLevelWrapper level) { this.clientLevelLoadEvent(level, false); }
public void multiverseClientLevelLoadEvent(@Nullable IClientLevelWrapper level) { this.clientLevelLoadEvent(level, true); }
private void clientLevelLoadEvent(@Nullable IClientLevelWrapper level, boolean isServerCommunication)
public void clientLevelLoadEvent(IClientLevelWrapper level)
{
if (MC.clientConnectedToDedicatedServer())
{
if (this.firstLevelLoadTimer == null)
{
this.firstLevelLoadTimer = TimerUtil.CreateTimer("FirstLevelLoadTimer");
this.firstLevelLoadTimer.schedule(new TimerTask()
{
@Override
public void run()
{
ClientApi.this.clientLevelLoadEvent(level);
}
}, FIRST_LEVEL_LOAD_DELAY);
return;
}
this.firstLevelLoadTimer.cancel();
}
try
{
if (this.isServerCommunicationEnabled && !isServerCommunication)
{
LOGGER.info("Server supports communication, deferring loading.");
return;
}
if (level == null)
{
// can happen on certain multiverse servers
return;
}
LOGGER.info("Loading " + (isServerCommunication ? "Multiverse" : "") + " client level [" + level + "]-["+level.getDimensionType().getDimensionName()+"].");
LOGGER.info("Loading client level [" + level + "]-["+level.getDimensionName()+"].");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
{
if (!this.pluginChannelApi.allowLevelLoading(level))
{
LOGGER.info("Levels in this connection are managed by the server, skipping auto-load.");
// Instead of attempting to load themselves, send config and wait for level key.
((DhClientWorld) world).networkState.sendConfigMessage();
return;
}
world.getOrLoadLevel(level);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level));
@@ -263,6 +286,7 @@ public class ClientApi
LOGGER.error("Unexpected error in ClientApi.clientLevelLoadEvent(), error: "+e.getMessage(), e);
}
}
private void loadWaitingChunksForLevel(IClientLevelWrapper level)
{
HashSet<Pair<IClientLevelWrapper, DhChunkPos>> keysToRemove = new HashSet<>();
@@ -355,124 +379,15 @@ public class ClientApi
//============//
// networking //
//============//
// /** @param byteBuf is Netty's {@link ByteBuffer} wrapper. */
// public void serverMessageReceived(ByteBuf byteBuf)
// {
// if (!Config.Client.Advanced.Multiplayer.enableMultiverseNetworking.get())
// {
// // multiverse networking disabled, ignore anything sent from the server
// return;
// }
//
//
//
// // either value can be set to true to debug the received byte stream
// boolean stopAndDisplayInputAsByteArray = false;
// boolean stopAndDisplayInputAsString = false;
// if (stopAndDisplayInputAsByteArray || stopAndDisplayInputAsString)
// {
// String messageString = "";
// if (stopAndDisplayInputAsByteArray)
// {
// int byteCount = byteBuf.readableBytes();
// byte[] arr = new byte[byteCount];
// StringBuilder stringBuilder = new StringBuilder("Server message received: [");
// for (int i = 0; i < byteCount; i++)
// {
// arr[i] = byteBuf.readByte();
// stringBuilder.append(arr[i]);
// }
// stringBuilder.append("]");
//
// messageString = stringBuilder.toString();
// }
// else if (stopAndDisplayInputAsString)
// {
// messageString = byteBuf.toString(StandardCharsets.UTF_8);
// }
//
// // this is logged as an error so it is easier to see in an Intellij log
// LOGGER.error(messageString);
// return;
// }
//
//
//
//
// // It is important to ensure malicious server input is ignored.
// if (this.serverNetworkingIsMalformed)
// {
// return;
// }
//
// // check that the incoming message is within the expected size
// short commandLength = byteBuf.readShort();
// if (commandLength < 1 || commandLength > 32)
// {
// LOGGER.error("Server command length ["+commandLength+"] outside the expected range of 1 to 32 (inclusive).");
// ClientApi.INSTANCE.serverNetworkingIsMalformed = true;
// return;
// }
//
// // parse the command
// String eventType;
// try
// {
// eventType = byteBuf.readCharSequence(commandLength, StandardCharsets.UTF_8).toString();
// }
// catch (Exception e)
// {
// LOGGER.error("Server sent un-parsable command. Error: "+e.getMessage());
// return;
// }
//
// switch (eventType)
// {
// case "ServerCommsEnabled":
// LOGGER.info("Server supports DH multiverse protocol.");
// ClientApi.INSTANCE.isServerCommunicationEnabled = true;
// KEYED_CLIENT_LEVEL_MANAGER.setUseOverrideWrapper(true);
// MC.executeOnRenderThread(() ->
// {
// // Unload the current world, since it may be wrong.
// // A followup WorldChanged event should be received from the server soon after this.
// LOGGER.info("Unloading current client level so the server can define the correct multiverse level.");
// this.clientLevelUnloadEvent((IClientLevelWrapper) MC.getWrappedClientWorld());
// });
// break;
//
// case "LevelChanged":
// short levelKeyLength = byteBuf.readShort();
// if (levelKeyLength < 1 || levelKeyLength > 128) // TODO 128 should be put into a constant somewhere
// {
// LOGGER.error("Server [LevelChanged] command length ["+commandLength+"] outside the expected range of 1 to 128 (inclusive).");
// this.serverNetworkingIsMalformed = true;
// return;
// }
//
// String levelKey = byteBuf.readCharSequence(levelKeyLength, StandardCharsets.UTF_8).toString();
// if (!levelKey.matches("[a-zA-Z0-9_]+"))
// {
// LOGGER.error("Server sent invalid world key name, and is being ignored.");
// this.isServerCommunicationEnabled = false;
// this.serverNetworkingIsMalformed = true;
// return;
// }
//
// LOGGER.info("Server level change event received, changing the level to ["+levelKey+"].");
// MC.executeOnRenderThread(() -> {
// if (MC.getWrappedClientWorld() != null)
// {
// this.clientLevelUnloadEvent((IClientLevelWrapper) MC.getWrappedClientWorld());
// }
// IServerKeyedClientLevel clientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(MC.getWrappedClientWorld(), levelKey);
// KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel);
// this.multiverseClientLevelLoadEvent(clientLevel);
// });
// break;
// }
// }
public void pluginMessageReceived(@NotNull NetworkMessage message)
{
Session session = this.pluginChannelApi.session;
if (session != null)
{
session.tryHandleMessage(message);
}
}
@@ -550,10 +465,24 @@ public class ClientApi
{
return;
}
IDhClientLevel level = dhClientWorld.getOrLoadClientLevel(levelWrapper);
IDhClientLevel level = (IDhClientLevel) dhClientWorld.getLevel(levelWrapper);
if (level == null)
{
return;
}
if (this.rendererDisabledBecauseOfExceptions)
{
// re-enable rendering if the user toggles DH rendering
if (!Config.Client.quickEnableRendering.get())
{
LOGGER.info("DH Renderer re-enabled after exception. Some rendering issues may occur. Please reboot Minecraft if you see any rendering issues.");
this.rendererDisabledBecauseOfExceptions = false;
Config.Client.quickEnableRendering.set(true);
}
return;
}
@@ -604,9 +533,9 @@ public class ClientApi
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons renderer has encountered an exception!");
MC.sendChatMessage("\u00A74Renderer is now disabled to prevent further issues.");
MC.sendChatMessage("\u00A74Please restart your game to re-enable Distant Horizons' LOD rendering.");
MC.sendChatMessage("\u00A74Exception detail: " + e);
MC.sendChatMessage("\u00A74Renderer disabled to try preventing GL state corruption.");
MC.sendChatMessage("\u00A74Toggle DH rendering via the config UI to re-activate DH rendering.");
MC.sendChatMessage("\u00A74Error: " + e);
}
finally
{
@@ -717,4 +646,4 @@ public class ClientApi
*/
public void showChatMessageNextFrame(String chatMessage) { this.chatMessageQueueForNextFrame.add(chatMessage); }
}
}
@@ -0,0 +1,116 @@
package com.seibel.distanthorizons.core.api.internal;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.network.event.internal.CloseEvent;
import com.seibel.distanthorizons.core.network.messages.base.CurrentLevelKeyMessage;
import com.seibel.distanthorizons.core.network.session.Session;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.apache.logging.log4j.LogManager;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.function.Consumer;
/**
* This class is used to manage the level keys.
*/
public class ClientPluginChannelApi
{
private static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
() -> Config.Client.Advanced.Logging.logNetworkEvent.get());
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
private final Consumer<IServerKeyedClientLevel> levelLoadHandler;
private final Consumer<IClientLevelWrapper> levelUnloadHandler;
@Nullable
public Session session;
public boolean allowLevelLoading(IClientLevelWrapper level)
{
return (KEYED_CLIENT_LEVEL_MANAGER.isEnabled() && level instanceof IServerKeyedClientLevel)
|| !KEYED_CLIENT_LEVEL_MANAGER.isEnabled();
}
public ClientPluginChannelApi(Consumer<IServerKeyedClientLevel> levelLoadHandler, Consumer<IClientLevelWrapper> levelUnloadHandler)
{
this.levelLoadHandler = levelLoadHandler;
this.levelUnloadHandler = levelUnloadHandler;
}
public void onJoin(@NonNull Session session)
{
Objects.requireNonNull(session);
this.session = session;
session.registerHandler(CurrentLevelKeyMessage.class, this::onCurrentLevelKeyMessage);
session.registerHandler(CloseEvent.class, this::onClose);
}
private void onCurrentLevelKeyMessage(CurrentLevelKeyMessage msg)
{
// prefix@namespace:path
// 1-50 characters in total, all parts except namespace can be omitted
if (!msg.levelKey.matches("^(?=.{1,50}$)([a-zA-Z0-9-_]+@)?[a-zA-Z0-9-_]+(:[a-zA-Z0-9-_]+)?$"))
{
throw new IllegalArgumentException("Server sent invalid level key.");
}
LOGGER.info("Server level key received: " + msg.levelKey);
MC.executeOnRenderThread(() -> {
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
if (existingKeyedClientLevel != null)
{
if (!existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
{
LOGGER.info("Unloading previous level with key: " + existingKeyedClientLevel.getServerLevelKey());
this.levelUnloadHandler.accept(existingKeyedClientLevel);
}
else
{
LOGGER.info("Level key matches the previous level key, ignoring the message.");
}
}
else
{
LOGGER.info("Unloading non-keyed level: " + clientLevel.getDimensionName());
this.levelUnloadHandler.accept(clientLevel);
}
if (existingKeyedClientLevel == null || !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
{
LOGGER.info("Loading level with key: " + msg.levelKey);
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.levelKey);
this.levelLoadHandler.accept(keyedLevel);
}
});
}
public void onClientLevelUnload()
{
KEYED_CLIENT_LEVEL_MANAGER.clearServerKeyedLevel();
}
private void onClose(CloseEvent event)
{
this.reset();
}
public void reset()
{
this.session = null;
KEYED_CLIENT_LEVEL_MANAGER.disable();
}
}
@@ -21,13 +21,9 @@ package com.seibel.distanthorizons.core.api.internal;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientServerWorld;
import com.seibel.distanthorizons.core.world.DhServerWorld;
@@ -37,6 +33,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
/**
* This holds the methods that should be called by the host mod loader (Fabric,
@@ -154,7 +151,7 @@ public class ServerApi
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well
{
LOGGER.debug("Waiting for player to connect: " + player.getUUID());
LOGGER.info("Creating state for player: " + player.getName());
((DhServerWorld) serverWorld).addPlayer(player);
}
}
@@ -163,9 +160,27 @@ public class ServerApi
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well
{
LOGGER.debug("Removing player from connect wait list: " + player.getUUID());
LOGGER.info("Destroying state for player: " + player.getName());
((DhServerWorld) serverWorld).removePlayer(player);
}
}
public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper origin, IServerLevelWrapper dest)
{
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well
{
LOGGER.info("Player changed level: " + player.getName());
((DhServerWorld) serverWorld).changePlayerLevel(player, origin, dest);
}
}
}
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull NetworkMessage message)
{
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well
{
((DhServerWorld) serverWorld).remotePlayerConnectionHandler.handlePluginMessage(player, message);
}
}
}
@@ -24,13 +24,11 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.IDhServerLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.TimerUtil;
@@ -45,10 +43,7 @@ import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.*;
/** Contains code and variables used by both {@link ClientApi} and {@link ServerApi} */
public class SharedApi
@@ -65,6 +60,7 @@ public class SharedApi
private static final Timer CHUNK_UPDATE_TIMER = TimerUtil.CreateTimer("ChunkUpdateTimer");
private static AbstractDhWorld currentWorld;
private static int lastWorldGenTickDelta = 0;
private static long lastOverloadedLogMessageMsTime = 0;
@@ -88,6 +84,7 @@ public class SharedApi
public static void setDhWorld(AbstractDhWorld newWorld)
{
AbstractDhWorld prevWorld = currentWorld;
currentWorld = newWorld;
// starting and stopping the DataRenderTransformer is necessary to prevent attempting to
@@ -99,12 +96,16 @@ public class SharedApi
else
{
ThreadPoolUtil.shutdownThreadPools();
DebugRenderer.clearRenderables();
MC_RENDER.clearTargetFrameBuffer();
// shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed
if (prevWorld != null && prevWorld.environment != EWorldEnvironment.Server_Only)
{
DebugRenderer.clearRenderables();
MC_RENDER.clearTargetFrameBuffer();
// shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed
AbstractDhRepo.closeAllConnections();
// needs to be closed on world shutdown to clear out un-processed chunks
UPDATING_CHUNK_POS_SET.clear();
UPDATING_CHUNK_POS_SET.clear();
}
// recommend that the garbage collector cleans up any objects from the old world and thread pools
System.gc();
@@ -258,7 +259,7 @@ public class SharedApi
else
{
// neighboring chunk
DhChunkPos neighbourPos = new DhChunkPos(chunkWrapper.getChunkPos().x + xOffset, chunkWrapper.getChunkPos().z + zOffset);
DhChunkPos neighbourPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
IChunkWrapper neighbourChunk = dhLevel.getLevelWrapper().tryGetChunk(neighbourPos);
if (neighbourChunk != null)
{
@@ -275,36 +276,43 @@ public class SharedApi
}
}
}
private static void bakeChunkLightingAndSendToLevelAsync(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighbourChunkList, IDhLevel dhLevel)
/** returning a {@link CompletableFuture} isn't necessary, but allows Intellij to properly show the full stack trace when debugging. */
@SuppressWarnings("UnusedReturnValue")
private static CompletableFuture<Void> bakeChunkLightingAndSendToLevelAsync(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighbourChunkList, IDhLevel dhLevel)
{
// lighting the chunk needs to be done on a separate thread to prevent lagging any of the event threads
ThreadPoolExecutor executor = ThreadPoolUtil.getLightPopulatorExecutor();
ThreadPoolExecutor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor == null)
{
return;
return CompletableFuture.completedFuture(null);
}
try
{
executor.execute(() ->
return CompletableFuture.runAsync(() ->
{
//LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
try
{
boolean checkChunkHash = !Config.Client.Advanced.LodBuilding.disableUnchangedChunkCheck.get();
// check if this chunk has been converted into an LOD already
int oldChunkHash = dhLevel.getChunkHash(chunkWrapper.getChunkPos()); // shouldn't happen on the render thread since it may take a few moments to run
int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
if (oldChunkHash == newChunkHash)
if (checkChunkHash)
{
// if the chunk hashes are the same then we don't need to bother with lighting the chunk
// or creating/updating the LODs
//LOGGER.info("skipping: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
return;
}
else
{
//LOGGER.info("g: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
if (oldChunkHash == newChunkHash)
{
// if the chunk hashes are the same then we don't need to bother with lighting the chunk
// or creating/updating the LODs
//LOGGER.info("skipping: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
return;
}
else
{
//LOGGER.info("g: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
}
}
@@ -357,13 +365,7 @@ public class SharedApi
// get this chunk's active beacons
List<BeaconBeamDTO> beaconBeamList = chunkWrapper.getAllActiveBeacons(nearbyChunkList);
dhLevel.setBeaconBeamsForChunk(chunkWrapper.getChunkPos(), beaconBeamList);
dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList);
dhLevel.updateChunkAsync(chunkWrapper, newChunkHash);
}
catch (Exception e)
@@ -389,9 +391,13 @@ public class SharedApi
UPDATING_CHUNK_POS_SET.remove(chunkWrapper.getChunkPos());
}
}
});
}, executor);
}
catch (RejectedExecutionException ignore)
{
// the executor was shut down, it should be back up shortly and able to accept new jobs
return CompletableFuture.completedFuture(null);
}
catch (RejectedExecutionException ignore) { /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
}
@@ -409,4 +415,4 @@ public class SharedApi
}
}
}
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.config;
import com.google.common.base.Suppliers;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.*;
import com.seibel.distanthorizons.api.enums.config.quickOptions.*;
@@ -43,6 +44,7 @@ import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
@@ -106,6 +108,8 @@ public class Config
public static ConfigLinkedEntry quickEnableWorldGenerator = new ConfigLinkedEntry(Advanced.WorldGenerator.enableDistantGeneration);
public static ConfigLinkedEntry quickLodCloudRendering = new ConfigLinkedEntry(Advanced.Graphics.GenericRendering.enableCloudRendering);
public static ConfigEntry<Boolean> optionsButton = new ConfigEntry.Builder<Boolean>()
.set(true)
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
@@ -161,8 +165,17 @@ public class Config
.build();
public static ConfigEntry<Integer> lodChunkRenderDistanceRadius = new ConfigEntry.Builder<Integer>()
.setServersideShortName("renderDistanceRadius")
.setMinDefaultMax(32, 128, 4096)
.comment("The radius of the mod's render distance. (measured in chunks)")
.comment("" +
"The radius of the mod's render distance. (measured in chunks)\n" +
"On server changes the distance players will receive real-time updates for, if enabled." +
"\n" +
"Note for servers:\n" +
"This setting does not prevent players from generating farther out.\n" +
"If you want to limit performance impact, change rate limits\n" +
"and thread count/runtime ratio settings instead.\n" +
"It also does not affect the visuals on clients.")
.setPerformance(EConfigEntryPerformance.HIGH)
.build();
@@ -699,6 +712,17 @@ public class Config
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Boolean> disableBeaconDistanceCulling = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "If true all beacons near the camera won't be drawn to prevent vanilla overdraw. \n"
+ "If false all beacons will be rendered. \n"
+ "\n"
+ "Generally this should be left as false. It's main purpose is for debugging\n"
+ "beacon updating/rendering.\n"
+ "")
.build();
}
}
@@ -706,16 +730,15 @@ public class Config
public static class WorldGenerator
{
public static ConfigEntry<Boolean> enableDistantGeneration = new ConfigEntry.Builder<Boolean>()
.setServersideShortName("enableDistantGeneration")
.set(true)
.comment(""
+ " Should Distant Horizons slowly generate LODs \n"
+ " outside the vanilla render distance?\n"
+ "\n"
+ " Note: when on a server, distant generation isn't supported \n"
+ " and will always be disabled.")
+ " outside the vanilla render distance?")
.build();
public static ConfigEntry<EDhApiDistantGeneratorMode> distantGeneratorMode = new ConfigEntry.Builder<EDhApiDistantGeneratorMode>()
.setServersideShortName("distantGeneratorMode")
.set(EDhApiDistantGeneratorMode.FEATURES)
.comment(""
+ "How detailed should LODs be generated outside the vanilla render distance? \n"
@@ -757,6 +780,7 @@ public class Config
.build();
public static ConfigEntry<Integer> worldGenerationTimeoutLengthInSeconds = new ConfigEntry.Builder<Integer>()
.setServersideShortName("worldGenerationTimeout")
.setMinDefaultMax(5, 60 * 3, 60 * 10/*10 minutes*/ )
.comment(""
+ "How long should a world generator thread run for before timing out? \n"
@@ -770,6 +794,7 @@ public class Config
public static class LodBuilding
{
public static ConfigEntry<Integer> minTimeBetweenChunkUpdatesInSeconds = new ConfigEntry.Builder<Integer>()
.setServersideShortName("minTimeBetweenChunkUpdates")
.setMinDefaultMax(0, 1, 60)
.comment(""
+ "Determines how long must pass between LOD chunk updates before another. \n"
@@ -780,6 +805,19 @@ public class Config
+ "")
.build();
public static ConfigEntry<Boolean> disableUnchangedChunkCheck = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "Normally DH will attempt to skip creating LODs for chunks it's already seen\n"
+ "and that haven't changed.\n"
+ "\n"
+ "However sometimes that logic incorrecly prevents LODs from being updated.\n"
+ "Disabling this check may fix issues where LODs aren't updated after\n"
+ "blocks have been changed.\n"
+ "")
.build();
/** Currently we always use the DH lighting engine because there's a high likelyhood of MC returning incorrect lighting otherwise */
@Deprecated
public static ConfigEntry<Boolean> onlyUseDhLightingEngine = new ConfigEntry.Builder<Boolean>()
.set(false)
@@ -818,7 +856,7 @@ public class Config
+ "unaffected until it needs to be re-written to the database.\n"
+ "\n"
+ EDhApiDataCompressionMode.UNCOMPRESSED + " \n"
+ "Should only be used for testing, is worse in every way vs ["+EDhApiDataCompressionMode.LZ4+"].\n"
+ "Should only be used for testing, is worse in every way vs [" + EDhApiDataCompressionMode.LZ4 + "].\n"
+ "Expected Compression Ratio: 1.0\n"
+ "Estimated average DTO read speed: 1.64 milliseconds\n"
+ "Estimated average DTO write speed: 12.44 milliseconds\n"
@@ -846,13 +884,13 @@ public class Config
+ "unaffected until it is modified or re-loaded.\n"
+ "\n"
+ EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS + " \n"
+ "Every block/biome change is recorded in the database. \n"
+ "This is what DH 2.0 and 2.0.1 all used by default and will store a lot of data. \n"
+ "Every block/biome change is recorded in the database. \n"
+ "This is what DH 2.0 and 2.0.1 all used by default and will store a lot of data. \n"
+ "Expected Compression Ratio: 1.0\n"
+ "\n"
+ EDhApiWorldCompressionMode.VISUALLY_EQUAL + " \n"
+ "Only visible block/biome changes are recorded in the database. \n"
+ "Hidden blocks (IE ores) are ignored. \n"
+ "Only visible block/biome changes are recorded in the database. \n"
+ "Hidden blocks (IE ores) are ignored. \n"
+ "Expected Compression Ratio: 0.7\n"
+ "")
.build();
@@ -940,7 +978,7 @@ public class Config
.build();
// not currently implemented
private static ConfigEntry<Boolean> enableMultiverseNetworking = new ConfigEntry.Builder<Boolean>()
public static ConfigEntry<Boolean> enableMultiverseNetworking = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If true Distant Horizons will attempt to communicate with the connected \n"
@@ -948,59 +986,137 @@ public class Config
+ "")
.build();
// not currently implemented
private static ConfigEntry<Boolean> enableServerNetworking = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "Attention: this is only for developers and hasn't been implemented.\n"
+ "\n"
+ "If true Distant Horizons will attempt to communicate with the connected \n"
+ "server in order to load LODs outside your vanilla render distance. \n"
+ "\n"
+ "Note: This requires DH to be installed on the server in order to function. \n"
+ "")
.build();
public static ConfigCategory serverNetworking = new ConfigCategory.Builder().set(ServerNetworking.class).build();
public static class ServerNetworking
{
public static ConfigUIComment generalSectionNote = new ConfigUIComment();
public static ConfigEntry<Boolean> enableServerNetworking = new ConfigEntry.Builder<Boolean>()
.setServersideShortName("enableServerNetworking")
.set(true)
.comment(""
+ "WARNING!\n"
+ "Server-client networking is not yet fully implemented!\n"
+ "Both the server and client must be running the server-side fork with this option enabled\n"
+ "for Distant Horizons data to be transceived.\n"
+ "\n"
+ "If true, the server and client will attempt to communicate to transceive Distant Horizons data.\n"
+ "This allows for further distant generation and LOD updates on all clients.\n"
+ "\n"
+ "This should only be used on trusted servers with trusted players!\n"
+ "")
.build();
public static ConfigEntry<Boolean> sendLevelKeys = new ConfigEntry.Builder<Boolean>()
.setServersideShortName("sendLevelKeys")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.set(true)
.comment(""
+ "Makes the server send level keys for each world.\n"
+ "Disable this if you use alternative ways to send level keys.\n"
+ "")
.build();
public static ConfigEntry<String> levelKeyPrefix = new ConfigEntry.Builder<String>()
.setServersideShortName("levelKeyPrefix")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.set(
Suppliers.compose(wrapper -> !wrapper.isDedicatedServer() || wrapper.isWorldInitialized(), () -> SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class)).get()
? ""
: "server" + ThreadLocalRandom.current().nextInt(1, 1000)
)
.comment(""
+ "Prefix of the level keys sent to the clients.\n"
+ "Should be set to a unique value for each backend server behind a proxy,\n"
+ "or empty if you don't use a proxy.\n"
+ "")
.build();
public static ConfigUIComment generationSectionNote = new ConfigUIComment();
public static ConfigEntry<Integer> generationRequestRateLimit = new ConfigEntry.Builder<Integer>()
.setServersideShortName("generationRequestRateLimit")
.setMinDefaultMax(1, 20, 100)
.comment(""
+ "How many LOD generation requests per second should a client send? \n"
+ "Also limits the amount of player's requests allowed to stay in the server's queue."
+ "")
.build();
public static ConfigUIComment realTimeUpdatesSectionNote = new ConfigUIComment();
public static ConfigEntry<Boolean> enableRealTimeUpdates = new ConfigEntry.Builder<Boolean>()
.setServersideShortName("enableRealTimeUpdates")
.set(false)
.comment(""
+ "If true, the client will receive real-time LOD updates for chunks outside the client's render distance."
+ "")
.build();
public static ConfigUIComment syncOnLoginSectionNote = new ConfigUIComment();
public static ConfigEntry<Boolean> synchronizeOnLogin = new ConfigEntry.Builder<Boolean>()
.setServersideShortName("synchronizeOnLogin")
.set(false)
.comment(""
+ "If true, clients will receive updated LODs on join if any changes occurred since last join."
+ "")
.build();
public static ConfigEntry<Integer> syncOnLoginRateLimit = new ConfigEntry.Builder<Integer>()
.setServersideShortName("syncOnLoginRateLimit")
.setMinDefaultMax(1, 50, 100)
.comment(""
+ "How many LOD sync requests per second should a client send? \n"
+ "Also limits the amount of player's requests allowed to stay in the server's queue."
+ "")
.build();
}
}
public static class MultiThreading
{
public static final String THREAD_NOTE = ""
+ "Multi-threading Note: \n"
+ "If the total thread count in Distant Horizon's config is more threads than your CPU has cores, \n"
+ "CPU performance may suffer if Distant Horizons has a lot to load or generate. \n"
+ "Multi-threading Note:\n"
+ "If the total thread count in Distant Horizon's config is more threads than your CPU has cores,\n"
+ "CPU performance may suffer if Distant Horizons has a lot to load or generate.\n"
+ "This can be an issue when first loading into a world, when flying, and/or when generating new terrain.";
public static final String THREAD_RUN_TIME_RATIO_NOTE = ""
+ "If this value is less than 1.0, it will be treated as a percentage \n"
+ "of time each thread can run before going idle. \n"
+ "If this value is less than 1.0, it will be treated as a percentage\n"
+ "of time each thread can run before going idle.\n"
+ "\n"
+ "This can be used to reduce CPU usage if the thread count \n"
+ "is already set to 1 for the given option, or more finely \n"
+ "This can be used to reduce CPU usage if the thread count\n"
+ "is already set to 1 for the given option, or more finely\n"
+ "tune CPU performance.";
public static final ConfigEntry<Integer> numberOfWorldGenerationThreads = new ConfigEntry.Builder<Integer>()
.setServersideShortName("numberOfWorldGenerationThreads")
.setMinDefaultMax(1,
ThreadPresetConfigEventHandler.getWorldGenDefaultThreadCount(),
Runtime.getRuntime().availableProcessors())
.comment(""
+ "How many threads should be used when generating LOD \n"
+ "chunks outside the normal render distance? \n"
+ "How many threads should be used when generating LOD\n"
+ "chunks outside the normal render distance?\n"
+ "\n"
+ "If you experience stuttering when generating distant LODs, \n"
+ "decrease this number. \n"
+ "If you want to increase LOD \n"
+ "generation speed, increase this number. \n"
+ "If you experience stuttering when generating distant LODs,\n"
+ "decrease this number.\n"
+ "If you want to increase LOD\n"
+ "generation speed, increase this number.\n"
+ "\n"
+ THREAD_NOTE)
.build();
public static final ConfigEntry<Double> runTimeRatioForWorldGenerationThreads = new ConfigEntry.Builder<Double>()
.setServersideShortName("runTimeRatioForWorldGenerationThreads")
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getWorldGenDefaultRunTimeRatio(), 1.0)
.comment(THREAD_RUN_TIME_RATIO_NOTE)
.build();
public static final ConfigEntry<Integer> numberOfFileHandlerThreads = new ConfigEntry.Builder<Integer>()
.setServersideShortName("numberOfFileHandlerThreads")
.setMinDefaultMax(1,
ThreadPresetConfigEventHandler.getFileHandlerDefaultThreadCount(),
Runtime.getRuntime().availableProcessors())
@@ -1014,6 +1130,7 @@ public class Config
+ THREAD_NOTE)
.build();
public static final ConfigEntry<Double> runTimeRatioForFileHandlerThreads = new ConfigEntry.Builder<Double>()
.setServersideShortName("runTimeRatioForFileHandlerThreads")
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getFileHandlerDefaultRunTimeRatio(), 1.0)
.comment(THREAD_RUN_TIME_RATIO_NOTE)
.build();
@@ -1044,6 +1161,7 @@ public class Config
.build();
public static final ConfigEntry<Integer> numberOfLodBuilderThreads = new ConfigEntry.Builder<Integer>()
.setServersideShortName("numberOfLodBuilderThreads")
.setMinDefaultMax(1,
ThreadPresetConfigEventHandler.getLodBuilderDefaultThreadCount(),
Runtime.getRuntime().availableProcessors())
@@ -1056,10 +1174,12 @@ public class Config
+ THREAD_NOTE)
.build();
public static final ConfigEntry<Double> runTimeRatioForLodBuilderThreads = new ConfigEntry.Builder<Double>()
.setServersideShortName("runTimeRatioForLodBuilderThreads")
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getLodBuilderDefaultRunTimeRatio(), 1.0)
.comment(THREAD_RUN_TIME_RATIO_NOTE)
.build();
public static final ConfigEntry<Boolean> enableLodBuilderThreadLimiting = new ConfigEntry.Builder<Boolean>()
.setServersideShortName("enableLodBuilderThreadLimiting")
.set(true)
.comment(""
+ "Should only be disabled if deadlock occurs and LODs refuse to update. \n"
@@ -1069,6 +1189,24 @@ public class Config
+ "")
.build();
public static final ConfigEntry<Integer> numberOfNetworkCompressionThreads = new ConfigEntry.Builder<Integer>()
.setServersideShortName("numberOfNetworkCompressionThreads")
.setMinDefaultMax(1,
ThreadPresetConfigEventHandler.getNetworkCompressionDefaultThreadCount(),
Runtime.getRuntime().availableProcessors())
.comment(""
+ "How many threads should be used when building LODs? \n"
+ "\n"
+ "These threads run when terrain is generated, when\n"
+ "certain graphics settings are changed, and when moving around the world. \n"
+ "\n"
+ THREAD_NOTE)
.build();
public static final ConfigEntry<Double> runTimeRatioForNetworkCompressionThreads = new ConfigEntry.Builder<Double>()
.setServersideShortName("runTimeRatioForNetworkCompressionThreads")
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getNetworkCompressionDefaultRunTimeRatio(), 1.0)
.comment(THREAD_RUN_TIME_RATIO_NOTE)
.build();
}
public static class AutoUpdater
@@ -1102,6 +1240,7 @@ public class Config
// TODO add change all option
// TODO default to error chat and info file
public static ConfigEntry<EDhApiLoggerMode> logWorldGenEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logWorldGenEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the world generation process. \n"
@@ -1109,6 +1248,7 @@ public class Config
.build();
public static ConfigEntry<EDhApiLoggerMode> logWorldGenPerformance = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logWorldGenPerformance")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log performance about the world generation process. \n"
@@ -1116,6 +1256,7 @@ public class Config
.build();
public static ConfigEntry<EDhApiLoggerMode> logWorldGenLoadEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logWorldGenPerformance")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the world generation process. \n"
@@ -1123,6 +1264,7 @@ public class Config
.build();
public static ConfigEntry<EDhApiLoggerMode> logLodBuilderEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logLodBuilderEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the LOD generation process. \n"
@@ -1144,6 +1286,7 @@ public class Config
.build();
public static ConfigEntry<EDhApiLoggerMode> logFileReadWriteEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logFileReadWriteEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about file read/write operations. \n"
@@ -1151,6 +1294,7 @@ public class Config
.build();
public static ConfigEntry<EDhApiLoggerMode> logFileSubDimEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logFileSubDimEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about file sub-dimension operations. \n"
@@ -1158,6 +1302,7 @@ public class Config
.build();
public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logNetworkEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about network operations. \n"
@@ -1409,8 +1554,8 @@ public class Config
"Can be changed if you experience crashing when loading into a world.\n" +
"\n" +
"Defines the OpenGL context type Distant Horizon's will create. \n" +
"Generally this should be left as ["+ EDhApiGlProfileMode.CORE+"] unless there is an issue with your GPU driver. \n" +
"Possible values: ["+ StringUtil.join("],[", EDhApiGlProfileMode.values())+"] \n" +
"Generally this should be left as [" + EDhApiGlProfileMode.CORE + "] unless there is an issue with your GPU driver. \n" +
"Possible values: [" + StringUtil.join("],[", EDhApiGlProfileMode.values()) + "] \n" +
"")
.build();
public static ConfigEntry<Boolean> enableGlForwardCompatibilityMode = new ConfigEntry.Builder<Boolean>()
@@ -1489,9 +1634,9 @@ public class Config
.set(new HashMap<String, String>())
.build();
public static ConfigUIButton uiButtonTest = new ConfigUIButton(() ->
public static ConfigUIButton uiButtonTest = new ConfigUIButton(() ->
{
new Thread(() ->
new Thread(() ->
{
if (!GraphicsEnvironment.isHeadless())
{
@@ -1573,4 +1718,4 @@ public class Config
}
}
}
}
@@ -57,7 +57,9 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
public AbstractPresetConfigEventHandler()
{
configGui.addOnScreenChangeListener(() -> this.onConfigUiClosed());
if (configGui != null) {
configGui.addOnScreenChangeListener(this::onConfigUiClosed);
}
}
@@ -32,6 +32,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@SuppressWarnings("FieldCanBeLocal")
public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHandler<EDhApiThreadPreset>
{
public static final ThreadPresetConfigEventHandler INSTANCE = new ThreadPresetConfigEventHandler();
@@ -128,6 +129,28 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
}});
public static int getNetworkCompressionDefaultThreadCount() { return getThreadCountByPercent(0.3); }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> networkCompressionThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfNetworkCompressionThreads,
new HashMap<EDhApiThreadPreset, Integer>()
{{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1);
this.put(EDhApiThreadPreset.LOW_IMPACT, getNetworkCompressionDefaultThreadCount());
this.put(EDhApiThreadPreset.BALANCED, getThreadCountByPercent(0.4));
this.put(EDhApiThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.6));
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(0.8));
}});
public static double getNetworkCompressionDefaultRunTimeRatio() { return 0.5; }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> networkCompressionRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForNetworkCompressionThreads,
new HashMap<EDhApiThreadPreset, Double>()
{{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.25);
this.put(EDhApiThreadPreset.LOW_IMPACT, getNetworkCompressionDefaultRunTimeRatio());
this.put(EDhApiThreadPreset.BALANCED, 0.75);
this.put(EDhApiThreadPreset.AGGRESSIVE, 1.0);
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
}});
//==============//
// constructors //
@@ -149,6 +172,9 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
this.configList.add(this.lodBuilderThreadCount);
this.configList.add(this.lodBuilderRunTime);
this.configList.add(this.networkCompressionThreadCount);
this.configList.add(this.networkCompressionRunTime);
for (ConfigEntryWithPresetOptions<EDhApiThreadPreset, ?> config : this.configList)
{
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.config.ConfigBase;
import com.seibel.distanthorizons.core.config.types.AbstractConfigType;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -191,6 +192,8 @@ public class ConfigFileHandling
public void saveEntry(ConfigEntry<?> entry, CommentedFileConfig workConfig)
{
if (!entry.getAppearance().showInFile) return;
if (SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class).isDedicatedServer() && entry.getServersideShortName() == null)
return;
if (entry.getTrueValue() == null)
throw new IllegalArgumentException("Entry [" + entry.getNameWCategory() + "] is null, this may be a problem with [" + configBase.modName + "]. Please contact the authors");
@@ -265,6 +268,9 @@ public class ConfigFileHandling
)
return;
if (SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class).isDedicatedServer() && entry.getServersideShortName() == null)
return;
String comment = entry.getComment().replaceAll("\n", "\n ").trim();
// the new line makes it easier to read and separate configs
// the space makes sure the first word of a comment isn't directly in line with the "#"
@@ -391,4 +397,4 @@ public class ConfigFileHandling
}
}
*/
}
}
@@ -44,6 +44,7 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
private T min;
private T max;
private final ArrayList<IConfigListener> listenerList;
private final String serversideShortName;
// API control //
/**
@@ -57,13 +58,14 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
/** Creates the entry */
private ConfigEntry(EConfigEntryAppearance appearance, T value, String comment, T min, T max, boolean allowApiOverride, EConfigEntryPerformance performance, ArrayList<IConfigListener> listenerList)
private ConfigEntry(EConfigEntryAppearance appearance, T value, String comment, T min, T max, String serversideShortName, boolean allowApiOverride, EConfigEntryPerformance performance, ArrayList<IConfigListener> listenerList)
{
super(appearance, value);
this.comment = comment;
this.min = min;
this.max = max;
this.serversideShortName = serversideShortName;
this.allowApiOverride = allowApiOverride;
this.performance = performance;
this.listenerList = listenerList;
@@ -179,6 +181,8 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
if (validness == 1) this.value = (T) NumberUtil.getMaximum(this.value.getClass());
}
public String getServersideShortName() { return this.serversideShortName; }
@Override
public String getComment() { return this.comment; }
@Override
@@ -315,6 +319,7 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
private String tmpComment = null;
private T tmpMin = null;
private T tmpMax = null;
protected String tmpServersideShortName = null;
private boolean tmpUseApiOverwrite = true;
private EConfigEntryPerformance tmpPerformance = EConfigEntryPerformance.DONT_SHOW;
protected ArrayList<IConfigListener> tmpIConfigListener = new ArrayList<>();
@@ -352,6 +357,12 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
return this;
}
public Builder<T> setServersideShortName(String name)
{
this.tmpServersideShortName = name;
return this;
}
public Builder<T> setUseApiOverwrite(boolean newUseApiOverwrite)
{
this.tmpUseApiOverwrite = newUseApiOverwrite;
@@ -394,7 +405,7 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
public ConfigEntry<T> build()
{
return new ConfigEntry<>(this.tmpAppearance, this.tmpValue, this.tmpComment, this.tmpMin, this.tmpMax, this.tmpUseApiOverwrite, this.tmpPerformance, this.tmpIConfigListener);
return new ConfigEntry<>(this.tmpAppearance, this.tmpValue, this.tmpComment, this.tmpMin, this.tmpMax, this.tmpServersideShortName, this.tmpUseApiOverwrite, this.tmpPerformance, this.tmpIConfigListener);
}
}
@@ -44,7 +44,7 @@ import java.util.Arrays;
/**
* This data source contains every datapoint over its given {@link DhSectionPos}. <br><br>
*
*
* @see FullDataPointUtil
* @see FullDataSourceV1
*/
@@ -53,10 +53,10 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/** useful for debugging, but can slow down update operations quite a bit due to being called so often. */
private static final boolean RUN_UPDATE_DEV_VALIDATION = false;
/**
/**
* If the data column order isn't correct
* block lighting may appear broken
* and/or certain detail level LODs may not appear at all.
* block lighting may appear broken
* and/or certain detail level LODs may not appear at all.
*/
private static final boolean RUN_DATA_ORDER_VALIDATION = ModInfo.IS_DEV_BUILD;
@@ -88,18 +88,20 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
public int levelMinY;
/**
/**
* stores how far each column has been generated should start with {@link EDhApiWorldGenerationStep#EMPTY}
* @see EDhApiWorldGenerationStep
*
* @see EDhApiWorldGenerationStep
*/
public byte[] columnGenerationSteps;
/**
/**
* stores what world compression was used for each column.
* @see EDhApiWorldCompressionMode
*
* @see EDhApiWorldCompressionMode
*/
public byte[] columnWorldCompressionMode;
/**
/**
* stored x/z, y <br>
* The y data should be sorted from top to bottom <br>
* TODO that ordering feels weird, it'd be nice to reverse that order, unfortunately
@@ -151,9 +153,9 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
if (FullDataSourceV1.WIDTH != WIDTH)
{
throw new UnsupportedOperationException(
"Unable to convert ["+FullDataSourceV1.class.getSimpleName()+"] into ["+FullDataSourceV2.class.getSimpleName()+"]. " +
"Data sources have different data point widths and no converter is present. " +
"input width ["+ FullDataSourceV1.WIDTH+"], recipient width ["+WIDTH+"].");
"Unable to convert [" + FullDataSourceV1.class.getSimpleName() + "] into [" + FullDataSourceV2.class.getSimpleName() + "]. " +
"Data sources have different data point widths and no converter is present. " +
"input width [" + FullDataSourceV1.WIDTH + "], recipient width [" + WIDTH + "].");
}
@@ -180,7 +182,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
boolean isAir = legacyData.mapping.getBlockStateWrapper(FullDataPointUtil.getId(dataPoint)).isAir();
byte blockLight = (byte) FullDataPointUtil.getBlockLight(dataPoint);
if (isAir)
{
// air shouldn't have any light, otherwise down sampling will look weird
@@ -256,7 +258,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
// and would lead to edge cases that don't necessarily need to be supported
// (IE what do you do when the input is smaller than a single datapoint in the receiving data source?)
// instead it's better to just percolate the updates up
throw new UnsupportedOperationException("Unsupported data source update. Expected input detail level of ["+thisDetailLevel+"] or ["+(thisDetailLevel+1)+"], received detail level ["+inputDetailLevel+"].");
throw new UnsupportedOperationException("Unsupported data source update. Expected input detail level of [" + thisDetailLevel + "] or [" + (thisDetailLevel + 1) + "], received detail level [" + inputDetailLevel + "].");
}
// determine if this data source should be applied to its parent
@@ -294,7 +296,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
byte inputGenState = inputDataSource.columnGenerationSteps[index];
if (inputGenState != EDhApiWorldGenerationStep.EMPTY.value
&& thisGenState <= inputGenState)
&& thisGenState <= inputGenState)
{
// check if the data changed
if (this.dataPoints[index] == null)
@@ -379,7 +381,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
for (int x = 0; x < WIDTH; x += 2)
{
for (int z = 0; z < WIDTH; z += 2)
{
{
int recipientX = (x / 2) + recipientOffsetX;
int recipientZ = (z / 2) + recipientOffsetZ;
int recipientIndex = relativePosToIndex(recipientX, recipientZ);
@@ -447,7 +449,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
return dataChanged;
}
/**
/**
* The minimum value is used because we don't want to accidentally record that
* something was generated when it wasn't.
*/
@@ -466,7 +468,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
}
return minWorldGenStepValue;
}
/**
/**
* The minimum value is used because we don't want to accidentally record that
* something was generated when it wasn't.
*/
@@ -492,7 +494,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
// special numbers:
// -2 = the column's height hasn't been determined yet
// -1 = we've reached the end of the column
int[] currentDatapointIndex = new int[] { -2, -2, -2, -2 };
int[] currentDatapointIndex = new int[]{-2, -2, -2, -2};
int lastId = 0;
byte lastBlockLight = 0;
@@ -512,10 +514,10 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
{
// if each column has reached the end of their data, nothing more needs to be done
if (currentDatapointIndex[0] == -1
&& currentDatapointIndex[1] == -1
&& currentDatapointIndex[2] == -1
&& currentDatapointIndex[3] == -1
)
&& currentDatapointIndex[1] == -1
&& currentDatapointIndex[2] == -1
&& currentDatapointIndex[3] == -1
)
{
break;
}
@@ -612,12 +614,12 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
int id = determineMostValueInColumnSlice(mergeIds, inputDataSource.mapping);
byte blockLight = (byte) determineAverageValueInColumnSlice(mergeBlockLights);
byte skyLight = (byte) determineAverageValueInColumnSlice(mergeSkyLights);
// if this slice is different then the last one, create a new one
if (id != lastId
// block and sky light might not be necessary
|| blockLight != lastBlockLight
|| skyLight != lastSkyLight)
// block and sky light might not be necessary
|| blockLight != lastBlockLight
|| skyLight != lastSkyLight)
{
if (height != 0)
{
@@ -665,7 +667,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
}
/**
* Only update the ID once it's been added to this data source.
* Updating the incoming data source will cause issues if it is applied
* Updating the incoming data source will cause issues if it is applied
* to anything else due to multiple remapping.
*/
private void remapDataColumn(int dataPointIndex, int[] remappedIds)
@@ -698,7 +700,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
{
LodUtil.assertTrue(sliceArray.length == 4, "Column Slice should only contain 4 values.");
}
int value0 = sliceArray[0];
int count0 = 0;
int value1 = sliceArray[1];
@@ -706,7 +708,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
int value2 = sliceArray[2];
int count2 = 0;
int value3 = sliceArray[3];
int count3 = 0;
int count3 = 0;
// count the occurrences of each value
for (int i = 0; i < 4; i++)
@@ -719,26 +721,42 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
}
if (value == value0)
{
count0++;
}
else if (value == value1)
{
count1++;
}
else if (value == value2)
{
count2++;
}
else
{
count3++;
}
}
// return the most common occurance
int maxCount = Math.max(count0, Math.max(count1, Math.max(count2, count3)));
if (maxCount == count0)
// if the max count is 1 then we'll just go with the first column
// if the max count is 1 then we'll just go with the first column
{
return value0;
}
else if (maxCount == count1)
{
return value1;
}
else if (maxCount == count2)
{
return value2;
else
}
else
{
return value3;
}
}
private static int determineAverageValueInColumnSlice(int[] sliceArray)
{
@@ -764,27 +782,27 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
// helper methods //
//================//
/**
/**
* Usually this should just be used internally, but there may be instances
* where the raw data arrays are available without the data source object.
*/
public static int relativePosToIndex(int relX, int relZ) throws IndexOutOfBoundsException
{
{
if (relX < 0 || relZ < 0 ||
relX > WIDTH || relZ > WIDTH)
relX > WIDTH || relZ > WIDTH)
{
throw new IndexOutOfBoundsException("Relative data source positions must be between [0] and ["+WIDTH+"] (inclusive) the relative pos: ["+relX+","+relZ+"] is outside of those boundaries.");
throw new IndexOutOfBoundsException("Relative data source positions must be between [0] and [" + WIDTH + "] (inclusive) the relative pos: [" + relX + "," + relZ + "] is outside of those boundaries.");
}
return (relX * WIDTH) + relZ;
return (relX * WIDTH) + relZ;
}
/**
/**
* Throws an exception if the given
* full data column array is in the wrong order
* IE if the first data point is the lowest and the last data point is the highest.
* Data columns should be in reverse order, IE the first data point should be the highest data point.
*
*
* @see FullDataSourceV2#dataPoints
*/
public static void throwIfDataColumnInWrongOrder(long pos, LongArrayList dataArray) throws IllegalStateException
@@ -818,7 +836,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
// reverse the array so index 0 is the highest,
// this is necessary for later logic
// source: https://stackoverflow.com/questions/2137755/how-do-i-reverse-an-int-array-in-java
for(int i = 0; i < dataColumn.size() / 2; i++)
for (int i = 0; i < dataColumn.size() / 2; i++)
{
long temp = dataColumn.getLong(i);
dataColumn.set(i, dataColumn.getLong(dataColumn.size() - i - 1));
@@ -866,18 +884,18 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
@Override
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); }
public EDhApiWorldGenerationStep getWorldGenStepAtRelativePos(int relX, int relZ)
public EDhApiWorldGenerationStep getWorldGenStepAtRelativePos(int relX, int relZ)
{
int index = relativePosToIndex(relX, relZ);
return EDhApiWorldGenerationStep.fromValue(this.columnGenerationSteps[index]);
return EDhApiWorldGenerationStep.fromValue(this.columnGenerationSteps[index]);
}
public void setSingleColumn(LongArrayList longArray, int relX, int relZ, EDhApiWorldGenerationStep worldGenStep, EDhApiWorldCompressionMode worldCompressionMode)
{
int index = relativePosToIndex(relX, relZ);
this.dataPoints[index] = longArray;
this.columnGenerationSteps[index] = worldGenStep.value;
this.columnWorldCompressionMode[index] = worldCompressionMode.value;
this.columnGenerationSteps[index] = worldGenStep.value;
this.columnWorldCompressionMode[index] = worldCompressionMode.value;
if (RUN_UPDATE_DEV_VALIDATION)
@@ -905,7 +923,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
@Override
public String toString() { return DhSectionPos.toString(this.pos); }
@Override
@Override
public int hashCode()
{
if (this.cachedHashCode == 0)
@@ -924,7 +942,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
this.cachedHashCode = result;
}
@Override
@Override
public boolean equals(Object obj)
{
if (!(obj instanceof FullDataSourceV2))
@@ -942,7 +960,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
// the positions are the same, use the hash as a quick method
// to determine if the data inside is the same.
// Note: this isn't perfect, but should work well enough for our use case.
return other.hashCode() == this.hashCode();
return other.hashCode() == this.hashCode();
}
}
@@ -952,4 +970,4 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
DATA_SOURCE_POOL.returnPooledDataSource(this);
}
}
}
@@ -25,7 +25,7 @@ import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRender
import com.seibel.distanthorizons.core.file.DataSourcePool;
import com.seibel.distanthorizons.core.file.IDataSource;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
@@ -21,10 +21,9 @@ package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
@@ -27,7 +27,7 @@ import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.util.ColorUtil;
@@ -28,7 +28,7 @@ import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArra
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
@@ -229,7 +229,7 @@ public class FullDataToRenderDataTransformer
if (!brokenPos.contains(fullDataMapping.getPos()))
{
brokenPos.add(fullDataMapping.getPos());
String dimName = level.getLevelWrapper().getDimensionType().getDimensionName();
String dimName = level.getLevelWrapper().getDimensionName();
LOGGER.warn("Unable to get data point with id ["+id+"] " +
"(Max possible ID: ["+fullDataMapping.getMaxValidId()+"]) " +
"for pos ["+fullDataMapping.getPos()+"] in dimension ["+dimName+"]. " +
@@ -386,4 +386,4 @@ public class FullDataToRenderDataTransformer
}
}
}
}
@@ -31,7 +31,8 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -66,8 +67,8 @@ public class LodDataBuilder
int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().x);
int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().z);
int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getX());
int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getZ());
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
@@ -77,8 +78,8 @@ public class LodDataBuilder
// compute the chunk dataSource offset
// this offset is used to determine where in the dataSource this chunk's data should go
int chunkOffsetX = chunkWrapper.getChunkPos().x;
if (chunkWrapper.getChunkPos().x < 0)
int chunkOffsetX = chunkWrapper.getChunkPos().getX();
if (chunkWrapper.getChunkPos().getX() < 0)
{
// expected offset positions:
// chunkPos -> offset
@@ -105,8 +106,8 @@ public class LodDataBuilder
}
chunkOffsetX *= LodUtil.CHUNK_WIDTH;
int chunkOffsetZ = chunkWrapper.getChunkPos().z;
if (chunkWrapper.getChunkPos().z < 0)
int chunkOffsetZ = chunkWrapper.getChunkPos().getZ();
if (chunkWrapper.getChunkPos().getZ() < 0)
{
chunkOffsetZ = ((chunkOffsetZ) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
if (chunkOffsetZ != 0)
@@ -237,7 +238,7 @@ public class LodDataBuilder
private static boolean blockVisible(IChunkWrapper chunkWrapper, int relBlockX, int blockY, int relBlockZ)
{
DhBlockPos originalBlockPos = new DhBlockPos(relBlockX,blockY,relBlockZ);
DhBlockPos testBlockPos = new DhBlockPos(relBlockX,blockY,relBlockZ);
final DhBlockPosMutable testBlockPos = new DhBlockPosMutable(relBlockX,blockY,relBlockZ);
// up/down
if (blockInDirectionVisible(chunkWrapper, EDhDirection.UP, originalBlockPos, testBlockPos))
@@ -272,20 +273,20 @@ public class LodDataBuilder
return false;
}
private static boolean blockInDirectionVisible(IChunkWrapper chunkWrapper, EDhDirection direction, DhBlockPos originalBlockPos, DhBlockPos testBlockPos)
private static boolean blockInDirectionVisible(IChunkWrapper chunkWrapper, EDhDirection direction, DhBlockPos originalBlockPos, DhBlockPosMutable testBlockPos)
{
originalBlockPos.mutateOffset(direction, testBlockPos);
// if the block is next to the border of a chunk, assume it's visible
if (testBlockPos.x < 0 || testBlockPos.x >= LodUtil.CHUNK_WIDTH)
if (testBlockPos.getX() < 0 || testBlockPos.getX() >= LodUtil.CHUNK_WIDTH)
{
return true;
}
if (testBlockPos.z < 0 || testBlockPos.z >= LodUtil.CHUNK_WIDTH)
if (testBlockPos.getZ() < 0 || testBlockPos.getZ() >= LodUtil.CHUNK_WIDTH)
{
return true;
}
if (testBlockPos.y < chunkWrapper.getMinBuildHeight() || testBlockPos.y > chunkWrapper.getMaxBuildHeight())
if (testBlockPos.getY() < chunkWrapper.getMinBuildHeight() || testBlockPos.getY() > chunkWrapper.getMaxBuildHeight())
{
return true;
}
@@ -25,7 +25,7 @@ import java.util.concurrent.locks.ReentrantLock;
// TODO is there a reason this is separate from FullDataSourceProviderV2?
// We shouldn't need multiple data source handlers
public abstract class AbstractDataSourceHandler
<TDataSource extends IDataSource<TDhLevel>,
<TDataSource extends IDataSource<TDhLevel>,
TDTO extends IBaseDTO<Long>,
TRepo extends AbstractDhRepo<Long, TDTO>,
TDhLevel extends IDhLevel>
@@ -38,20 +38,20 @@ public abstract class AbstractDataSourceHandler
/**
* The highest numerical detail level possible.
* Used when determining which positions to update.
*
*
* @see AbstractDataSourceHandler#MIN_SECTION_DETAIL_LEVEL
*/
public static final byte TOP_SECTION_DETAIL_LEVEL = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + LodUtil.REGION_DETAIL_LEVEL;
/**
* The lowest numerical detail level possible.
/**
* The lowest numerical detail level possible.
*
* @see AbstractDataSourceHandler#TOP_SECTION_DETAIL_LEVEL
* */
*/
public static final byte MIN_SECTION_DETAIL_LEVEL = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
protected final PositionalLockProvider updateLockProvider = new PositionalLockProvider();
/**
/**
* generally just used for debugging,
* keeps track of which positions are currently locked.
*/
@@ -123,7 +123,7 @@ public abstract class AbstractDataSourceHandler
{
return CompletableFuture.supplyAsync(() -> this.get(pos), executor);
}
catch (RejectedExecutionException ignore)
catch (RejectedExecutionException ignore)
{
// the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
return CompletableFuture.completedFuture(null);
@@ -205,7 +205,7 @@ public abstract class AbstractDataSourceHandler
}
catch (Exception e)
{
LOGGER.error("Unexpected error in async data source update, error: "+e.getMessage(), e);
LOGGER.error("Unexpected error in async data source update, error: " + e.getMessage(), e);
}
finally
{
@@ -221,9 +221,10 @@ public abstract class AbstractDataSourceHandler
}
}
/**
/**
* After this method returns the inputData will be written to file.
* @param updatePos the position to update
*
* @param updatePos the position to update
*/
protected void updateDataSourceAtPos(long updatePos, @NotNull FullDataSourceV2 inputData, boolean lockOnUpdatePos)
{
@@ -268,7 +269,7 @@ public abstract class AbstractDataSourceHandler
}
catch (Exception e)
{
LOGGER.error("Error updating pos ["+updatePos+"], error: "+e.getMessage(), e);
LOGGER.error("Error updating pos [" + updatePos + "], error: "+e.getMessage(), e);
}
finally
{
@@ -353,4 +354,4 @@ public abstract class AbstractDataSourceHandler
void OnDataSourceUpdated(TDataSource updatedFullDataSource);
}
}
}
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.file.fullDatafile;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
@@ -37,6 +38,7 @@ import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
@@ -45,18 +47,21 @@ import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
/**
* Handles reading/writing {@link FullDataSourceV2}
* Handles reading/writing {@link FullDataSourceV2}
* to and from the database.
*/
public class FullDataSourceProviderV2
extends AbstractDataSourceHandler<FullDataSourceV2, FullDataSourceV2DTO, FullDataSourceV2Repo, IDhLevel>
public class FullDataSourceProviderV2
extends AbstractDataSourceHandler<FullDataSourceV2, FullDataSourceV2DTO, FullDataSourceV2Repo, IDhLevel>
implements IDebugRenderable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -72,7 +77,7 @@ public class FullDataSourceProviderV2
/** how many data sources should be pulled down for migration at once */
private static final int MIGRATION_BATCH_COUNT = NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD;
private static final String MIGRATION_THREAD_NAME_PREFIX = "Full Data Migration Thread: ";
/**
/**
* 5 minutes <br>
* This should be much longer than any update should take. This is just
* to make sure the thread doesn't get stuck.
@@ -81,9 +86,9 @@ public class FullDataSourceProviderV2
protected final ThreadPoolExecutor migrationThreadPool;
/**
/**
* Interrupting the migration thread pool doesn't work well and may corrupt the database
* vs gracefully shutting down the thread ourselves.
* vs gracefully shutting down the thread ourselves.
*/
protected final AtomicBoolean migrationThreadRunning = new AtomicBoolean(true);
protected final FullDataSourceProviderV1<IDhLevel> legacyFileHandler;
@@ -94,17 +99,18 @@ public class FullDataSourceProviderV2
protected long migrationCount = -1;
protected boolean migrationStoppedWithError = false;
/**
/**
* Tracks which positions are currently being updated
* to prevent duplicate concurrent updates.
*/
public final Set<Long> parentUpdatingPosSet = ConcurrentHashMap.newKeySet();
// TODO only run thread if modifications happened recently
/**
/**
* This isn't in {@link AbstractDataSourceHandler} since we don't need parent updating logic
* for render data, only full data.
*/
@Nullable
private final ThreadPoolExecutor updateQueueProcessor;
@@ -114,21 +120,28 @@ public class FullDataSourceProviderV2
//=============//
public FullDataSourceProviderV2(IDhLevel level, AbstractSaveStructure saveStructure) { this(level, saveStructure, null); }
public FullDataSourceProviderV2(IDhLevel level, AbstractSaveStructure saveStructure, @Nullable File saveDirOverride)
public FullDataSourceProviderV2(IDhLevel level, AbstractSaveStructure saveStructure, @Nullable File saveDirOverride)
{
super(level, saveStructure, saveDirOverride);
this.legacyFileHandler = new FullDataSourceProviderV1<>(level, saveStructure, saveDirOverride);
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataUpdateStatus);
String dimensionName = level.getLevelWrapper().getDimensionType().getDimensionName();
String dimensionName = level.getLevelWrapper().getDimensionName();
// start migrating any legacy data sources present in the background
this.migrationThreadPool = ThreadUtil.makeRateLimitedThreadPool(1, MIGRATION_THREAD_NAME_PREFIX +"["+dimensionName+"]", Config.Client.Advanced.MultiThreading.runTimeRatioForUpdatePropagatorThreads.get(), Thread.MIN_PRIORITY, (Semaphore)null);
this.migrationThreadPool.execute(() -> this.convertLegacyDataSources());
this.migrationThreadPool = ThreadUtil.makeRateLimitedThreadPool(1, MIGRATION_THREAD_NAME_PREFIX + "[" + dimensionName + "]", Config.Client.Advanced.MultiThreading.runTimeRatioForUpdatePropagatorThreads.get(), Thread.MIN_PRIORITY, (Semaphore) null);
this.migrationThreadPool.execute(this::convertLegacyDataSources);
this.updateQueueProcessor = ThreadUtil.makeSingleThreadPool("Parent Update Queue ["+dimensionName+"]");
this.updateQueueProcessor.execute(() -> this.runUpdateQueue());
if (SharedApi.getEnvironment() != EWorldEnvironment.Server_Only)
{
this.updateQueueProcessor = ThreadUtil.makeSingleThreadPool("Parent Update Queue [" + dimensionName + "]");
this.updateQueueProcessor.execute(this::runUpdateQueue);
}
else
{
this.updateQueueProcessor = null;
}
}
@@ -152,7 +165,7 @@ public class FullDataSourceProviderV2
}
}
@Override
@Override
protected FullDataSourceV2DTO createDtoFromDataSource(FullDataSourceV2 dataSource)
{
try
@@ -163,7 +176,7 @@ public class FullDataSourceProviderV2
}
catch (IOException e)
{
LOGGER.warn("Unable to create DTO, error: "+e.getMessage(), e);
LOGGER.warn("Unable to create DTO, error: " + e.getMessage(), e);
return null;
}
}
@@ -175,6 +188,58 @@ public class FullDataSourceProviderV2
@Override
protected FullDataSourceV2 makeEmptyDataSource(long pos) { return FullDataSourceV2.DATA_SOURCE_POOL.getPooledSource(pos, true); }
@Nullable
public Long getTimestampForPos(long pos)
{
try
{
PreparedStatement preparedStatement = this.repo.createPreparedStatement(
"SELECT LastModifiedUnixDateTime " +
"FROM " + this.repo.getTableName() + " " +
"WHERE DetailLevel = ? " +
"AND PosX = ? " +
"AND PosZ = ?;"
);
preparedStatement.setInt(1, DhSectionPos.getDetailLevel(pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
preparedStatement.setInt(2, DhSectionPos.getX(pos));
preparedStatement.setInt(3, DhSectionPos.getZ(pos));
List<Map<String, Object>> row = this.repo.query(preparedStatement);
return !row.isEmpty() ? (Long) row.get(0).get("LastModifiedUnixDateTime") : null;
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
}
public Map<Long, Long> getTimestampsForRange(byte detailLevel, int startPosX, int startPosZ, int endPosX, int endPosZ)
{
try
{
PreparedStatement preparedStatement = this.repo.createPreparedStatement(
"SELECT PosX, PosZ, LastModifiedUnixDateTime " +
"FROM " + this.repo.getTableName() + " " +
"WHERE DetailLevel = ? " +
"AND PosX BETWEEN ? AND ? " +
"AND PosZ BETWEEN ? AND ?;"
);
preparedStatement.setInt(1, detailLevel - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
preparedStatement.setInt(2, startPosX);
preparedStatement.setInt(3, endPosX);
preparedStatement.setInt(4, startPosZ);
preparedStatement.setInt(5, endPosZ);
return this.repo.query(preparedStatement).stream().collect(Collectors.toMap(
row -> DhSectionPos.encode(detailLevel, (int) row.get("PosX"), (int) row.get("PosZ")),
row -> (long) row.get("LastModifiedUnixDateTime"))
);
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
}
//================//
@@ -199,7 +264,7 @@ public class FullDataSourceProviderV2
// queue parent updates
if (executor.getQueue().size() < MAX_UPDATE_TASK_COUNT
&& this.parentUpdatingPosSet.size() < MAX_UPDATE_TASK_COUNT)
&& this.parentUpdatingPosSet.size() < MAX_UPDATE_TASK_COUNT)
{
// get the positions that need to be applied to their parents
LongArrayList parentUpdatePosList = this.repo.getPositionsToUpdate(MAX_UPDATE_TASK_COUNT);
@@ -208,7 +273,7 @@ public class FullDataSourceProviderV2
HashMap<Long, HashSet<Long>> updatePosByParentPos = new HashMap<>();
for (Long pos : parentUpdatePosList)
{
updatePosByParentPos.compute(DhSectionPos.getParentPos(pos), (parentPos, updatePosSet) ->
updatePosByParentPos.compute(DhSectionPos.getParentPos(pos), (parentPos, updatePosSet) ->
{
if (updatePosSet == null)
{
@@ -224,7 +289,7 @@ public class FullDataSourceProviderV2
{
// stop if there are already a bunch of updates queued
if (this.parentUpdatingPosSet.size() > MAX_UPDATE_TASK_COUNT
|| !this.parentUpdatingPosSet.add(parentUpdatePos))
|| !this.parentUpdatingPosSet.add(parentUpdatePos))
{
break;
}
@@ -289,7 +354,8 @@ public class FullDataSourceProviderV2
}
});
}
catch (RejectedExecutionException ignore) { /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
catch (RejectedExecutionException ignore)
{ /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
catch (Exception e)
{
this.parentUpdatingPosSet.remove(parentUpdatePos);
@@ -299,14 +365,17 @@ public class FullDataSourceProviderV2
}
}
catch (InterruptedException ignored) { Thread.currentThread().interrupt(); }
catch (InterruptedException ignored)
{
Thread.currentThread().interrupt();
}
catch (Exception e)
{
LOGGER.error("Unexpected error in the parent update queue thread. Error: " + e.getMessage(), e);
}
}
LOGGER.info("Update thread ["+Thread.currentThread().getName()+"] terminated.");
LOGGER.info("Update thread [" + Thread.currentThread().getName() + "] terminated.");
}
@@ -317,8 +386,8 @@ public class FullDataSourceProviderV2
private void convertLegacyDataSources()
{
String dimensionName = this.level.getLevelWrapper().getDimensionType().getDimensionName();
LOGGER.info("Attempting to migrate data sources for: ["+dimensionName+"]-["+this.saveDir+"]...");
String dimensionName = this.level.getLevelWrapper().getDimensionName();
LOGGER.info("Attempting to migrate data sources for: [" + dimensionName + "]-[" + this.saveDir + "]...");
@@ -337,9 +406,9 @@ public class FullDataSourceProviderV2
this.showMigrationStartMessage();
LOGGER.info("deleting [" + dimensionName + "] - ["+totalDeleteCount+"] unused data sources...");
LOGGER.info("deleting [" + dimensionName + "] - [" + totalDeleteCount + "] unused data sources...");
this.legacyDeletionCount = totalDeleteCount;
ArrayList<String> unusedDataPosList = this.legacyFileHandler.repo.getUnusedDataSourcePositionStringList(50);
while (unusedDataPosList.size() != 0)
{
@@ -355,7 +424,7 @@ public class FullDataSourceProviderV2
long endStart = System.currentTimeMillis();
long deleteTime = endStart - startTime;
LOGGER.info("Deleting [" + dimensionName + "] - [" + unusedCount + "/" + totalDeleteCount + "] in ["+deleteTime+"]ms ...");
LOGGER.info("Deleting [" + dimensionName + "] - [" + unusedCount + "/" + totalDeleteCount + "] in [" + deleteTime + "]ms ...");
// a slight delay is added to prevent accidentally locking the database when deleting a lot of rows
@@ -366,10 +435,12 @@ public class FullDataSourceProviderV2
// and weak computers wait no time at all
Thread.sleep(deleteTime / 2);
}
catch (InterruptedException ignore){}
catch (InterruptedException ignore)
{
}
}
LOGGER.info("Done deleting [" + dimensionName + "] - ["+totalDeleteCount+"] unused data sources.");
LOGGER.info("Done deleting [" + dimensionName + "] - [" + totalDeleteCount + "] unused data sources.");
}
@@ -380,7 +451,7 @@ public class FullDataSourceProviderV2
long totalMigrationCount = this.legacyFileHandler.getDataSourceMigrationCount();
this.migrationCount = totalMigrationCount;
LOGGER.info("Found ["+totalMigrationCount+"] data sources that need migration.");
LOGGER.info("Found [" + totalMigrationCount + "] data sources that need migration.");
ArrayList<FullDataSourceV1> legacyDataSourceList = this.legacyFileHandler.getDataSourcesToMigrate(MIGRATION_BATCH_COUNT);
if (!legacyDataSourceList.isEmpty())
@@ -420,40 +491,40 @@ public class FullDataSourceProviderV2
{
newDataSource.close();
}
catch (Exception ignore)
{
}
});
}
catch (Exception e)
{
Long migrationPos = legacyDataSource.getPos();
LOGGER.warn("Unexpected issue migrating data source at pos " + migrationPos + ". Error: " + e.getMessage(), e);
this.legacyFileHandler.markMigrationFailed(migrationPos);
}
catch (Exception ignore)
{
}
});
}
try
catch (Exception e)
{
// wait for each thread to finish updating
CompletableFuture<Void> combinedFutures = CompletableFuture.allOf(updateFutureList.toArray(new CompletableFuture[0]));
combinedFutures.get(MIGRATION_MAX_UPDATE_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
Long migrationPos = legacyDataSource.getPos();
LOGGER.warn("Unexpected issue migrating data source at pos " + migrationPos + ". Error: " + e.getMessage(), e);
this.legacyFileHandler.markMigrationFailed(migrationPos);
}
catch (InterruptedException | TimeoutException e)
{
LOGGER.warn("Migration update timed out after [" + MIGRATION_MAX_UPDATE_TIMEOUT_IN_MS + "] milliseconds. Migration will re-try the same positions again in a moment..", e);
}
catch (ExecutionException e)
{
LOGGER.warn("Migration update failed. Migration will re-try the same positions again. Error:" + e.getMessage(), e);
}
legacyDataSourceList = this.legacyFileHandler.getDataSourcesToMigrate(MIGRATION_BATCH_COUNT);
progressCount += legacyDataSourceList.size();
this.migrationCount -= legacyDataSourceList.size();
}
try
{
// wait for each thread to finish updating
CompletableFuture<Void> combinedFutures = CompletableFuture.allOf(updateFutureList.toArray(new CompletableFuture[0]));
combinedFutures.get(MIGRATION_MAX_UPDATE_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
}
catch (InterruptedException | TimeoutException e)
{
LOGGER.warn("Migration update timed out after [" + MIGRATION_MAX_UPDATE_TIMEOUT_IN_MS + "] milliseconds. Migration will re-try the same positions again in a moment..", e);
}
catch (ExecutionException e)
{
LOGGER.warn("Migration update failed. Migration will re-try the same positions again. Error:" + e.getMessage(), e);
}
legacyDataSourceList = this.legacyFileHandler.getDataSourcesToMigrate(MIGRATION_BATCH_COUNT);
progressCount += legacyDataSourceList.size();
this.migrationCount -= legacyDataSourceList.size();
}
}
catch (Exception e)
{
@@ -463,16 +534,16 @@ public class FullDataSourceProviderV2
}
finally
{
if (this.migrationThreadRunning.get())
{
LOGGER.info("migration complete for: ["+dimensionName+"]-["+this.saveDir+"].");
this.showMigrationEndMessage(true);
this.migrationCount = 0;
}
else
{
LOGGER.info("migration stopped for: ["+dimensionName+"]-["+this.saveDir+"].");
this.showMigrationEndMessage(false);
if (this.migrationThreadRunning.get())
{
LOGGER.info("migration complete for: [" + dimensionName + "]-[" + this.saveDir + "].");
this.showMigrationEndMessage(true);
this.migrationCount = 0;
}
else
{
LOGGER.info("migration stopped for: [" + dimensionName + "]-[" + this.saveDir + "].");
this.showMigrationEndMessage(false);
this.migrationStoppedWithError = true;
}
}
@@ -498,7 +569,7 @@ public class FullDataSourceProviderV2
}
this.migrationStartMessageQueued = true;
String dimName = this.level.getLevelWrapper().getDimensionType().getDimensionName();
String dimName = this.level.getLevelWrapper().getDimensionName();
ClientApi.INSTANCE.showChatMessageNextFrame(
"Old Distant Horizons data is being migrated for ["+dimName+"]. \n" +
"While migrating LODs may load slowly \n" +
@@ -509,7 +580,7 @@ public class FullDataSourceProviderV2
private void showMigrationEndMessage(boolean success)
{
String dimName = this.level.getLevelWrapper().getDimensionType().getDimensionName();
String dimName = this.level.getLevelWrapper().getDimensionName();
if (success)
{
@@ -534,27 +605,27 @@ public class FullDataSourceProviderV2
* Returns true if this provider can generate or retrieve
* {@link FullDataSourceV2}'s that aren't currently in the database.
*/
public boolean canRetrieveMissingDataSources()
{
public boolean canRetrieveMissingDataSources()
{
// the base handler just handles basic reading/writing
// to the database and as such can't retrieve anything else.
return false;
return false;
}
/**
* Returns false if this provider isn't accepting new requests,
* this can be due to having a full queue or some other
* limiting factor. <br><br>
*
*
* Note: when overriding make sure to add: <br>
* <code>
* if (!super.canQueueRetrieval()) <br>
* { <br>
* return false; <br>
* return false; <br>
* } <br>
* </code>
* to the beginning of your override.
* Otherwise, parent retrieval limits will be ignored.
* to the beginning of your override.
* Otherwise, parent retrieval limits will be ignored.
*/
public boolean canQueueRetrieval()
{
@@ -564,18 +635,18 @@ public class FullDataSourceProviderV2
return !this.migrationThreadRunning.get();
}
/**
/**
* @return null if this provider can't generate any positions and
* an empty array if all positions were generated
* an empty array if all positions were generated
*/
@Nullable
public LongArrayList getPositionsToRetrieve(Long pos) { return null; }
public LongArrayList getPositionsToRetrieve(Long pos) { return null; }
/**
* Returns how many positions could potentially be generated for this position assuming the position is empty.
* Used when estimating the total number of retrieval requests.
*/
public int getMaxPossibleRetrievalPositionCountForPos(Long pos) { return -1; }
public int getMaxPossibleRetrievalPositionCountForPos(Long pos) { return -1; }
/** @return true if the position was queued, false if not */
public boolean queuePositionForRetrieval(Long genPos) { return false; }
@@ -585,15 +656,16 @@ public class FullDataSourceProviderV2
public void clearRetrievalQueue() { }
/** Can be used to display how many total retrieval requests might be available. */
public void setTotalRetrievalPositionCount(int newCount) { }
public void setTotalRetrievalPositionCount(int newCount) { }
/**
/**
* Returns how many data sources are currently in memory and haven't
* been saved to the database.
* Returns -1 if this provider never stores data sources to memory.
*/
public int getUnsavedDataSourceCount() { return -1; }
public boolean fileExists(long pos) { return this.repo.getDataSizeInBytes(pos) > 0; }
//===========//
@@ -612,11 +684,14 @@ public class FullDataSourceProviderV2
.forEach((pos) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 80f, 0.20f, Color.MAGENTA)); });
}
@Override
@Override
public void close()
{
super.close();
this.updateQueueProcessor.shutdownNow();
if (this.updateQueueProcessor != null)
{
this.updateQueueProcessor.shutdownNow();
}
this.legacyFileHandler.close();
@@ -624,4 +699,4 @@ public class FullDataSourceProviderV2
this.migrationThreadPool.shutdown();
}
}
}
@@ -36,12 +36,15 @@ import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.IntStream;
public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 implements IDebugRenderable
{
@@ -70,6 +73,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
//=============//
public GeneratedFullDataSourceProvider(IDhLevel level, AbstractSaveStructure saveStructure) { super(level, saveStructure); }
public GeneratedFullDataSourceProvider(IDhLevel level, AbstractSaveStructure saveStructure, @Nullable File saveDirOverride) { super(level, saveStructure, saveDirOverride); }
@@ -93,7 +97,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// don't log shutdown exceptions
if (!(exception instanceof CancellationException || exception.getCause() instanceof CancellationException))
{
LOGGER.error("Uncaught Gen Task Exception at [" + genTaskResult.pos + "], error: ["+ exception.getMessage() + "].", exception);
LOGGER.error("Uncaught Gen Task Exception at [" + genTaskResult.pos + "], error: [" + exception.getMessage() + "].", exception);
}
}
else if (genTaskResult.success)
@@ -132,21 +136,21 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
//===================================//
/**
* Assigns the queue for handling world gen and does first time setup as well. <br>
* Assumes there isn't a pre-existing queue.
* Assigns the queue for handling world gen and does first time setup as well. <br>
* Assumes there isn't a pre-existing queue.
*/
public void setWorldGenerationQueue(IFullDataSourceRetrievalQueue newWorldGenQueue)
{
boolean oldQueueExists = this.worldGenQueueRef.compareAndSet(null, newWorldGenQueue);
LodUtil.assertTrue(oldQueueExists, "previous world gen queue is still here!");
LOGGER.info("Set world gen queue for level ["+this.level+"].");
LOGGER.info("Set world gen queue for level [" + this.level.getLevelWrapper().getDimensionName() + "].");
}
@Override
public boolean canRetrieveMissingDataSources() { return true; }
@Override
public void setTotalRetrievalPositionCount(int newCount)
public void setTotalRetrievalPositionCount(int newCount)
{
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue != null)
@@ -201,7 +205,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// don't queue additional world gen requests beyond the max allotted count
return worldGenQueue.getWaitingTaskCount() < maxQueueCount;
return worldGenQueue.getWaitingTaskCount() < maxQueueCount;
}
@Override
@@ -237,6 +241,12 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
public int getUnsavedDataSourceCount() { return this.delayedFullDataSourceSaveCache.getUnsavedCount(); }
public boolean isFullyGenerated(byte[] columnGenerationSteps)
{
return IntStream.range(0, columnGenerationSteps.length)
.noneMatch(i -> columnGenerationSteps[i] == EDhApiWorldGenerationStep.EMPTY.value);
}
@Override
public LongArrayList getPositionsToRetrieve(Long pos)
{
@@ -350,6 +360,22 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
return BitShiftUtil.powerOfTwo(detailLevelDiff);
}
public Map<Long, Integer> getLoadStates(Iterable<Long> posList)
{
HashMap<Long, Integer> map = new HashMap<>();
for (long pos : posList)
{
map.put(pos,
// Loaded
this.delayedFullDataSourceSaveCache.dataSourceByPosition.containsKey(pos) ? 3
// Unloaded, but exists
: this.fileExists(pos) ? 2
// Not generated
: 1);
}
return map;
}
//=======//
@@ -392,8 +418,9 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
GeneratedFullDataSourceProvider.this.delayedFullDataSourceSaveCache.queueDataSourceForUpdateAndSave(chunkSizedFullDataSource);
};
}
}
private void onDataSourceSave(FullDataSourceV2 fullDataSource)
private void onDataSourceSave(FullDataSourceV2 fullDataSource)
{ GeneratedFullDataSourceProvider.this.updateDataSourceAsync(fullDataSource); }
@@ -407,4 +434,4 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
}
}
}
@@ -19,15 +19,67 @@
package com.seibel.distanthorizons.core.file.fullDatafile;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoginRequestQueue;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class RemoteFullDataSourceProvider extends FullDataSourceProviderV2
public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvider
{
public RemoteFullDataSourceProvider(IDhLevel level, AbstractSaveStructure saveStructure) { super(level, saveStructure); }
public RemoteFullDataSourceProvider(IDhLevel level, AbstractSaveStructure saveStructure, @Nullable File saveDirOverride) { super(level, saveStructure, saveDirOverride); }
@Nullable
private final SyncOnLoginRequestQueue syncOnLoginRequestQueue;
private final Set<Long> finishedTaskPositions = ConcurrentHashMap.newKeySet();
}
public RemoteFullDataSourceProvider(IDhLevel level, AbstractSaveStructure saveStructure, @Nullable File saveDirOverride, @Nullable SyncOnLoginRequestQueue syncOnLoginRequestQueue)
{
super(level, saveStructure, saveDirOverride);
this.syncOnLoginRequestQueue = syncOnLoginRequestQueue;
}
@Override
@Nullable
public FullDataSourceV2 get(long pos)
{
FullDataSourceV2 fullDataSource = super.get(pos);
if (fullDataSource == null || this.syncOnLoginRequestQueue == null)
{
return fullDataSource;
}
int posToMinimumDetailScale = (DhSectionPos.getDetailLevel(pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + 1);
Map<Long, Long> timestamps = this.getTimestampsForRange(
DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL,
DhSectionPos.getX(pos) * posToMinimumDetailScale,
DhSectionPos.getZ(pos) * posToMinimumDetailScale,
(DhSectionPos.getX(pos) + 1) * posToMinimumDetailScale - 1,
(DhSectionPos.getZ(pos) + 1) * posToMinimumDetailScale - 1
);
for (Map.Entry<Long, Long> entry : timestamps.entrySet())
{
if (this.finishedTaskPositions.add(entry.getKey()))
{
this.syncOnLoginRequestQueue.submitRequest(entry.getKey(), entry.getValue(), this.delayedFullDataSourceSaveCache::queueDataSourceForUpdateAndSave);
}
}
return fullDataSource;
}
@Override
public void close()
{
if (this.syncOnLoginRequestQueue != null)
{
this.syncOnLoginRequestQueue.close();
}
super.close();
}
}
@@ -88,9 +88,9 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
if (newLevelWrapper instanceof IServerKeyedClientLevel)
{
IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper;
LOGGER.info("Loading level " + newLevelWrapper.getDimensionType().getDimensionName() + " with key: " + keyedClientLevel.getServerLevelKey());
LOGGER.info("Loading level " + newLevelWrapper.getDimensionName() + " with key: " + keyedClientLevel.getServerLevelKey());
// This world was identified by the server directly, so we can know for sure which folder to use.
return new File(getSaveStructureFolderPath() + File.separatorChar + keyedClientLevel.getServerLevelKey());
return new File(getSaveStructureFolderPath() + File.separatorChar + keyedClientLevel.getServerLevelKey().replaceAll(":", "@@"));
}
@@ -102,9 +102,9 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
// create the matcher if one doesn't exist
if (this.subDimMatcher == null || !this.subDimMatcher.isFindingLevel(newClientLevelWrapper))
{
LOGGER.info("Loading level " + newClientLevelWrapper.getDimensionType().getDimensionName());
LOGGER.info("Loading level " + newClientLevelWrapper.getDimensionName());
List<File> levelFolders = this.getDhDataFoldersForDimension(newClientLevelWrapper.getDimensionType());
List<File> levelFolders = this.getDhDataFoldersForDimension(newClientLevelWrapper);
this.subDimMatcher = new SubDimensionLevelMatcher(newClientLevelWrapper, this.folder, levelFolders);
}
@@ -133,7 +133,7 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
private File getLevelFolderWithoutSimilarityMatching(ILevelWrapper level)
{
List<File> folders = this.getDhDataFoldersForDimension(level.getDimensionType());
List<File> folders = this.getDhDataFoldersForDimension(level);
if (!folders.isEmpty() && folders.get(0) != null)
{
// use the first existing sub-dimension
@@ -144,12 +144,12 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
else
{
// no valid sub dimension was found, create a new one
LOGGER.info("Default Sub Dimension not found. Creating: [" + level.getDimensionType().getDimensionName() + "]");
return new File(this.folder, level.getDimensionType().getDimensionName());
LOGGER.info("Default Sub Dimension not found. Creating: [" + level.getDimensionName() + "]");
return new File(this.folder, level.getDimensionName().replaceAll(":", "@@"));
}
}
public List<File> getDhDataFoldersForDimension(IDimensionTypeWrapper dimensionType)
public List<File> getDhDataFoldersForDimension(ILevelWrapper level)
{
File[] folders = this.folder.listFiles();
if (folders == null)
@@ -158,7 +158,7 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
}
// filter by dimension name
String expectedDimName = dimensionType.getDimensionName();
String expectedDimName = level.getDimensionName();
ArrayList<File> possibleDimFolders = new ArrayList<>();
for (File dimFolder : folders)
{
@@ -309,4 +309,4 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
@Override
public String toString() { return "[" + this.getClass().getSimpleName() + "@" + this.folder.getName() + "]"; }
}
}
@@ -201,8 +201,8 @@ public class SubDimensionLevelMatcher implements AutoCloseable
//================================//
// log the start of this attempt
LOGGER.info("Attempting to determine sub-dimension for [" + MC_CLIENT.getWrappedClientLevel().getDimensionType().getDimensionName() + "]");
LOGGER.info("Player block pos in dimension: [" + this.playerData.playerBlockPos.x + "," + this.playerData.playerBlockPos.y + "," + this.playerData.playerBlockPos.z + "]");
LOGGER.info("Attempting to determine sub-dimension for [" + MC_CLIENT.getWrappedClientLevel().getDimensionName() + "]");
LOGGER.info("Player block pos in dimension: [" + this.playerData.playerBlockPos.getX() + "," + this.playerData.playerBlockPos.getY() + "," + this.playerData.playerBlockPos.getZ() + "]");
LOGGER.info("Potential Sub Dimension folders: [" + this.potentialLevelFolders.size() + "]");
SubDimCompare mostSimilarSubDim = null;
@@ -214,7 +214,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
try
{
// get the data source to compare against
try (IDhLevel tempLevel = new DhClientLevel(new ClientOnlySaveStructure(), this.currentClientLevel, testLevelFolder, false))
try (IDhLevel tempLevel = new DhClientLevel(new ClientOnlySaveStructure(), this.currentClientLevel, testLevelFolder, false, null))
{
testFullDataSource = tempLevel.getFullDataProvider().getAsync(DhSectionPos.encodeContaining(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, this.playerData.playerBlockPos)).join();
if (testFullDataSource == null)
@@ -315,7 +315,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
// get the player data for this dimension folder
SubDimensionPlayerData testPlayerData = new SubDimensionPlayerData(testLevelFolder);
LOGGER.info("Last known player pos: [" + testPlayerData.playerBlockPos.x + "," + testPlayerData.playerBlockPos.y + "," + testPlayerData.playerBlockPos.z + "]");
LOGGER.info("Last known player pos: [" + testPlayerData.playerBlockPos.getX() + "," + testPlayerData.playerBlockPos.getY() + "," + testPlayerData.playerBlockPos.getZ() + "]");
// check if the block positions are close
int playerBlockDist = testPlayerData.playerBlockPos.getManhattanDistance(this.playerData.playerBlockPos);
@@ -380,9 +380,9 @@ public class SubDimensionLevelMatcher implements AutoCloseable
}
private File CreateSubDimFolder(String subDimId) { return new File(this.levelsFolder.getPath() + File.separatorChar + this.currentClientLevel.getDimensionType().getDimensionName(), subDimId); }
private File CreateSubDimFolder(String subDimId) { return new File(this.levelsFolder.getPath() + File.separatorChar + this.currentClientLevel.getDimensionName(), subDimId); }
@Override
public void close() { this.matcherThread.shutdownNow(); }
}
}
@@ -22,7 +22,7 @@ package com.seibel.distanthorizons.core.file.subDimMatching;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
@@ -127,9 +127,9 @@ public class SubDimensionPlayerData
public void toTomlFile(CommentedFileConfig toml)
{
// player block pos
toml.add(PLAYER_BLOCK_POS_X_PATH, this.playerBlockPos.x);
toml.add(PLAYER_BLOCK_POS_Y_PATH, this.playerBlockPos.y);
toml.add(PLAYER_BLOCK_POS_Z_PATH, this.playerBlockPos.z);
toml.add(PLAYER_BLOCK_POS_X_PATH, this.playerBlockPos.getX());
toml.add(PLAYER_BLOCK_POS_Y_PATH, this.playerBlockPos.getY());
toml.add(PLAYER_BLOCK_POS_Z_PATH, this.playerBlockPos.getZ());
toml.save();
}
@@ -138,7 +138,7 @@ public class SubDimensionPlayerData
@Override
public String toString()
{
return "PlayerBlockPos: [" + this.playerBlockPos.x + "," + this.playerBlockPos.y + "," + this.playerBlockPos.z + "]";
return "PlayerBlockPos: [" + this.playerBlockPos.getX() + "," + this.playerBlockPos.getY() + "," + this.playerBlockPos.getZ() + "]";
}
}
@@ -33,7 +33,7 @@ public class AdjacentChunkHolder
{
for (int zOffset = -1; zOffset <= 1; zOffset++)
{
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.x + xOffset, centerChunkPos.z + zOffset);
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.getX() + xOffset, centerChunkPos.getZ() + zOffset);
requestedAdjacentPositions.add(adjacentPos);
}
}
@@ -69,13 +69,13 @@ public class AdjacentChunkHolder
DhChunkPos centerPos = this.chunkArray[4].getChunkPos();
DhChunkPos offsetPos = centerWrapper.getChunkPos();
int offsetX = offsetPos.x - centerPos.x;
int offsetX = offsetPos.getX() - centerPos.getX();
if (offsetX < -1 || offsetX > 1)
{
return;
}
int offsetZ = offsetPos.z - centerPos.z;
int offsetZ = offsetPos.getZ() - centerPos.getZ();
if (offsetZ < -1 || offsetZ > 1)
{
return;
@@ -91,18 +91,18 @@ public class AdjacentChunkHolder
int chunkZ = BitShiftUtil.divideByPowerOfTwo(blockZ, 4);
IChunkWrapper centerChunk = this.chunkArray[4];
DhChunkPos centerPos = centerChunk.getChunkPos();
if (centerPos.x == chunkX && centerPos.z == chunkZ)
if (centerPos.getX() == chunkX && centerPos.getZ() == chunkZ)
{
return centerChunk;
}
int offsetX = chunkX - centerPos.x;
int offsetX = chunkX - centerPos.getX();
if (offsetX < -1 || offsetX > 1)
{
return null;
}
int offsetZ = chunkZ - centerPos.z;
int offsetZ = chunkZ - centerPos.getZ();
if (offsetZ < -1 || offsetZ > 1)
{
return null;
@@ -21,13 +21,17 @@ package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
@@ -49,15 +53,28 @@ public class DhLightingEngine
* Since these objects are always mutated anyway, using a {@link ThreadLocal} will allow us to
* only create as many of these {@link DhBlockPos} as necessary.
*/
private static final ThreadLocal<DhBlockPos> PRIMARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPos());
private static final ThreadLocal<DhBlockPos> SECONDARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPos());
private static final ThreadLocal<DhBlockPosMutable> PRIMARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPosMutable());
private static final ThreadLocal<DhBlockPosMutable> SECONDARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPosMutable());
/** if enabled will render each block light value when the lighting engine is run */
private static final boolean RENDER_BLOCK_LIGHT_WIREFRAME = false;
/** if enabled will render each sky light value when the lighting engine is run */
private static final boolean RENDER_SKY_LIGHT_WIREFRAME = false;
//=============//
// constructor //
//=============//
private DhLightingEngine() { }
//=========//
// methods //
//=========//
/**
* Note: depending on the implementation of {@link IChunkWrapper#setDhBlockLight(int, int, int, int)} and {@link IChunkWrapper#setDhSkyLight(int, int, int, int)}
* the light values may be stored in the wrapper itself instead of the wrapped chunk object.
@@ -76,12 +93,12 @@ public class DhLightingEngine
// try-finally to handle the stableArray resources
StableLightPosStack blockLightPosQueue = null;
StableLightPosStack skyLightPosQueue = null;
StableLightPosStack blockLightWorldPosQueue = null;
StableLightPosStack skyLightWorldPosQueue = null;
try
{
blockLightPosQueue = StableLightPosStack.borrowStableLightPosArray();
skyLightPosQueue = StableLightPosStack.borrowStableLightPosArray();
blockLightWorldPosQueue = StableLightPosStack.borrowStableLightPosArray();
skyLightWorldPosQueue = StableLightPosStack.borrowStableLightPosArray();
@@ -92,7 +109,7 @@ public class DhLightingEngine
{
for (int zOffset = -1; zOffset <= 1; zOffset++)
{
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.x + xOffset, centerChunkPos.z + zOffset);
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.getX() + xOffset, centerChunkPos.getZ() + zOffset);
requestedAdjacentPositions.add(adjacentPos);
}
}
@@ -114,11 +131,14 @@ public class DhLightingEngine
// get and set the adjacent chunk's initial block lights
final DhBlockPos relLightBlockPos = PRIMARY_BLOCK_POS_REF.get();
final DhBlockPos relBlockPos = SECONDARY_BLOCK_POS_REF.get();
//==================//
// set block lights //
//==================//
ArrayList<DhBlockPos> blockLightPosList = chunk.getBlockLightPosList();
// get and set the adjacent chunk's initial block lights
final DhBlockPosMutable relLightBlockPos = PRIMARY_BLOCK_POS_REF.get();
ArrayList<DhBlockPos> blockLightPosList = chunk.getWorldBlockLightPosList();
for (int blockLightIndex = 0; blockLightIndex < blockLightPosList.size(); blockLightIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
{
DhBlockPos blockLightPos = blockLightPosList.get(blockLightIndex);
@@ -127,14 +147,18 @@ public class DhLightingEngine
// get the light
IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos);
int lightValue = blockState.getLightEmission();
blockLightPosQueue.push(blockLightPos.x, blockLightPos.y, blockLightPos.z, lightValue);
blockLightWorldPosQueue.push(blockLightPos.getX(), blockLightPos.getY(), blockLightPos.getZ(), lightValue);
// set the light
blockLightPos.mutateToChunkRelativePos(relBlockPos);
chunk.setDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, lightValue);
chunk.setDhBlockLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), lightValue);
}
//================//
// set sky lights //
//================//
// get and set the adjacent chunk's initial skylights,
// if the dimension has skylights
if (maxSkyLight > 0)
@@ -160,11 +184,11 @@ public class DhLightingEngine
// add sky light to the queue
DhBlockPos skyLightPos = new DhBlockPos(chunk.getMinBlockX() + relX, y, chunk.getMinBlockZ() + relZ);
skyLightPosQueue.push(skyLightPos.x, skyLightPos.y, skyLightPos.z, maxSkyLight);
skyLightWorldPosQueue.push(skyLightPos.getX(), skyLightPos.getY(), skyLightPos.getZ(), maxSkyLight);
// set the chunk's sky light
skyLightPos.mutateToChunkRelativePos(relBlockPos);
chunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, maxSkyLight);
skyLightPos.mutateToChunkRelativePos(relLightBlockPos);
chunk.setDhSkyLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), maxSkyLight);
}
}
}
@@ -180,14 +204,16 @@ public class DhLightingEngine
}
// block light
this.propagateLightPosList(blockLightPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, newLightValue));
this.propagateLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
true);
// sky light
this.propagateLightPosList(skyLightPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, newLightValue));
this.propagateLightPosList(skyLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
false);
}
catch (Exception e)
{
@@ -195,8 +221,8 @@ public class DhLightingEngine
}
finally
{
StableLightPosStack.returnStableLightPosArray(blockLightPosQueue);
StableLightPosStack.returnStableLightPosArray(skyLightPosQueue);
StableLightPosStack.returnStableLightPosArray(blockLightWorldPosQueue);
StableLightPosStack.returnStableLightPosArray(skyLightWorldPosQueue);
}
@@ -212,13 +238,14 @@ public class DhLightingEngine
/** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */
private void propagateLightPosList(
StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder,
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc)
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc,
boolean propagatingBlockLights)
{
// these objects are saved so they can be mutated throughout the method,
// this reduces the number of allocations necessary, reducing GC pressure
final LightPos lightPos = new LightPos(0, 0, 0, 0);
final DhBlockPos neighbourBlockPos = PRIMARY_BLOCK_POS_REF.get();
final DhBlockPos relNeighbourBlockPos = SECONDARY_BLOCK_POS_REF.get();
final DhBlockPosMutable neighbourBlockPos = PRIMARY_BLOCK_POS_REF.get();
final DhBlockPosMutable relNeighbourBlockPos = SECONDARY_BLOCK_POS_REF.get();
// update each light position
@@ -239,14 +266,14 @@ public class DhLightingEngine
// only continue if the light position is inside one of our chunks
IChunkWrapper neighbourChunk = adjacentChunkHolder.getByBlockPos(neighbourBlockPos.x, neighbourBlockPos.z);
IChunkWrapper neighbourChunk = adjacentChunkHolder.getByBlockPos(neighbourBlockPos.getX(), neighbourBlockPos.getZ());
if (neighbourChunk == null)
{
// the light pos is outside our generator's range, ignore it
continue;
}
if (relNeighbourBlockPos.y < neighbourChunk.getMinNonEmptyHeight() || relNeighbourBlockPos.y > neighbourChunk.getMaxBuildHeight())
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight() || relNeighbourBlockPos.getY() > neighbourChunk.getMaxBuildHeight())
{
// the light pos is outside the chunk's min/max height,
// this can happen if given a chunk that hasn't finished generating
@@ -273,16 +300,103 @@ public class DhLightingEngine
// now that light has been propagated to this blockPos
// we need to queue it up so its neighbours can be propagated as well
lightPosQueue.push(neighbourBlockPos.x, neighbourBlockPos.y, neighbourBlockPos.z, targetLevel);
lightPosQueue.push(neighbourBlockPos.getX(), neighbourBlockPos.getY(), neighbourBlockPos.getZ(), targetLevel);
}
}
}
// can be enable if troubleshooting lighting issues
if (RENDER_BLOCK_LIGHT_WIREFRAME && propagatingBlockLights)
{
RenderDhLightValuesAsWireframe(adjacentChunkHolder, true);
}
else if (RENDER_SKY_LIGHT_WIREFRAME && !propagatingBlockLights)
{
RenderDhLightValuesAsWireframe(adjacentChunkHolder, false);
}
// propagation complete
}
//===========//
// debugging //
//===========//
/** Draw a wireframe representing each block's light value */
private static void RenderDhLightValuesAsWireframe(AdjacentChunkHolder adjacentChunkHolder, boolean renderBlockLights)
{
for (IChunkWrapper chunk : adjacentChunkHolder.chunkArray)
{
if (chunk == null)
{
continue;
}
int chunkMinX = chunk.getMinBlockX();
int chunkMinZ = chunk.getMinBlockZ();
int minY = chunk.getMinNonEmptyHeight();
int maxY = chunk.getMaxNonEmptyHeight();
// check each position's light
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
for (int y = minY; y < maxY; y++)
{
int lightValue = renderBlockLights? chunk.getDhBlockLight(x, y, z) : chunk.getDhSkyLight(x, y, z);
if (lightValue != LodUtil.MIN_MC_LIGHT)
{
// hotter colors for more intense light
Color color;
if (lightValue >= 14)
{
color = Color.WHITE;
}
else if (lightValue >= 10)
{
color = Color.PINK;
}
else if (lightValue >= 6)
{
color = Color.YELLOW;
}
else if (lightValue >= 4)
{
color = Color.ORANGE;
}
else
{
color = Color.RED;
}
// a color can be set to null if you only want to troubleshoot up to a certain light level
if (color != null)
{
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(DhSectionPos.encode((byte) 0, chunkMinX + x, chunkMinZ + z), y, y + 1, 0.2f, color),
10.0, 0f
)
);
}
}
}
}
}
}
}
//================//
// helper classes //
//================//
@@ -293,7 +407,7 @@ public class DhLightingEngine
@FunctionalInterface
interface ISetLightFunc { void setLight(IChunkWrapper chunk, DhBlockPos pos, int lightValue); }
private static class LightPos extends DhBlockPos
private static class LightPos extends DhBlockPosMutable
{
public int lightValue;
@@ -303,6 +417,11 @@ public class DhLightingEngine
this.lightValue = lightValue;
}
@Override
public String toString() { return this.lightValue+" - ["+ this.x +", "+ this.y +", "+ this.z +"]"; }
}
/**
@@ -402,9 +521,9 @@ public class DhLightingEngine
{
int subIndex = this.index * INTS_PER_LIGHT_POS;
pos.x = this.lightPositions.getInt(subIndex);
pos.y = this.lightPositions.getInt(subIndex + 1);
pos.z = this.lightPositions.getInt(subIndex + 2);
pos.setX(this.lightPositions.getInt(subIndex));
pos.setY(this.lightPositions.getInt(subIndex + 1));
pos.setZ(this.lightPositions.getInt(subIndex + 2));
pos.lightValue = this.lightPositions.getInt(subIndex + 3);
this.index--;
@@ -21,11 +21,12 @@ package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.LodQuadTree;
import java.io.Closeable;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
@@ -104,6 +105,8 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
/** used for rendering to the F3 menu */
int getEstimatedTotalTaskCount();
void setEstimatedTotalTaskCount(int newEstimate);
void addDebugMenuStringsToList(List<String> messageList);
}
}
@@ -27,7 +27,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.tasks.*;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.config.Config;
@@ -47,6 +47,7 @@ import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Consumer;
@@ -456,8 +457,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
case VANILLA_CHUNKS:
{
return this.generator.generateChunks(
chunkPosMin.x,
chunkPosMin.z,
chunkPosMin.getX(),
chunkPosMin.getZ(),
granularity,
targetDataDetail,
generatorMode,
@@ -482,8 +483,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
case API_CHUNKS:
{
return this.generator.generateApiChunks(
chunkPosMin.x,
chunkPosMin.z,
chunkPosMin.getX(),
chunkPosMin.getZ(),
granularity,
targetDataDetail,
generatorMode,
@@ -535,6 +536,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
@Override
public void setEstimatedTotalTaskCount(int newEstimate) { this.estimatedTotalTaskCount = newEstimate; }
public void addDebugMenuStringsToList(List<String> messageList) { }
//==========//
@@ -698,4 +701,4 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
}
}
}
@@ -0,0 +1,75 @@
package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.client.AbstractFullDataRequestQueue;
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.LodUtil;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.*;
public class WorldRemoteGenerationQueue extends AbstractFullDataRequestQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private int estimatedTotalTaskCount;
@Override
protected int getRequestRateLimit() { return this.networkState.config.getGenerationRequestRateLimit(); }
@Override
protected String getQueueName() { return "World Remote Generation Queue"; }
public WorldRemoteGenerationQueue(ClientNetworkState networkState, IDhClientLevel level)
{
super(networkState, level, false, Config.Client.Advanced.Debugging.DebugWireframe.showWorldGenQueue);
}
@Override
public byte lowestDataDetail()
{
return LodUtil.BLOCK_DETAIL_LEVEL;
}
@Override
public byte highestDataDetail()
{
return LodUtil.BLOCK_DETAIL_LEVEL;
}
@Override
public CompletableFuture<WorldGenResult> submitGenTask(long sectionPos, byte requiredDataDetail, IWorldGenTaskTracker tracker)
{
return super.submitRequest(sectionPos, tracker.getChunkDataConsumer())
.thenApply(result -> result
? WorldGenResult.CreateSuccess(sectionPos)
: WorldGenResult.CreateFail());
}
@Override
public void startAndSetTargetPos(DhBlockPos2D targetPos)
{
super.tick(targetPos);
}
@Override
public int getEstimatedTotalTaskCount() { return this.estimatedTotalTaskCount; }
@Override
public void setEstimatedTotalTaskCount(int newEstimate) { this.estimatedTotalTaskCount = newEstimate; }
@Override
public CompletableFuture<Void> startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning)
{
return super.startClosing(alsoInterruptRunning);
}
}
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.generation.tasks;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import javax.annotation.Nullable;
import java.util.function.Consumer;
/**
@@ -32,6 +33,7 @@ public interface IWorldGenTaskTracker
/** Returns true if the task hasn't been garbage collected. */
boolean isMemoryAddressValid();
@Nullable
Consumer<FullDataSourceV2> getChunkDataConsumer();
}
@@ -36,4 +36,4 @@ public final class InProgressWorldGenTaskGroup
this.group = group;
}
}
}
@@ -40,11 +40,9 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class AbstractDhLevel implements IDhLevel
{
@@ -57,7 +55,7 @@ public abstract class AbstractDhLevel implements IDhLevel
@Nullable
public BeaconBeamRepo beaconBeamRepo;
protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 2_000);
protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 500);
/** contains the {@link DhChunkPos} for each {@link DhSectionPos} that are queued to save via {@link AbstractDhLevel#delayedFullDataSourceSaveCache} */
protected final ConcurrentHashMap<Long, HashSet<DhChunkPos>> updatedChunkPosSetBySectionPos = new ConcurrentHashMap<>();
protected final ConcurrentHashMap<DhChunkPos, Integer> updatedChunkHashesByChunkPos = new ConcurrentHashMap<>();
@@ -113,11 +111,15 @@ public abstract class AbstractDhLevel implements IDhLevel
GenericObjectRenderer genericRenderer = this.getGenericRenderer();
if (genericRenderer != null)
{
// only add clouds for certain dimension types
if (!this.getLevelWrapper().hasCeiling()
&& !this.getLevelWrapper().getDimensionType().isTheEnd())
// only client levels can render clouds
if (this instanceof IDhClientLevel)
{
this.cloudRenderHandler = new CloudRenderHandler(this, genericRenderer);
// only add clouds for certain dimension types
if (!this.getLevelWrapper().hasCeiling()
&& !this.getLevelWrapper().getDimensionType().isTheEnd())
{
this.cloudRenderHandler = new CloudRenderHandler((IDhClientLevel)this, genericRenderer);
}
}
@@ -149,7 +151,7 @@ public abstract class AbstractDhLevel implements IDhLevel
}
this.updatedChunkPosSetBySectionPos.compute(dataSource.getPos(), (dataSourcePos, chunkPosSet) ->
this.updatedChunkPosSetBySectionPos.compute(dataSource.getPos(), (dataSourcePos, chunkPosSet) ->
{
if (chunkPosSet == null)
{
@@ -166,7 +168,7 @@ public abstract class AbstractDhLevel implements IDhLevel
private void onDataSourceSave(FullDataSourceV2 fullDataSource)
{
this.updateDataSourcesAsync(fullDataSource).thenRun(() ->
this.updateDataSourcesAsync(fullDataSource).thenRun(() ->
{
HashSet<DhChunkPos> updatedChunkPosSet = this.updatedChunkPosSetBySectionPos.remove(fullDataSource.getPos());
if (updatedChunkPosSet != null)
@@ -182,7 +184,7 @@ public abstract class AbstractDhLevel implements IDhLevel
ApiEventInjector.INSTANCE.fireAllEvents(
DhApiChunkModifiedEvent.class,
new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.x, chunkPos.z));
new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.getX(), chunkPos.getZ()));
}
}
});
@@ -215,11 +217,12 @@ public abstract class AbstractDhLevel implements IDhLevel
//=================//
@Override
public void setBeaconBeamsForChunk(DhChunkPos chunkPos, List<BeaconBeamDTO> newBeamList)
public void updateBeaconBeamsForChunk(IChunkWrapper chunkToUpdate, ArrayList<IChunkWrapper> nearbyChunkList)
{
if (this.beaconRenderHandler != null)
{
this.beaconRenderHandler.setBeaconBeamsForChunk(chunkPos, newBeamList);
List<BeaconBeamDTO> activeBeamList = chunkToUpdate.getAllActiveBeacons(nearbyChunkList);
this.beaconRenderHandler.setBeaconBeamsForChunk(chunkToUpdate.getChunkPos(), activeBeamList);
}
}
@@ -259,4 +262,4 @@ public abstract class AbstractDhLevel implements IDhLevel
}
}
}
}
@@ -27,7 +27,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.AbstractDataSourceHandler;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.LodQuadTree;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
@@ -20,15 +20,27 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.config.AppliedConfigState;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.generation.WorldRemoteGenerationQueue;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoginRequestQueue;
import com.seibel.distanthorizons.core.network.event.ScopedNetworkEventSource;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
@@ -36,6 +48,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import javax.annotation.CheckForNull;
import java.awt.*;
import java.io.File;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -44,25 +58,66 @@ import java.util.concurrent.CompletableFuture;
public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static class WorldGenState extends WorldGenModule.AbstractWorldGenState
{
WorldGenState(IDhClientLevel level, ClientNetworkState networkState)
{
this.worldGenerationQueue = new WorldRemoteGenerationQueue(networkState, level);
}
}
public final ClientLevelModule clientside;
public final IClientLevelWrapper levelWrapper;
public final AbstractSaveStructure saveStructure;
public final RemoteFullDataSourceProvider dataFileHandler;
@CheckForNull
private final ClientNetworkState networkState;
@Nullable
private final ScopedNetworkEventSource eventSource;
public final WorldGenModule worldGenModule;
public final AppliedConfigState<Boolean> worldGeneratorEnabledConfig;
@Nullable
private final SyncOnLoginRequestQueue syncOnLoginRequestQueue;
//=============//
// constructor //
//=============//
public DhClientLevel(AbstractSaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper) { this(saveStructure, clientLevelWrapper, null, true); }
public DhClientLevel(AbstractSaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper, @Nullable File fullDataSaveDirOverride, boolean enableRendering)
public DhClientLevel(AbstractSaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper, @Nullable ClientNetworkState networkState) { this(saveStructure, clientLevelWrapper, null, true, networkState); }
public DhClientLevel(AbstractSaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper, @Nullable File fullDataSaveDirOverride, boolean enableRendering, @Nullable ClientNetworkState networkState)
{
if (saveStructure.getFullDataFolder(clientLevelWrapper).mkdirs())
{
LOGGER.warn("unable to create data folder.");
}
this.levelWrapper = clientLevelWrapper;
this.levelWrapper.setParentLevel(this);
this.saveStructure = saveStructure;
this.dataFileHandler = new RemoteFullDataSourceProvider(this, saveStructure, fullDataSaveDirOverride);
this.networkState = networkState;
if (networkState != null)
{
this.eventSource = new ScopedNetworkEventSource(networkState.getSession());
this.syncOnLoginRequestQueue = new SyncOnLoginRequestQueue(this, networkState);
this.registerNetworkHandlers();
}
else
{
this.eventSource = null;
this.syncOnLoginRequestQueue = null;
}
this.dataFileHandler = new RemoteFullDataSourceProvider(this, saveStructure, fullDataSaveDirOverride, this.syncOnLoginRequestQueue);
this.worldGeneratorEnabledConfig = new AppliedConfigState<>(Config.Client.Advanced.WorldGenerator.enableDistantGeneration);
this.worldGenModule = new WorldGenModule(this);
this.clientside = new ClientLevelModule(this);
this.createAndSetSupportingRepos(this.dataFileHandler.repo.databaseFile);
@@ -75,7 +130,30 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
}
}
private void registerNetworkHandlers()
{
assert this.eventSource != null;
assert this.networkState != null;
this.eventSource.registerHandler(FullDataPartialUpdateMessage.class, msg ->
{
try
{
FullDataSourceV2DTO dataSourceDto = this.networkState.decodeDataSourceAndReleaseBuffer(msg.payload);
if (!msg.isSameLevelAs(this.levelWrapper))
{
return;
}
this.updateDataSourcesAsync(dataSourceDto.createPooledDataSource(this.levelWrapper));
}
catch (Exception e)
{
LOGGER.error("Error while updating full data source", e);
}
});
}
//==============//
// tick methods //
@@ -87,6 +165,11 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
try
{
this.clientside.clientTick();
if (this.syncOnLoginRequestQueue != null)
{
this.syncOnLoginRequestQueue.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
}
}
catch (Exception e)
{
@@ -94,6 +177,50 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
}
}
@Override
public void doWorldGen()
{
ClientNetworkState networkState = this.networkState;
boolean isClientUsable = false, isAllowedDimension = false;
if (networkState != null)
{
isClientUsable = networkState.isReady();
isAllowedDimension = MC_CLIENT.getWrappedClientLevel() == this.levelWrapper;
}
boolean shouldDoWorldGen = isClientUsable
&& networkState.config.isDistantGenerationEnabled()
&& isAllowedDimension
&& this.clientside.isRendering();
boolean isWorldGenRunning = this.worldGenModule.isWorldGenRunning();
if (shouldDoWorldGen && !isWorldGenRunning)
{
// start world gen
this.worldGenModule.startWorldGen(this.dataFileHandler, new WorldGenState(this, networkState));
// populate the queue based on the current rendering tree
ClientLevelModule.ClientRenderState renderState = this.clientside.ClientRenderStateRef.get();
renderState.quadtree.leafNodeIterator().forEachRemaining(node -> {
this.dataFileHandler.getAsync(node.sectionPos);
});
}
else if (!shouldDoWorldGen && isWorldGenRunning)
{
// stop world gen
this.worldGenModule.stopWorldGen(this.dataFileHandler);
}
if (this.worldGenModule.isWorldGenRunning())
{
this.worldGenModule.worldGenTick(
new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())
.scale(MC_CLIENT.getWrappedClientLevel().getDimensionType().getTeleportationScale(this.getLevelWrapper().getDimensionType()))
);
}
}
@Override
public void render(DhApiRenderParam renderEventParam, IProfilerWrapper profiler)
{ this.clientside.render(renderEventParam, profiler); }
@@ -115,7 +242,10 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
public IClientLevelWrapper getClientLevelWrapper() { return this.levelWrapper; }
@Override
public void clearRenderCache() { this.clientside.clearRenderCache(); }
public void clearRenderCache()
{
this.clientside.clearRenderCache();
}
@Override
public ILevelWrapper getLevelWrapper() { return this.levelWrapper; }
@@ -129,7 +259,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
@Override
public void addDebugMenuStringsToList(List<String> messageList)
{
String dimName = this.levelWrapper.getDimensionType().getDimensionName();
String dimName = this.levelWrapper.getDimensionName();
boolean rendering = this.clientside.isRendering();
messageList.add("["+dimName+"] rendering: "+(rendering ? "yes" : "no"));
@@ -152,11 +282,33 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
{
messageList.add(" Migration Failed");
}
// world gen
this.worldGenModule.addDebugMenuStringsToList(messageList);
if (this.syncOnLoginRequestQueue != null)
{
assert this.networkState != null;
if (this.networkState.config.getSynchronizeOnLogin())
{
this.syncOnLoginRequestQueue.addDebugMenuStringsToList(messageList);
}
}
}
@Override
public void close()
{
if (this.worldGenModule != null)
{
this.worldGenModule.close();
}
if (this.eventSource != null)
{
this.eventSource.close();
}
this.levelWrapper.setParentLevel(null);
this.clientside.close();
super.close();
@@ -183,4 +335,16 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
return (renderState != null) ? renderState.renderBufferHandler : null;
}
}
@Override
public void onWorldGenTaskComplete(long pos)
{
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(pos, 128f, 156f, 0.09f, Color.red.darker()),
0.2, 32f
)
);
this.clientside.reloadPos(pos);
}
}
@@ -28,8 +28,8 @@ import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
@@ -154,12 +154,14 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
public IClientLevelWrapper getClientLevelWrapper() { return MC_CLIENT.getWrappedClientLevel(); }
@Override
public void clearRenderCache() { this.clientside.clearRenderCache(); }
public void clearRenderCache() {
this.clientside.clearRenderCache();
}
@Override
public IServerLevelWrapper getServerLevelWrapper() { return serverLevelWrapper; }
public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; }
@Override
public ILevelWrapper getLevelWrapper() { return getServerLevelWrapper(); }
public ILevelWrapper getLevelWrapper() { return this.getServerLevelWrapper(); }
@Override
public FullDataSourceProviderV2 getFullDataProvider() { return this.serverside.fullDataFileHandler; }
@@ -167,7 +169,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
@Override
public AbstractSaveStructure getSaveStructure()
{
return serverside.saveStructure;
return this.serverside.saveStructure;
}
@Override
@@ -189,7 +191,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
public void addDebugMenuStringsToList(List<String> messageList)
{
// header
String dimName = this.serverLevelWrapper.getDimensionType().getDimensionName();
String dimName = this.serverLevelWrapper.getDimensionName();
boolean rendering = this.clientside.isRendering();
messageList.add("["+dimName+"] rendering: "+(rendering ? "yes" : "no"));
@@ -216,12 +218,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
// world gen
WorldGenModule worldGenState = this.serverside.worldGenModule;
String worldGenDisplayString = worldGenState.getDebugMenuString();
if (worldGenDisplayString != null)
{
messageList.add(worldGenDisplayString);
}
this.serverside.worldGenModule.addDebugMenuStringsToList(messageList);
}
@@ -262,4 +259,4 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
this.clientside.reloadPos(pos);
}
}
}
@@ -19,33 +19,73 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.server.RemotePlayerConnectionHandler;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException;
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
import com.seibel.distanthorizons.core.network.messages.ILevelRelatedMessage;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import com.seibel.distanthorizons.core.network.messages.TrackableMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPayload;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage;
import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.CheckForNull;
import java.text.MessageFormat;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.Map;
import java.util.concurrent.*;
import java.util.function.Consumer;
public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final ConfigBasedLogger NETWORK_LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
() -> Config.Client.Advanced.Logging.logNetworkEvent.get());
public static final int FULL_DATA_CHUNK_SIZE = 1048000; // 576 bytes left for other contents
public final ServerLevelModule serverside;
private final IServerLevelWrapper serverLevelWrapper;
private final RemotePlayerConnectionHandler remotePlayerConnectionHandler;
/**
* This queue is used for ensuring fair generation speed for each player. <br>
* Every tick the first player gets used for centering generation, and then is immediately moved into the back of the queue. <br>
* TODO only add players that actually have something to generate
*/
private final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenPlayerCenteringQueue = new ConcurrentLinkedQueue<>();
private final ConcurrentMap<Long, DataSourceRequestGroup> requestGroupsByPos = new ConcurrentHashMap<>();
private final ConcurrentMap<Long, DataSourceRequestGroup> requestGroupsByFutureId = new ConcurrentHashMap<>();
//=============//
// constructor //
//=============//
public DhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper)
public DhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, RemotePlayerConnectionHandler remotePlayerConnectionHandler)
{
if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs())
{
@@ -57,74 +97,363 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
this.runRepoReliantSetup();
LOGGER.info("Started DHLevel for {} with saves at {}", serverLevelWrapper, saveStructure);
this.remotePlayerConnectionHandler = remotePlayerConnectionHandler;
}
public void registerNetworkHandlers(ServerPlayerState serverPlayerState)
{
serverPlayerState.session.registerHandler(FullDataSourceRequestMessage.class, this.currentLevelOnly(msg ->
{
ServerPlayerState.RateLimiterSet rateLimiterSet = serverPlayerState.getRateLimiterSet(this);
if (msg.clientTimestamp == null)
{
// Normal generation
if (!serverPlayerState.config.isDistantGenerationEnabled())
{
msg.sendResponse(new RequestRejectedException("Operation is disabled from config."));
return;
}
if (!rateLimiterSet.generationRequestRateLimiter.tryAcquire(msg))
{
return;
}
while (true)
{
DataSourceRequestGroup requestGroup = this.requestGroupsByPos.computeIfAbsent(msg.sectionPos, pos ->
{
DataSourceRequestGroup newGroup = new DataSourceRequestGroup();
this.tryFulfillDataSourceRequestGroup(newGroup, pos);
NETWORK_LOGGER.debug("[{}] Created request group for pos {}", this.serverLevelWrapper.getDimensionName(), pos);
return newGroup;
});
// If this fails, loop until either permit is acquired or group is removed to create another one
if (!requestGroup.requestAddSemaphore.tryAcquire())
{
Thread.yield();
continue;
}
this.requestGroupsByFutureId.put(msg.futureId, requestGroup);
requestGroup.requestMessages.put(msg.futureId, msg);
requestGroup.requestAddSemaphore.release();
break;
}
}
else
{
// Sync only
if (!serverPlayerState.config.getSynchronizeOnLogin())
{
msg.sendResponse(new RequestRejectedException("Operation is disabled from config."));
return;
}
if (!rateLimiterSet.syncOnLoginRateLimiter.tryAcquire(msg))
{
return;
}
Long serverTimestamp = this.serverside.fullDataFileHandler.getTimestampForPos(msg.sectionPos);
if (serverTimestamp == null || serverTimestamp <= msg.clientTimestamp)
{
rateLimiterSet.syncOnLoginRateLimiter.release();
msg.sendResponse(new FullDataSourceResponseMessage(null));
return;
}
ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
if (executor == null)
{
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
return;
}
this.serverside.fullDataFileHandler.getAsync(msg.sectionPos).thenAcceptAsync(fullDataSource ->
{
rateLimiterSet.syncOnLoginRateLimiter.release();
FullDataPayload payload = new FullDataPayload(fullDataSource);
payload.acceptInChunkMessages(FULL_DATA_CHUNK_SIZE, msg.getSession()::sendMessage);
msg.sendResponse(new FullDataSourceResponseMessage(payload));
}, executor);
}
}));
serverPlayerState.session.registerHandler(CancelMessage.class, msg ->
{
DataSourceRequestGroup requestGroup = this.requestGroupsByFutureId.remove(msg.futureId);
if (requestGroup == null)
{
return;
}
// If this fails, group is being removed and completing cancellation is not necessary
if (requestGroup.requestRemoveSemaphore.tryAcquire())
{
// Prevent adding requests in case the group will be removed by this cancellation
requestGroup.requestAddSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
requestGroup.requestRemoveSemaphore.release();
serverPlayerState.getRateLimiterSet(this).generationRequestRateLimiter.release();
FullDataSourceRequestMessage requestMessage = requestGroup.requestMessages.remove(msg.futureId);
if (requestGroup.requestMessages.isEmpty())
{
NETWORK_LOGGER.debug("[{}] Cancelled request group {}", this.serverLevelWrapper.getDimensionName(), requestMessage.sectionPos);
this.requestGroupsByPos.remove(requestMessage.sectionPos);
this.serverside.fullDataFileHandler.removeRetrievalRequestIf(pos -> pos == requestMessage.sectionPos);
}
else
{
requestGroup.requestAddSemaphore.release(Short.MAX_VALUE);
}
}
});
}
public <T extends NetworkMessage> Consumer<T> currentLevelOnly(Consumer<T> next)
{
return msg ->
{
LodUtil.assertTrue(msg instanceof ILevelRelatedMessage, MessageFormat.format("Received message does not implement {0}: {1}", ILevelRelatedMessage.class.getSimpleName(), msg.getClass().getSimpleName()));
// Handle only in requested dimension
if (!((ILevelRelatedMessage) msg).isSameLevelAs(this.getServerLevelWrapper()))
{
return;
}
// If player is not in this dimension and handling multiple dimensions at once is not allowed
assert msg.getSession().serverPlayer != null;
if (msg.getSession().serverPlayer.getLevel() != this.getLevelWrapper())
{
// If the message can be replied to - reply with error, otherwise just ignore
if (msg instanceof TrackableMessage)
{
((TrackableMessage) msg).sendResponse(new InvalidLevelException(MessageFormat.format(
"Generation not allowed. Requested dimension: {0}, player dimension: {1}, handler dimension: {2}",
((ILevelRelatedMessage) msg).getLevelName(),
msg.getSession().serverPlayer.getLevel().getDimensionName(),
this.getLevelWrapper().getDimensionName()
)));
}
return;
}
next.accept(msg);
};
}
//=========//
// methods //
//=========//
public void serverTick() { }
public void addPlayer(IServerPlayerWrapper serverPlayer)
{
this.worldGenPlayerCenteringQueue.add(serverPlayer);
}
public void removePlayer(IServerPlayerWrapper serverPlayer)
{
this.worldGenPlayerCenteringQueue.remove(serverPlayer);
}
@Override
public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data) { return this.getFullDataProvider().updateDataSourceAsync(data); }
public void serverTick()
{
// Send finished data source requests
for (Map.Entry<Long, DataSourceRequestGroup> entry : this.requestGroupsByPos.entrySet())
{
DataSourceRequestGroup requestGroup = entry.getValue();
if (requestGroup.fullDataSource == null)
{
continue;
}
NETWORK_LOGGER.debug("[{}] Fulfilled request group {}", this.serverLevelWrapper.getDimensionName(), entry.getKey());
// Make this group unavailable for adding into
this.requestGroupsByPos.remove(entry.getKey());
requestGroup.requestRemoveSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
requestGroup.requestAddSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
if (executor == null)
{
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
continue;
}
CompletableFuture.runAsync(() ->
{
FullDataPayload payload = new FullDataPayload(requestGroup.fullDataSource);
for (FullDataSourceRequestMessage msg : requestGroup.requestMessages.values())
{
this.requestGroupsByFutureId.remove(msg.futureId);
ServerPlayerState serverPlayerState = this.remotePlayerConnectionHandler.getConnectedPlayer(msg.serverPlayer());
if (serverPlayerState == null)
{
continue;
}
serverPlayerState.getRateLimiterSet(this).generationRequestRateLimiter.release();
payload.acceptInChunkMessages(FULL_DATA_CHUNK_SIZE, msg.getSession()::sendMessage);
msg.sendResponse(new FullDataSourceResponseMessage(payload));
}
}, executor);
}
}
@Override
public int getMinY() { return getLevelWrapper().getMinHeight(); }
public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data)
{
if (!Config.Client.Advanced.Multiplayer.ServerNetworking.enableRealTimeUpdates.get())
{
return this.getFullDataProvider().updateDataSourceAsync(data);
}
ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
if (executor == null)
{
LOGGER.warn("Unable to send FullDataPartialUpdateMessage - getNetworkCompressionExecutor() is null");
return this.getFullDataProvider().updateDataSourceAsync(data);
}
CompletableFuture.runAsync(() ->
{
FullDataPayload payload = new FullDataPayload(data);
for (ServerPlayerState serverPlayerState : this.remotePlayerConnectionHandler.getConnectedPlayers())
{
if (serverPlayerState.serverPlayer().getLevel() != this.serverLevelWrapper)
{
continue;
}
if (!serverPlayerState.config.isRealTimeUpdatesEnabled())
{
continue;
}
Vec3d playerPosition = serverPlayerState.serverPlayer().getPosition();
int distanceFromPlayer = DhSectionPos.getManhattanBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
if (distanceFromPlayer >= serverPlayerState.serverPlayer().getViewDistance() &&
distanceFromPlayer <= serverPlayerState.config.getRenderDistanceRadius())
{
payload.acceptInChunkMessages(FULL_DATA_CHUNK_SIZE, serverPlayerState.session::sendMessage);
serverPlayerState.session.sendMessage(new FullDataPartialUpdateMessage(this.serverLevelWrapper, payload));
}
}
}, executor);
return this.getFullDataProvider().updateDataSourceAsync(data);
}
@Override
public int getMinY()
{
return this.getLevelWrapper().getMinHeight();
}
@Override
public void close()
{
super.close();
serverside.close();
LOGGER.info("Closed DHLevel for {}", getLevelWrapper());
this.serverside.close();
LOGGER.info("Closed DHLevel for {}", this.getLevelWrapper());
}
@Override
public void doWorldGen()
{
boolean shouldDoWorldGen = true; //todo;
boolean isWorldGenRunning = serverside.worldGenModule.isWorldGenRunning();
boolean isWorldGenRunning = this.serverside.worldGenModule.isWorldGenRunning();
if (shouldDoWorldGen && !isWorldGenRunning)
{
// start world gen
serverside.worldGenModule.startWorldGen(serverside.fullDataFileHandler, new ServerLevelModule.WorldGenState(this));
this.serverside.worldGenModule.startWorldGen(this.serverside.fullDataFileHandler, new ServerLevelModule.WorldGenState(this));
}
else if (!shouldDoWorldGen && isWorldGenRunning)
{
// stop world gen
serverside.worldGenModule.stopWorldGen(serverside.fullDataFileHandler);
this.serverside.worldGenModule.stopWorldGen(this.serverside.fullDataFileHandler);
}
if (serverside.worldGenModule.isWorldGenRunning())
if (this.serverside.worldGenModule.isWorldGenRunning())
{
serverside.worldGenModule.worldGenTick(new DhBlockPos2D(0, 0)); // todo;
IServerPlayerWrapper firstPlayer = this.worldGenPlayerCenteringQueue.peek();
if (firstPlayer == null)
{
return;
}
// Put first player in back before removing from front, so it can be removed by other thread without blocking
// - if it gets removed, remove() below will remove the item we just put instead
this.worldGenPlayerCenteringQueue.add(firstPlayer);
this.worldGenPlayerCenteringQueue.remove(firstPlayer);
Vec3d position = firstPlayer.getPosition();
this.serverside.worldGenModule.worldGenTick(new DhBlockPos2D((int) position.x, (int) position.z));
}
}
@Override
public IServerLevelWrapper getServerLevelWrapper() { return serverLevelWrapper; }
public IServerLevelWrapper getServerLevelWrapper()
{
return this.serverLevelWrapper;
}
@Override
public ILevelWrapper getLevelWrapper() { return getServerLevelWrapper(); }
public ILevelWrapper getLevelWrapper()
{
return this.getServerLevelWrapper();
}
@Override
public FullDataSourceProviderV2 getFullDataProvider() { return this.serverside.fullDataFileHandler; }
public FullDataSourceProviderV2 getFullDataProvider()
{
return this.serverside.fullDataFileHandler;
}
@Override
public AbstractSaveStructure getSaveStructure()
{
return serverside.saveStructure;
return this.serverside.saveStructure;
}
@Override
public boolean hasSkyLight() { return this.serverLevelWrapper.hasSkyLight(); }
private void tryFulfillDataSourceRequestGroup(DataSourceRequestGroup requestGroup, long pos)
{
this.serverside.fullDataFileHandler.getAsync(pos).thenAccept(fullDataSource -> {
if (this.serverside.fullDataFileHandler.isFullyGenerated(fullDataSource.columnGenerationSteps))
{
requestGroup.fullDataSource = fullDataSource;
}
else
{
this.serverside.fullDataFileHandler.queuePositionForRetrieval(pos);
}
});
}
@Override
public void onWorldGenTaskComplete(long pos)
{
//TODO: Send packet to client
DataSourceRequestGroup requestGroup = this.requestGroupsByPos.get(pos);
if (requestGroup != null)
{
this.tryFulfillDataSourceRequestGroup(requestGroup, pos);
}
}
@Override
@@ -148,8 +477,21 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
@Override
public void addDebugMenuStringsToList(List<String> messageList)
{
String dimName = this.serverLevelWrapper.getDimensionType().getDimensionName();
String dimName = this.serverLevelWrapper.getDimensionName();
messageList.add("["+dimName+"]");
}
}
private static class DataSourceRequestGroup
{
public final ConcurrentMap<Long, FullDataSourceRequestMessage> requestMessages = new ConcurrentHashMap<>();
@CheckForNull
public FullDataSourceV2 fullDataSource;
// Maybe there's a better way to do synchronization, but this should suffice
// Why not something like ReentrantReadWriteLock: locks should not be bound to threads
public final Semaphore requestAddSemaphore = new Semaphore(Short.MAX_VALUE, true);
public final Semaphore requestRemoveSemaphore = new Semaphore(Short.MAX_VALUE, true);
}
}
@@ -20,13 +20,13 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
public interface IDhClientLevel extends IDhLevel
public interface IDhClientLevel extends IDhWorldGenLevel
{
void clientTick();
@@ -25,11 +25,11 @@ import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -48,7 +48,7 @@ public interface IDhLevel extends AutoCloseable
void updateChunkAsync(IChunkWrapper chunk, int newChunkHash);
void loadBeaconBeamsInPos(long pos);
void setBeaconBeamsForChunk(DhChunkPos chunkPos, List<BeaconBeamDTO> beamList);
void updateBeaconBeamsForChunk(IChunkWrapper chunkToUpdate, ArrayList<IChunkWrapper> nearbyChunkList);
void unloadBeaconBeamsInPos(long pos);
FullDataSourceProviderV2 getFullDataProvider();
@@ -59,8 +59,8 @@ public interface IDhLevel extends AutoCloseable
CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data);
/**
* this number is generally related to how many data sources have been updated
/**
* this number is generally related to how many data sources have been updated
* due to chunk modifications or loads.
*/
int getUnsavedDataSourceCount();
@@ -80,4 +80,4 @@ public interface IDhLevel extends AutoCloseable
@Nullable
RenderBufferHandler getRenderBufferHandler();
}
}
@@ -19,7 +19,7 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
/**
@@ -28,15 +28,12 @@ import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindab
*/
public interface IKeyedClientLevelManager extends IBindable
{
IServerKeyedClientLevel getServerKeyedLevel();
/** Called when a client level is wrapped by a ServerEnhancedClientLevel, for integration into mod internals. */
void setServerKeyedLevel(IServerKeyedClientLevel clientLevel);
IServerKeyedClientLevel getOverrideWrapper();
IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String levelKey);
void clearServerKeyedLevel();
/** Returns a new instance of a ServerEnhancedClientLevel. */
IServerKeyedClientLevel getServerKeyedLevel(ILevelWrapper level, String serverLevelKey);
/** Sets the LOD engine to use the override wrapper, if the server has communication enabled. */
void setUseOverrideWrapper(boolean useOverrideWrapper);
boolean getUseOverrideWrapper();
boolean isEnabled();
void disable();
}
@@ -23,10 +23,11 @@ import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSource
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import org.apache.logging.log4j.Logger;
import java.io.Closeable;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
@@ -137,20 +138,21 @@ public class WorldGenModule implements Closeable
public boolean isWorldGenRunning() { return this.worldGenStateRef.get() != null; }
public String getDebugMenuString()
public void addDebugMenuStringsToList(List<String> messageList)
{
AbstractWorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
return null;
return;
}
String waitingCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getWaitingTaskCount());
String inProgressCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getInProgressTaskCount());
String totalCountEstimateStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getEstimatedTotalTaskCount());
return "World Gen Tasks: "+waitingCountStr+"/"+totalCountEstimateStr+" (in progress: "+inProgressCountStr+")";
messageList.add("World Gen Tasks: "+waitingCountStr+"/"+totalCountEstimateStr+" (in progress: "+inProgressCountStr+")");
worldGenState.worldGenerationQueue.addDebugMenuStringsToList(messageList);
}
@@ -185,4 +187,4 @@ public class WorldGenModule implements Closeable
{ this.worldGenerationQueue.startAndSetTargetPos(targetPosForGeneration); }
}
}
}
@@ -101,7 +101,7 @@ public class ConfigBasedLogger
else
logger.log(logLevel, msgStr);
}
if (mode.levelForChat.isLessSpecificThan(level))
if (MC != null && mode.levelForChat.isLessSpecificThan(level))
{
if (param.length > 0 && param[param.length - 1] instanceof Throwable)
MC.logToChat(level, msgStr + "\n" +
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.logging.f3;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.jar.ModJarInfo;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
@@ -60,9 +61,9 @@ public class F3Screen
Parent Update #: 12 <br><br>
Client_Server World with 3 levels <br>
[overworld] rendering: Active <br>
[the_end] rendering: Inactive <br>
[the_nether] rendering: Inactive <br><br>
[minecraft:overworld] rendering: Active <br>
[minecraft:the_end] rendering: Inactive <br>
[minecraft:the_nether] rendering: Inactive <br><br>
VBO Render Count: 199/374 <br>
</code>
@@ -72,6 +73,7 @@ public class F3Screen
ThreadPoolExecutor worldGenPool = ThreadPoolUtil.getWorldGenExecutor();
ThreadPoolExecutor fileHandlerPool = ThreadPoolUtil.getFileHandlerExecutor();
ThreadPoolExecutor updatePool = ThreadPoolUtil.getUpdatePropagatorExecutor();
ThreadPoolExecutor lodBuilderPool = ThreadPoolUtil.getChunkToLodBuilderExecutor();
ThreadPoolExecutor bufferBuilderPool = ThreadPoolUtil.getBufferBuilderExecutor();
ThreadPoolExecutor bufferUploaderPool = ThreadPoolUtil.getBufferUploaderExecutor();
@@ -81,11 +83,17 @@ public class F3Screen
messageList.add("");
messageList.add(ModInfo.READABLE_NAME+": "+ModInfo.VERSION);
if (ModInfo.IS_DEV_BUILD)
{
String shortCommitHash = ModJarInfo.Git_Commit.length() >= 8 ? ModJarInfo.Git_Commit.substring(0, 8) : ModJarInfo.Git_Commit;
messageList.add("Build: " + shortCommitHash + " (" + ModJarInfo.Git_Branch + ")");
}
messageList.add("");
// thread pools
messageList.add(getThreadPoolStatString("World Gen", worldGenPool));//"World Gen Tasks: 40/5304, (in progress: 7)");
messageList.add(getThreadPoolStatString("File Handler", fileHandlerPool));
messageList.add(getThreadPoolStatString("Update Propagator", updatePool));
messageList.add(getThreadPoolStatString("LOD Builder", lodBuilderPool));
messageList.add(getThreadPoolStatString("Buffer Builder", bufferBuilderPool));
messageList.add(getThreadPoolStatString("Buffer Uploader", bufferUploaderPool));
messageList.add("");
@@ -93,7 +101,7 @@ public class F3Screen
messageList.add(SharedApi.INSTANCE.getDebugMenuString());
messageList.add("");
// world / levels
messageList.add(world.GetDebugMenuString());
world.addDebugMenuStringsToList(messageList);
for (IDhLevel level : levelIterator)
{
level.addDebugMenuStringsToList(messageList);
@@ -133,4 +141,4 @@ public class F3Screen
}
}
@@ -0,0 +1,346 @@
package com.seibel.distanthorizons.core.multiplayer.client;
import com.google.common.base.Stopwatch;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException;
import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException;
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
import com.seibel.distanthorizons.core.network.session.SessionClosedException;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateLimiter;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.LogManager;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import java.awt.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
public abstract class AbstractFullDataRequestQueue implements IDebugRenderable, AutoCloseable
{
private static final ConfigBasedSpamLogger LOGGER = new ConfigBasedSpamLogger(LogManager.getLogger(),
() -> Config.Client.Advanced.Logging.logNetworkEvent.get(), 3);
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final Timer TASK_FINISH_TIMER = TimerUtil.CreateTimer("RequestTaskFinishTimer");
private static final int MAX_RETRY_ATTEMPTS = 3;
protected static final long SHUTDOWN_TIMEOUT_SECONDS = 5;
public final ClientNetworkState networkState;
protected final IDhClientLevel level;
private final boolean changedOnly;
private volatile CompletableFuture<Void> closingFuture = null;
protected final ConcurrentMap<Long, RequestQueueEntry> waitingTasks = new ConcurrentHashMap<>();
private final Semaphore pendingTasksSemaphore = new Semaphore(Short.MAX_VALUE, true);
private final AtomicInteger finishedRequests = new AtomicInteger();
private final AtomicInteger failedRequests = new AtomicInteger();
private final ConfigEntry<Boolean> showDebugWireframeConfig;
private final SupplierBasedRateLimiter<Void> rateLimiter = new SupplierBasedRateLimiter<>(this::getRequestRateLimit);
protected abstract int getRequestRateLimit();
protected abstract String getQueueName();
public AbstractFullDataRequestQueue(ClientNetworkState networkState, IDhClientLevel level, boolean changedOnly, ConfigEntry<Boolean> showDebugWireframeConfig)
{
this.networkState = networkState;
this.level = level;
this.changedOnly = changedOnly;
this.showDebugWireframeConfig = showDebugWireframeConfig;
DebugRenderer.register(this, this.showDebugWireframeConfig);
}
public CompletableFuture<Boolean> submitRequest(long sectionPos, Consumer<FullDataSourceV2> chunkDataConsumer)
{
return this.submitRequest(sectionPos, null, chunkDataConsumer);
}
public CompletableFuture<Boolean> submitRequest(long sectionPos, @Nullable Long clientTimestamp, Consumer<FullDataSourceV2> chunkDataConsumer)
{
LodUtil.assertTrue(DhSectionPos.getDetailLevel(sectionPos) == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL, "Only highest-detail sections are allowed.");
RequestQueueEntry entry = new RequestQueueEntry(chunkDataConsumer, clientTimestamp);
entry.future.whenComplete((result, throwable) ->
{
this.waitingTasks.remove(sectionPos);
this.finishedRequests.incrementAndGet();
if (!result || throwable != null)
{
this.failedRequests.incrementAndGet();
}
});
this.waitingTasks.put(sectionPos, entry);
return entry.future;
}
protected int posDistanceSquared(DhBlockPos2D targetPos, long pos)
{
return (int) DhSectionPos.getCenterBlockPos(pos).distSquared(targetPos);
}
public synchronized boolean tick(DhBlockPos2D targetPos)
{
if (this.closingFuture != null || !this.networkState.isReady())
{
return false;
}
while (this.getWaitingTaskCount() > this.getInProgressTaskCount()
&& this.getInProgressTaskCount() < this.getRequestRateLimit()
&& this.pendingTasksSemaphore.tryAcquire())
{
if (!this.rateLimiter.tryAcquire())
{
this.pendingTasksSemaphore.release();
break;
}
this.sendNewRequest(targetPos);
}
return true;
}
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf)
{
for (Map.Entry<Long, RequestQueueEntry> mapEntry : this.waitingTasks.entrySet())
{
long pos = mapEntry.getKey();
RequestQueueEntry entry = mapEntry.getValue();
if (removeIf.accept(pos))
{
LOGGER.debug("Removing request " + mapEntry.getKey() + "...");
entry.future.cancel(false);
if (entry.request != null)
{
entry.request.cancel(false);
}
}
}
}
private void sendNewRequest(DhBlockPos2D targetPos)
{
Map.Entry<Long, RequestQueueEntry> mapEntry = this.waitingTasks.entrySet().stream()
.filter(task -> task.getValue().request == null)
.min((x, y) -> this.posDistanceSquared(targetPos, x.getKey()) - this.posDistanceSquared(targetPos, y.getKey()))
.orElse(null);
if (mapEntry == null)
{
this.pendingTasksSemaphore.release();
return;
}
long sectionPos = mapEntry.getKey();
RequestQueueEntry entry = mapEntry.getValue();
CompletableFuture<FullDataSourceResponseMessage> request = this.networkState.getSession().sendRequest(
new FullDataSourceRequestMessage(this.level.getLevelWrapper(), sectionPos, entry.updateTimestamp),
FullDataSourceResponseMessage.class
);
entry.request = request;
request.handle((response, throwable) ->
{
this.pendingTasksSemaphore.release();
try
{
if (throwable != null)
{
throw throwable;
}
if (response.payload != null)
{
FullDataSourceV2DTO dataSourceDto = this.networkState.decodeDataSourceAndReleaseBuffer(response.payload);
ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
if (executor == null)
{
LOGGER.warn("Unable to handle FullDataPayload - getNetworkCompressionExecutor() is null");
return null;
}
CompletableFuture.runAsync(() ->
{
try
{
FullDataSourceV2 fullDataSource = dataSourceDto.createPooledDataSource(this.level.getLevelWrapper());
entry.chunkDataConsumer.accept(fullDataSource);
FullDataSourceV2.DATA_SOURCE_POOL.returnPooledDataSource(fullDataSource);
}
catch (IOException | DataCorruptedException | InterruptedException e)
{
throw new RuntimeException(e);
}
}, executor);
}
else
{
LodUtil.assertTrue(this.changedOnly, "Received empty data source response for not changes-only request");
}
}
catch (InvalidLevelException | RequestRejectedException ignored)
{
// We're too late / some cases might trigger a bunch of expected rejections
return entry.future.complete(false);
}
catch (SessionClosedException | CancellationException ignored)
{
// Triggered when level is unloaded
return entry.future.cancel(false);
}
catch (RateLimitedException e)
{
LOGGER.warn("Rate limited by server, re-queueing task [" + sectionPos + "]: " + e.getMessage());
// Skip 1 second
this.rateLimiter.acquireOrDrain(Integer.MAX_VALUE);
entry.request = null;
return null;
}
catch (Throwable e)
{
entry.retryAttempts--;
LOGGER.error("Error while fetching full data source, attempts left: {} / {}", entry.retryAttempts, MAX_RETRY_ATTEMPTS, e);
// Retry logic
if (entry.retryAttempts > 0)
{
entry.request = null;
return null;
}
else
{
return entry.future.complete(false);
}
}
// Hack to work around a race condition
// If you finish the request too quickly, the section will never render
TASK_FINISH_TIMER.schedule(new TimerTask()
{
@Override
public void run()
{
entry.future.complete(true);
}
}, 10000);
return null;
});
}
public void addDebugMenuStringsToList(List<String> messageList)
{
messageList.add(this.getQueueName() + " [" + this.level.getClientLevelWrapper().getDimensionName() + "]");
messageList.add("Requests: " + this.finishedRequests + " / " + (this.getWaitingTaskCount() + this.finishedRequests.get()) + " (failed: " + this.failedRequests + ", rate limit: " + this.getRequestRateLimit() + ")");
}
public int getWaitingTaskCount() { return this.waitingTasks.size(); }
public int getInProgressTaskCount() { return Short.MAX_VALUE - this.pendingTasksSemaphore.availablePermits(); }
public CompletableFuture<Void> startClosing(boolean alsoInterruptRunning)
{
return this.closingFuture = CompletableFuture.runAsync(() -> {
Stopwatch stopwatch = Stopwatch.createStarted();
do
{
for (RequestQueueEntry entry : this.waitingTasks.values())
{
entry.future.cancel(alsoInterruptRunning);
if (entry.request != null && entry.request.cancel(alsoInterruptRunning))
{
this.pendingTasksSemaphore.release();
}
}
}
while (!this.pendingTasksSemaphore.tryAcquire(Short.MAX_VALUE) && stopwatch.elapsed(TimeUnit.SECONDS) < SHUTDOWN_TIMEOUT_SECONDS);
if (stopwatch.elapsed(TimeUnit.SECONDS) >= SHUTDOWN_TIMEOUT_SECONDS)
{
LOGGER.warn(this.getQueueName() + " for " + this.level.getLevelWrapper() + " did not shutdown in " + SHUTDOWN_TIMEOUT_SECONDS + " seconds! Some unfinished tasks might be left hanging.");
}
});
}
@Override
public void close()
{
DebugRenderer.unregister(this, this.showDebugWireframeConfig);
}
@Override
public void debugRender(DebugRenderer r)
{
if (MC_CLIENT.getWrappedClientLevel() != this.level.getClientLevelWrapper())
{
return;
}
for (Map.Entry<Long, RequestQueueEntry> mapEntry : this.waitingTasks.entrySet())
{
r.renderBox(new DebugRenderer.Box(mapEntry.getKey(), -32f, 64f, 0.05f,
mapEntry.getValue().request != null ? Color.red : Color.gray
));
}
}
protected static class RequestQueueEntry
{
public final CompletableFuture<Boolean> future = new CompletableFuture<>();
public final Consumer<FullDataSourceV2> chunkDataConsumer;
@Nullable
public final Long updateTimestamp;
@CheckForNull
public CompletableFuture<?> request;
public int retryAttempts = MAX_RETRY_ATTEMPTS;
public RequestQueueEntry(
Consumer<FullDataSourceV2> chunkDataConsumer,
@Nullable Long updateTimestamp)
{
this.chunkDataConsumer = chunkDataConsumer;
this.updateTimestamp = updateTimestamp;
}
}
}
@@ -0,0 +1,175 @@
package com.seibel.distanthorizons.core.multiplayer.client;
import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
import com.seibel.distanthorizons.core.network.INetworkObject;
import com.seibel.distanthorizons.core.network.event.ScopedNetworkEventSource;
import com.seibel.distanthorizons.core.network.event.internal.CloseEvent;
import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageEvent;
import com.seibel.distanthorizons.core.network.messages.base.CurrentLevelKeyMessage;
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataChunkMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPayload;
import com.seibel.distanthorizons.core.network.session.Session;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.coreapi.ModInfo;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;
import org.apache.logging.log4j.LogManager;
import org.jetbrains.annotations.Nullable;
import java.io.Closeable;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
public class ClientNetworkState implements Closeable
{
protected static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
() -> Config.Client.Advanced.Logging.logNetworkEvent.get());
private final Session session = new Session(null);
/**
* Returns the client used by this instance. <p>
* If you need to subscribe to any packet events, create an instance of {@link ScopedNetworkEventSource} using the returned instance.
*/
public Session getSession() { return this.session; }
public SessionConfig config = new SessionConfig();
private volatile boolean configReceived = false;
private final SessionConfig.ChangeListener configChangeListener = new SessionConfig.ChangeListener(this::sendConfigMessage);
public boolean isReady() { return this.configReceived; }
private EServerSupportStatus serverSupportStatus = EServerSupportStatus.NONE;
/** Protocol version closest to supported by this mod version */
@Nullable
private Integer closestProtocolVersion;
private final ConcurrentMap<Integer, CompositeByteBuf> fullDataBuffers = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS)
.<Integer, CompositeByteBuf>build()
.asMap();
/**
* Constructs a new instance.
*/
public ClientNetworkState()
{
this.session.registerHandler(IncompatibleMessageEvent.class, event ->
{
if (this.closestProtocolVersion == null || Math.abs(event.protocolVersion - ModInfo.PROTOCOL_VERSION) < this.closestProtocolVersion)
{
this.closestProtocolVersion = event.protocolVersion;
}
});
this.session.registerHandler(CurrentLevelKeyMessage.class, msg ->
{
if (this.serverSupportStatus == EServerSupportStatus.NONE)
{
this.serverSupportStatus = EServerSupportStatus.LEVELS_ONLY;
}
});
this.session.registerHandler(SessionConfigMessage.class, msg ->
{
this.serverSupportStatus = EServerSupportStatus.FULL;
LOGGER.info("Connection config has been changed: {}", msg.config);
this.config = msg.config;
this.configReceived = true;
});
this.session.registerHandler(CloseEvent.class, msg ->
{
this.configReceived = false;
});
this.session.registerHandler(FullDataChunkMessage.class, msg ->
{
if (msg.isFirst)
{
CompositeByteBuf composite = this.fullDataBuffers.remove(msg.bufferId);
if (composite != null)
{
composite.release();
LOGGER.debug("Released full data buffer {}: {}", msg.bufferId, composite);
}
}
CompositeByteBuf composite = this.fullDataBuffers.computeIfAbsent(msg.bufferId, bufferId -> ByteBufAllocator.DEFAULT.compositeBuffer());
composite.addComponent(true, msg.buffer);
LOGGER.debug("Full data buffer {}: {}", msg.bufferId, composite);
});
this.session.registerHandler(FullDataPartialUpdateMessage.class, msg ->
{
// Dummy handler to prevent unhandled message warnings
});
}
public FullDataSourceV2DTO decodeDataSourceAndReleaseBuffer(FullDataPayload msg)
{
CompositeByteBuf composite = this.fullDataBuffers.remove(msg.dtoBufferId);
Objects.requireNonNull(composite);
try
{
return INetworkObject.decodeToInstance(new FullDataSourceV2DTO(), composite);
}
finally
{
composite.release();
}
}
public void sendConfigMessage()
{
this.configReceived = false;
this.getSession().sendMessage(new SessionConfigMessage(new SessionConfig()));
}
public void addDebugMenuStringsToList(List<String> messageList)
{
if (this.session.isClosed())
{
messageList.add("Session closed: " + this.session.getCloseReason().getMessage());
return;
}
if (this.serverSupportStatus == EServerSupportStatus.NONE && this.closestProtocolVersion != null)
{
messageList.add("Incompatible protocol version: " + this.closestProtocolVersion + ", required: " + ModInfo.PROTOCOL_VERSION);
return;
}
messageList.add(this.serverSupportStatus.message);
}
@Override
public void close()
{
this.configChangeListener.close();
this.session.close();
}
private enum EServerSupportStatus
{
NONE("Server does not support DH"),
LEVELS_ONLY("Server supports shared level keys"),
FULL("Server has full DH support");
public final String message;
EServerSupportStatus(String message)
{
this.message = message;
}
}
}
@@ -0,0 +1,30 @@
package com.seibel.distanthorizons.core.multiplayer.client;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
public class SyncOnLoginRequestQueue extends AbstractFullDataRequestQueue
{
public SyncOnLoginRequestQueue(IDhClientLevel level, ClientNetworkState networkState)
{
super(networkState, level, true, Config.Client.Advanced.Debugging.DebugWireframe.showWorldGenQueue);
}
@Override
protected int getRequestRateLimit() { return this.networkState.config.getSyncOnLoginRateLimit(); }
@Override
protected String getQueueName() { return "Sync On Login Queue"; }
@Override
public boolean tick(DhBlockPos2D targetPos)
{
if (!this.networkState.config.getSynchronizeOnLogin())
{
return false;
}
return super.tick(targetPos);
}
}
@@ -0,0 +1,149 @@
package com.seibel.distanthorizons.core.multiplayer.config;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.network.INetworkObject;
import io.netty.buffer.ByteBuf;
import java.io.Closeable;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.seibel.distanthorizons.core.config.Config.Client.Advanced.*;
public class SessionConfig implements INetworkObject
{
private static final LinkedHashMap<String, Entry> CONFIG_ENTRIES = new LinkedHashMap<>();
private static <T> void registerConfigEntry(ConfigEntry<T> configEntry, BiFunction<T, T, T> valueConstrainer)
{
CONFIG_ENTRIES.put(Objects.requireNonNull(configEntry.getServersideShortName()), new Entry(configEntry, valueConstrainer));
}
private final LinkedHashMap<String, Object> values = new LinkedHashMap<>();
public SessionConfig constrainingConfig;
static
{
// Note: config values are ordered by serversideShortName when transmitted
registerConfigEntry(Graphics.Quality.lodChunkRenderDistanceRadius, Math::min);
registerConfigEntry(WorldGenerator.enableDistantGeneration, (x, y) -> x && y);
registerConfigEntry(Multiplayer.ServerNetworking.generationRequestRateLimit, Math::min);
registerConfigEntry(Multiplayer.ServerNetworking.enableRealTimeUpdates, (x, y) -> x && y);
registerConfigEntry(Multiplayer.ServerNetworking.synchronizeOnLogin, (x, y) -> x && y);
registerConfigEntry(Multiplayer.ServerNetworking.syncOnLoginRateLimit, Math::min);
}
public int getRenderDistanceRadius() { return this.getValue(Graphics.Quality.lodChunkRenderDistanceRadius); }
public boolean isDistantGenerationEnabled() { return this.getValue(WorldGenerator.enableDistantGeneration); }
public int getGenerationRequestRateLimit() { return this.getValue(Multiplayer.ServerNetworking.generationRequestRateLimit); }
public boolean isRealTimeUpdatesEnabled() { return this.getValue(Multiplayer.ServerNetworking.enableRealTimeUpdates); }
public boolean getSynchronizeOnLogin() { return this.getValue(Multiplayer.ServerNetworking.synchronizeOnLogin); }
public int getSyncOnLoginRateLimit() { return this.getValue(Multiplayer.ServerNetworking.syncOnLoginRateLimit); }
@SuppressWarnings("unchecked")
private <T> T getValue(String name)
{
Entry entry = CONFIG_ENTRIES.get(name);
T value = (T) this.values.get(name);
if (value == null)
{
value = (T) entry.supplier.get();
}
return (this.constrainingConfig != null
? (T) entry.valueConstrainer.apply(value, this.constrainingConfig.getValue(name))
: value);
}
private <T> T getValue(ConfigEntry<T> configEntry)
{
return this.getValue(configEntry.getServersideShortName());
}
private Map<String, ?> getValues()
{
return CONFIG_ENTRIES.keySet().stream().collect(Collectors.toMap(
Function.identity(),
this::getValue,
(x, y) -> x,
LinkedHashMap::new
));
}
@Override
public void encode(ByteBuf out)
{
this.writeFixedLengthCollection(out, this.getValues().values());
}
@Override
public void decode(ByteBuf in)
{
for (String key : CONFIG_ENTRIES.keySet())
{
Object currentValue = this.getValue(key);
Object newValue = Codec.getCodec(currentValue.getClass()).decode.apply(currentValue, in);
this.values.put(key, newValue);
}
}
@Override
public String toString()
{
return MoreObjects.toStringHelper(this)
.add("values", this.getValues())
.toString();
}
private static class Entry
{
public final ConfigEntry<Object> supplier;
public final BiFunction<Object, Object, Object> valueConstrainer;
@SuppressWarnings("unchecked")
private <T> Entry(ConfigEntry<T> supplier, BiFunction<T, T, T> valueConstrainer)
{
this.supplier = (ConfigEntry<Object>) supplier;
this.valueConstrainer = (BiFunction<Object, Object, Object>) valueConstrainer;
}
}
public static class ChangeListener implements Closeable
{
private final ArrayList<ConfigChangeListener<?>> changeListeners;
public ChangeListener(Runnable runnable)
{
this.changeListeners = new ArrayList<>(CONFIG_ENTRIES.size());
for (Entry entry : CONFIG_ENTRIES.values())
{
this.changeListeners.add(new ConfigChangeListener<>(entry.supplier, ignored -> runnable.run()));
}
}
@Override
public void close()
{
for (ConfigChangeListener<?> changeListener : this.changeListeners)
{
changeListener.close();
}
this.changeListeners.clear();
}
}
}
@@ -0,0 +1,70 @@
package com.seibel.distanthorizons.core.multiplayer.server;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import com.seibel.distanthorizons.core.network.session.Session;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
public class RemotePlayerConnectionHandler
{
private final ConcurrentMap<IServerPlayerWrapper, ServerPlayerState> connectedPlayers = new ConcurrentHashMap<>();
private final ConcurrentMap<IServerPlayerWrapper, Queue<NetworkMessage>> messageQueue = new ConcurrentHashMap<>();
public void handlePluginMessage(IServerPlayerWrapper player, NetworkMessage message)
{
ServerPlayerState playerState = this.connectedPlayers.get(player);
if (playerState != null)
{
playerState.session.tryHandleMessage(message);
}
else
{
this.messageQueue.computeIfAbsent(player, k -> new ConcurrentLinkedQueue<>()).add(message);
}
}
public ServerPlayerState getConnectedPlayer(IServerPlayerWrapper player)
{
return this.connectedPlayers.get(player);
}
public Iterable<ServerPlayerState> getConnectedPlayers()
{
return this.connectedPlayers.values();
}
public ServerPlayerState registerJoinedPlayer(IServerPlayerWrapper serverPlayer)
{
ServerPlayerState state = new ServerPlayerState(serverPlayer);
this.connectedPlayers.put(serverPlayer, state);
Queue<NetworkMessage> queuedMessages = this.messageQueue.get(serverPlayer);
if (queuedMessages != null)
{
Session session = state.session;
for (NetworkMessage message : queuedMessages)
{
session.tryHandleMessage(message);
}
this.messageQueue.remove(serverPlayer);
}
return state;
}
public void unregisterLeftPlayer(IServerPlayerWrapper serverPlayer)
{
ServerPlayerState playerState = this.connectedPlayers.remove(serverPlayer);
if (playerState != null)
{
playerState.close();
}
}
}
@@ -0,0 +1,106 @@
package com.seibel.distanthorizons.core.multiplayer.server;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.level.DhServerLevel;
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
import com.seibel.distanthorizons.core.network.messages.base.CurrentLevelKeyMessage;
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
import com.seibel.distanthorizons.core.network.event.internal.CloseEvent;
import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
import com.seibel.distanthorizons.core.network.session.Session;
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateAndConcurrencyLimiter;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.ConcurrentHashMap;
import static com.seibel.distanthorizons.core.config.Config.Client.Advanced.Multiplayer.ServerNetworking;
public class ServerPlayerState
{
public final Session session;
public IServerPlayerWrapper serverPlayer() { return this.session.serverPlayer; }
@NotNull
public SessionConfig config = new SessionConfig();
private final SessionConfig.ChangeListener configChangeListener = new SessionConfig.ChangeListener(this::onConfigChanged);
private String lastLevelKey = "";
private final ConfigChangeListener<String> levelKeyPrefixChangeListener = new ConfigChangeListener<>(ServerNetworking.levelKeyPrefix, this::sendLevelKey);
private final ConcurrentHashMap<DhServerLevel, RateLimiterSet> rateLimiterSets = new ConcurrentHashMap<>();
public RateLimiterSet getRateLimiterSet(DhServerLevel level)
{
return this.rateLimiterSets.computeIfAbsent(level, ignored -> new RateLimiterSet());
}
public void clearRateLimiterSets()
{
this.rateLimiterSets.clear();
}
public ServerPlayerState(IServerPlayerWrapper serverPlayer)
{
this.session = new Session(serverPlayer);
this.session.registerHandler(SessionConfigMessage.class, sessionConfigMessage ->
{
this.config.constrainingConfig = sessionConfigMessage.config;
this.sendLevelKey(null);
this.session.sendMessage(new SessionConfigMessage(this.config));
});
this.session.registerHandler(CloseEvent.class, event -> {
// No-op. removes "Unhandled message" log entries
});
}
private void sendLevelKey(String ignored)
{
if (ServerNetworking.sendLevelKeys.get())
{
String levelKey = this.serverPlayer().getLevel().getKeyedLevelDimensionName();
if (!levelKey.equals(this.lastLevelKey))
{
this.lastLevelKey = levelKey;
this.session.sendMessage(new CurrentLevelKeyMessage(levelKey));
}
}
}
private void onConfigChanged()
{
this.session.sendMessage(new SessionConfigMessage(this.config));
}
public void close()
{
this.levelKeyPrefixChangeListener.close();
this.configChangeListener.close();
this.session.close();
}
public class RateLimiterSet
{
public final SupplierBasedRateAndConcurrencyLimiter<FullDataSourceRequestMessage> generationRequestRateLimiter = new SupplierBasedRateAndConcurrencyLimiter<>(
() -> ServerNetworking.generationRequestRateLimit.get(),
msg -> {
msg.sendResponse(new RateLimitedException("Full data request rate limit: " + ServerPlayerState.this.config.getGenerationRequestRateLimit()));
}
);
public final SupplierBasedRateAndConcurrencyLimiter<FullDataSourceRequestMessage> syncOnLoginRateLimiter = new SupplierBasedRateAndConcurrencyLimiter<>(
() -> ServerNetworking.syncOnLoginRateLimit.get(),
msg -> {
msg.sendResponse(new RateLimitedException("Sync on login rate limit: " + ServerPlayerState.this.config.getSyncOnLoginRateLimit()));
}
);
}
}
@@ -0,0 +1,192 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network;
import io.netty.buffer.ByteBuf;
import org.jetbrains.annotations.Contract;
import javax.annotation.Nullable;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.*;
public interface INetworkObject
{
void encode(ByteBuf out);
void decode(ByteBuf in);
static <T extends INetworkObject> T decodeToInstance(T obj, ByteBuf inputByteBuf)
{
obj.decode(inputByteBuf);
return obj;
}
@Contract("_, null -> false; _, !null -> true")
default boolean writeOptional(ByteBuf outputByteBuf, Object value)
{
boolean isNull = value != null;
outputByteBuf.writeBoolean(isNull);
return isNull;
}
@Nullable
default <T> T readOptional(ByteBuf inputByteBuf, Supplier<T> decoder)
{
return inputByteBuf.readBoolean()
? decoder.get()
: null;
}
default void readOptional(ByteBuf inputByteBuf, Runnable decoder)
{
if (inputByteBuf.readBoolean())
{
decoder.run();
}
}
default void writeString(String inputString, ByteBuf outputByteBuf)
{
INetworkObject.writeStringStatic(inputString, outputByteBuf);
}
default String readString(ByteBuf inputByteBuf)
{
return INetworkObject.readStringStatic(inputByteBuf);
}
static void writeStringStatic(String inputString, ByteBuf outputByteBuf)
{
byte[] bytes = inputString.getBytes(StandardCharsets.UTF_8);
outputByteBuf.writeShort(bytes.length);
outputByteBuf.writeBytes(bytes);
}
static String readStringStatic(ByteBuf inputByteBuf)
{
int length = inputByteBuf.readUnsignedShort();
return inputByteBuf.readSlice(length).toString(StandardCharsets.UTF_8);
}
default <T> void writeCollection(ByteBuf outputByteBuf, Collection<T> collection)
{
outputByteBuf.writeInt(collection.size());
this.writeFixedLengthCollection(outputByteBuf, collection);
}
default <T> void writeFixedLengthCollection(ByteBuf outputByteBuf, Collection<T> collection)
{
for (T item : collection)
{
Codec codec = Codec.getCodec(item.getClass());
codec.encode.accept(item, outputByteBuf);
}
}
default <T> void readCollection(ByteBuf inputByteBuf, Collection<T> collection, Supplier<T> innerValueConstructor)
{
int size = inputByteBuf.readInt();
Codec codec = null;
for (int i = 0; i < size; i++)
{
T item = innerValueConstructor.get();
if (codec == null)
{
codec = Codec.getCodec(item.getClass());
}
//noinspection unchecked
item = (T) codec.decode.apply(item, inputByteBuf);
collection.add(item);
}
}
default <K, V> void readMap(ByteBuf inputByteBuf, Map<K, V> map, Supplier<K> keySupplier, Supplier<V> valueSupplier)
{
ArrayList<Map.Entry<K, V>> entryList = new ArrayList<>();
this.readCollection(inputByteBuf, entryList, () -> new AbstractMap.SimpleEntry<>(keySupplier.get(), valueSupplier.get()));
for (Map.Entry<K, V> entry : entryList)
{
map.put(entry.getKey(), entry.getValue());
}
}
/**
* Should only be used for non-editable classes;
* otherwise, you may want to implement {@link INetworkObject} and use its methods where applicable.
*/
class Codec
{
private static final ConcurrentMap<Class<?>, Codec> codecMap = new ConcurrentHashMap<Class<?>, Codec>()
{{
// Primitives must be added manually here
this.put(Integer.class, new Codec((obj, out) -> out.writeInt((int)obj), (obj, in) -> in.readInt()));
this.put(Boolean.class, new Codec((obj, out) -> out.writeBoolean((boolean) obj), (obj, in) -> in.readBoolean()));
this.put(String.class, new Codec((obj, out) -> INetworkObject.writeStringStatic((String) obj, out), (obj, in) -> INetworkObject.readStringStatic(in)));
this.put(INetworkObject.class, new Codec(INetworkObject::encode, INetworkObject::decodeToInstance));
this.put(Map.Entry.class, new Codec(
(obj, out) -> {
Map.Entry<?, ?> entry = (Entry<?, ?>) obj;
getCodec(entry.getKey().getClass()).encode.accept(entry.getKey(), out);
getCodec(entry.getValue().getClass()).encode.accept(entry.getValue(), out);
},
(obj, in) -> {
Map.Entry<?, ?> entry = (Entry<?, ?>) obj;
return new SimpleEntry<>(
getCodec(entry.getKey().getClass()).decode.apply(entry.getKey(), in),
getCodec(entry.getValue().getClass()).decode.apply(entry.getValue(), in)
);
}
));
}};
public final BiConsumer<Object, ByteBuf> encode;
public final BiFunction<Object, ByteBuf, Object> decode;
@SuppressWarnings("unchecked")
public <T> Codec(BiConsumer<T, ByteBuf> encode, BiFunction<T, ByteBuf, T> decode)
{
this.encode = (BiConsumer<Object, ByteBuf>) encode;
this.decode = (BiFunction<Object, ByteBuf, Object>) decode;
}
public static <T> Codec getCodec(Class<T> clazz)
{
return codecMap.computeIfAbsent(clazz, ignored -> {
for (Map.Entry<Class<?>, Codec> entry : codecMap.entrySet())
{
if (entry.getKey().isAssignableFrom(clazz))
{
return entry.getValue();
}
}
throw new AssertionError("Class has no compatible codec: " + clazz.getSimpleName());
});
}
}
}
@@ -1,175 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.CloseMessage;
import com.seibel.distanthorizons.core.network.messages.CloseReasonMessage;
import com.seibel.distanthorizons.core.network.messages.HelloMessage;
import com.seibel.distanthorizons.core.network.protocol.NetworkChannelInitializer;
//import io.netty.bootstrap.Bootstrap;
//import io.netty.channel.Channel;
//import io.netty.channel.ChannelFuture;
//import io.netty.channel.ChannelOption;
//import io.netty.channel.EventLoopGroup;
//import io.netty.channel.nio.NioEventLoopGroup;
//import io.netty.channel.socket.nio.NioSocketChannel;
import org.apache.logging.log4j.Logger;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
public class NetworkClient //extends NetworkEventSource implements AutoCloseable
{
// private static final Logger LOGGER = DhLoggerBuilder.getLogger();
//
// private enum EConnectionState
// {
// OPEN,
// RECONNECT,
// RECONNECT_FORCE,
// CLOSE_WAIT,
// CLOSED
// }
//
// private static final int FAILURE_RECONNECT_DELAY_SEC = 5;
// private static final int FAILURE_RECONNECT_ATTEMPTS = 3;
//
// // TODO move to payload of some sort
// private final InetSocketAddress address;
//
// private final EventLoopGroup workerGroup = new NioEventLoopGroup();
// private final Bootstrap clientBootstrap = new Bootstrap()
// .group(this.workerGroup)
// .channel(NioSocketChannel.class)
// .option(ChannelOption.SO_KEEPALIVE, true)
// .handler(new NetworkChannelInitializer(this.messageHandler));
//
// private EConnectionState connectionState;
// private Channel channel;
// private int reconnectAttempts = FAILURE_RECONNECT_ATTEMPTS;
//
//
//
// public NetworkClient(String host, int port)
// {
// this.address = new InetSocketAddress(host, port);
//
// this.registerHandlers();
// this.connect();
// }
//
// private void registerHandlers()
// {
// this.registerHandler(HelloMessage.class, (helloMessage, channelContext) ->
// {
// LOGGER.info("Connected to server: "+channelContext.channel().remoteAddress());
// });
//
// this.registerHandler(CloseReasonMessage.class, (closeReasonMessage, channelContext) ->
// {
// LOGGER.info(closeReasonMessage.reason);
// this.connectionState = EConnectionState.CLOSE_WAIT;
// });
//
// this.registerHandler(CloseMessage.class, (closeMessage, channelContext) ->
// {
// LOGGER.info("Disconnected from server: "+channelContext.channel().remoteAddress());
// if (this.connectionState == EConnectionState.CLOSE_WAIT)
// {
// this.close();
// }
// });
// }
//
// private void connect()
// {
// LOGGER.info("Connecting to server: "+this.address);
// this.connectionState = EConnectionState.OPEN;
//
// // FIXME sometimes this causes the MC connection to crash
// // this might happen if the URL can't be converted to a IP (IE UnknownHostException)
// ChannelFuture connectFuture = this.clientBootstrap.connect(this.address);
// connectFuture.addListener((ChannelFuture channelFuture) ->
// {
// if (!channelFuture.isSuccess())
// {
// LOGGER.warn("Connection failed: "+channelFuture.cause());
// return;
// }
//
// this.channel.writeAndFlush(new HelloMessage());
// });
//
// this.channel = connectFuture.channel();
// this. channel.closeFuture().addListener((ChannelFuture channelFuture) ->
// {
// switch (this.connectionState)
// {
// case OPEN:
// this.reconnectAttempts--;
// LOGGER.info("Reconnection attempts left: ["+this.reconnectAttempts+"] of ["+FAILURE_RECONNECT_ATTEMPTS+"].");
// if (this.reconnectAttempts == 0)
// {
// this.connectionState = EConnectionState.CLOSE_WAIT;
// return;
// }
//
// this.connectionState = EConnectionState.RECONNECT;
// this.workerGroup.schedule(this::connect, FAILURE_RECONNECT_DELAY_SEC, TimeUnit.SECONDS);
// break;
//
// case RECONNECT_FORCE:
// LOGGER.info("Reconnecting forcefully.");
// this.reconnectAttempts = FAILURE_RECONNECT_ATTEMPTS;
//
// this.connectionState = EConnectionState.RECONNECT;
// this.workerGroup.schedule(this::connect, 0, TimeUnit.SECONDS);
// break;
// }
// });
// }
//
// /** Kills the current connection, triggering auto-reconnection immediately. */
// public void reconnect()
// {
// this.connectionState = EConnectionState.RECONNECT_FORCE;
// this.channel.disconnect();
// }
//
// @Override
// public void close()
// {
// if (this.closeReason != null)
// {
// LOGGER.error(this.closeReason);
// }
//
// if (this.connectionState == EConnectionState.CLOSED)
// {
// return;
// }
//
// this.connectionState = EConnectionState.CLOSED;
// this.workerGroup.shutdownGracefully().syncUninterruptibly();
// this.channel.close().syncUninterruptibly();
// }
}
@@ -1,82 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.AckMessage;
import com.seibel.distanthorizons.core.network.messages.HelloMessage;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.MessageHandler;
import com.seibel.distanthorizons.coreapi.ModInfo;
//import io.netty.channel.ChannelHandlerContext;
import org.apache.logging.log4j.Logger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public abstract class NetworkEventSource implements AutoCloseable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
protected final MessageHandler messageHandler = new MessageHandler();
protected String closeReason = null;
// public NetworkEventSource()
// {
// this.registerHandler(HelloMessage.class, (helloMessage, channelContext) ->
// {
// if (helloMessage.version != ModInfo.PROTOCOL_VERSION)
// {
// try
// {
// String closeReason = "Ignoring message from channel ["+channelContext.name()+"], due to version mismatch. Expected version: ["+ModInfo.PROTOCOL_VERSION+"], received version: ["+helloMessage.version+"].";
// LOGGER.info(closeReason);
// this.close(closeReason);
// }
// catch (Exception e)
// {
// throw new RuntimeException(e);
// }
// }
// });
// }
//
// public <T extends INetworkMessage> void registerHandler(Class<T> clazz, BiConsumer<T, ChannelHandlerContext> handler) { this.messageHandler.registerHandler(clazz, handler); }
//
// public <T extends INetworkMessage> void registerAckHandler(Class<T> clazz, Consumer<ChannelHandlerContext> handler)
// {
// this.messageHandler.registerHandler(AckMessage.class, (ackMessage, channelContext) ->
// {
// if (ackMessage.messageType == clazz)
// {
// handler.accept(channelContext);
// }
// });
// }
//
// public void close(String reason) throws Exception
// {
// this.closeReason = reason;
// this.close();
// }
}
@@ -1,122 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.CloseReasonMessage;
import com.seibel.distanthorizons.core.network.messages.CloseMessage;
import com.seibel.distanthorizons.core.network.messages.HelloMessage;
import com.seibel.distanthorizons.core.network.protocol.NetworkChannelInitializer;
//import io.netty.bootstrap.ServerBootstrap;
//import io.netty.channel.*;
//import io.netty.channel.nio.NioEventLoopGroup;
//import io.netty.channel.socket.nio.NioServerSocketChannel;
//import io.netty.handler.logging.LogLevel;
//import io.netty.handler.logging.LoggingHandler;
import org.apache.logging.log4j.Logger;
public class NetworkServer //extends NetworkEventSource implements AutoCloseable
{
// private static final Logger LOGGER = DhLoggerBuilder.getLogger();
//
// // TODO move to the config
// private final int port;
//
// private final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// private final EventLoopGroup workerGroup = new NioEventLoopGroup();
// private Channel channel;
// private boolean isClosed = false;
//
//
//
// public NetworkServer(int port)
// {
// this.port = port;
//
// LOGGER.info("Starting server on port "+port);
// this.registerHandlers();
// this.bind();
// }
//
// private void registerHandlers()
// {
// this.registerHandler(HelloMessage.class, (helloMessage, channelContext) ->
// {
// LOGGER.info("Client connected: "+channelContext.channel().remoteAddress());
// channelContext.channel().writeAndFlush(new HelloMessage());
// });
//
// this.registerHandler(CloseMessage.class, (closeMessage, channelContext) ->
// {
// LOGGER.info("Client disconnected: "+channelContext.channel().remoteAddress());
// });
// }
//
// private void bind()
// {
// ServerBootstrap bootstrap = new ServerBootstrap()
// .group(this.bossGroup, this.workerGroup)
// .channel(NioServerSocketChannel.class)
// .handler(new LoggingHandler(LogLevel.DEBUG))
// .childHandler(new NetworkChannelInitializer(this.messageHandler));
//
// ChannelFuture bindFuture = bootstrap.bind(this.port);
// bindFuture.addListener((ChannelFuture channelFuture) ->
// {
// if (!channelFuture.isSuccess())
// {
// throw new RuntimeException("Failed to bind: " + channelFuture.cause());
// }
//
// LOGGER.info("Server is started on port "+this.port);
// });
//
// this.channel = bindFuture.channel();
// this.channel.closeFuture().addListener(future -> this.close());
// }
//
// public void disconnectClient(ChannelHandlerContext ctx, String reason)
// {
// ctx.channel().config().setAutoRead(false);
// ctx.writeAndFlush(new CloseReasonMessage(reason))
// .addListener(ChannelFutureListener.CLOSE);
// }
//
// @Override
// public void close()
// {
// if (this.closeReason != null)
// {
// LOGGER.error(this.closeReason);
// }
//
// if (this.isClosed)
// {
// return;
// }
// this.isClosed = true;
//
// LOGGER.info("Shutting down the network server.");
// this.workerGroup.shutdownGracefully().syncUninterruptibly();
// this.bossGroup.shutdownGracefully().syncUninterruptibly();
// LOGGER.info("Network server has been closed.");
// }
}
@@ -0,0 +1,186 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.event;
import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.network.event.internal.InternalEvent;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import com.seibel.distanthorizons.core.network.messages.TrackableMessage;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
import com.seibel.distanthorizons.core.network.session.SessionClosedException;
import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage;
import com.seibel.distanthorizons.core.network.messages.requests.ExceptionMessage;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager;
import java.io.InvalidClassException;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.*;
import java.util.function.Consumer;
public abstract class NetworkEventSource
{
private static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
() -> Config.Client.Advanced.Logging.logNetworkEvent.get());
protected final ConcurrentMap<
Class<? extends NetworkMessage>,
ConcurrentMap<
NetworkEventSource,
Set<Consumer<NetworkMessage>>
>
> handlers = new ConcurrentHashMap<>();
private final ConcurrentMap<Long, FutureResponseData> pendingFutures = new ConcurrentHashMap<>();
private final Set<Long> cancelledFutures = Collections.newSetFromMap(CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.<Long, Boolean>build()
.asMap());
protected void handleMessage(NetworkMessage message)
{
boolean handled = false;
ConcurrentMap<NetworkEventSource, Set<Consumer<NetworkMessage>>> handlersByEventSource = this.handlers.get(message.getClass());
if (handlersByEventSource != null)
{
for (Set<Consumer<NetworkMessage>> handlerSet : handlersByEventSource.values())
{
for (Consumer<NetworkMessage> handler : handlerSet)
{
handled = true;
handler.accept(message);
}
}
}
if (message instanceof TrackableMessage)
{
TrackableMessage trackableMessage = (TrackableMessage) message;
FutureResponseData responseData = this.pendingFutures.get(trackableMessage.futureId);
if (responseData != null)
{
handled = true;
if (message instanceof ExceptionMessage)
{
responseData.future.completeExceptionally(((ExceptionMessage) message).exception);
}
else if (message.getClass() != responseData.responseClass)
{
responseData.future.completeExceptionally(new InvalidClassException("Response with invalid type: expected " + responseData.responseClass.getSimpleName() + ", got:" + message));
}
else
{
responseData.future.complete(trackableMessage);
}
}
else if (this.cancelledFutures.remove(trackableMessage.futureId))
{
handled = true;
}
}
if (!handled && ModInfo.IS_DEV_BUILD)
{
LOGGER.warn("Unhandled message: {}", message);
}
}
public abstract <T extends NetworkMessage> void registerHandler(Class<T> handlerClass, Consumer<T> handlerImplementation);
protected final <T extends NetworkMessage> void registerHandler(NetworkEventSource instance, Class<T> handlerClass, Consumer<T> handlerImplementation)
{
if (!InternalEvent.class.isAssignableFrom(handlerClass))
{
MessageRegistry.INSTANCE.getMessageId(handlerClass);
}
//noinspection unchecked
this.handlers.computeIfAbsent(handlerClass, missingHandlerClass -> new ConcurrentHashMap<>())
.computeIfAbsent(instance, _instance -> ConcurrentHashMap.newKeySet())
.add((Consumer<NetworkMessage>) handlerImplementation);
}
protected void removeAllHandlers(NetworkEventSource childInstance)
{
for (ConcurrentMap<NetworkEventSource, Set<Consumer<NetworkMessage>>> handlerMap : this.handlers.values())
{
handlerMap.remove(childInstance);
}
}
protected <TResponse extends TrackableMessage> CompletableFuture<TResponse> createRequest(TrackableMessage msg, Class<TResponse> responseClass)
{
CompletableFuture<TResponse> responseFuture = new CompletableFuture<>();
responseFuture.whenComplete((response, throwable) ->
{
if (throwable instanceof CancellationException)
{
this.cancelledFutures.add(msg.futureId);
msg.sendResponse(new CancelMessage());
}
if (!(throwable instanceof SessionClosedException))
{
this.pendingFutures.remove(msg.futureId);
}
});
this.pendingFutures.put(msg.futureId, new FutureResponseData(responseClass, responseFuture));
return responseFuture;
}
protected final void completeAllFuturesExceptionally(Throwable cause)
{
for (FutureResponseData responseData : this.pendingFutures.values())
{
responseData.future.completeExceptionally(cause);
}
}
public void close()
{
this.handlers.clear();
this.completeAllFuturesExceptionally(new SessionClosedException(this.getClass().getSimpleName() + " is closed."));
}
private static class FutureResponseData
{
public final Class<? extends TrackableMessage> responseClass;
public final CompletableFuture<TrackableMessage> future;
private <T extends TrackableMessage> FutureResponseData(Class<T> responseClass, CompletableFuture<T> future)
{
this.responseClass = responseClass;
//noinspection unchecked
this.future = (CompletableFuture<TrackableMessage>) future;
}
}
}
@@ -0,0 +1,62 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.event;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import java.util.function.Consumer;
/** Provides a way to register network message handlers which are expected to be removed later. */
public final class ScopedNetworkEventSource extends NetworkEventSource
{
public final NetworkEventSource parent;
private boolean isClosed = false;
private final Consumer<NetworkMessage> actualHandleMessageStable = this::handleMessage;
public ScopedNetworkEventSource(NetworkEventSource parent)
{
this.parent = parent;
}
@Override
public <T extends NetworkMessage> void registerHandler(Class<T> handlerClass, Consumer<T> handlerImplementation)
{
if (this.isClosed)
{
return;
}
//noinspection unchecked
this.parent.registerHandler(this, handlerClass, (Consumer<T>) this.actualHandleMessageStable);
super.registerHandler(this, handlerClass, handlerImplementation);
}
@Override
public void close()
{
this.isClosed = true;
this.parent.removeAllHandlers(this);
}
}
@@ -0,0 +1,9 @@
package com.seibel.distanthorizons.core.network.event.internal;
/**
* This event is used to indicate a disconnect.
*/
public class CloseEvent extends InternalEvent
{
}
@@ -0,0 +1,15 @@
package com.seibel.distanthorizons.core.network.event.internal;
/**
* This event is received instead of a message if its protocol version is incompatible with version the mod uses.
*/
public class IncompatibleMessageEvent extends InternalEvent
{
public final int protocolVersion;
public IncompatibleMessageEvent(int protocolVersion)
{
this.protocolVersion = protocolVersion;
}
}
@@ -0,0 +1,20 @@
package com.seibel.distanthorizons.core.network.event.internal;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import io.netty.buffer.ByteBuf;
public abstract class InternalEvent extends NetworkMessage
{
@Override
public void encode(ByteBuf out)
{
throw new UnsupportedOperationException(this.getClass().getSimpleName() + " is an internal event, and cannot be sent.");
}
@Override
public void decode(ByteBuf in)
{
throw new UnsupportedOperationException(this.getClass().getSimpleName() + " is an internal event, and cannot be received.");
}
}
@@ -0,0 +1,23 @@
package com.seibel.distanthorizons.core.network.event.internal;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import org.jetbrains.annotations.Nullable;
/**
* This event is used to indicate that encoding or decoding of a message threw an exception.
*/
public class ProtocolErrorEvent extends InternalEvent
{
public final Throwable reason;
@Nullable
public final NetworkMessage message;
public final boolean replyWithCloseReason;
public ProtocolErrorEvent(Throwable reason, @Nullable NetworkMessage message, boolean replyWithCloseReason)
{
this.reason = reason;
this.message = message;
this.replyWithCloseReason = replyWithCloseReason;
}
}
@@ -17,11 +17,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.protocol;
package com.seibel.distanthorizons.core.network.exceptions;
/** For now this is only used for constraining listeners */
public interface INetworkMessage extends INetworkObject
public class InvalidLevelException extends Exception
{
public InvalidLevelException(String message)
{
super(message);
}
}
@@ -17,22 +17,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages;
package com.seibel.distanthorizons.core.network.exceptions;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
import com.seibel.distanthorizons.coreapi.ModInfo;
//import io.netty.buffer.ByteBuf;
public class HelloMessage implements INetworkMessage
public class InvalidSectionPosException extends Exception
{
public int version = ModInfo.PROTOCOL_VERSION;
// @Override
// public void encode(ByteBuf out) { out.writeInt(this.version); }
//
// @Override
// public void decode(ByteBuf in) { this.version = in.readInt(); }
public InvalidSectionPosException(String message)
{
super(message);
}
}
@@ -17,14 +17,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.protocol;
package com.seibel.distanthorizons.core.network.exceptions;
/**
* CLIENT, <br>
* SERVER, <br>
*/
public enum EMessageHandlerSide
public class RateLimitedException extends Exception
{
CLIENT,
SERVER
public RateLimitedException(String message)
{
super(message);
}
}
@@ -0,0 +1,10 @@
package com.seibel.distanthorizons.core.network.exceptions;
public class RequestRejectedException extends Exception
{
public RequestRejectedException(String message)
{
super(message);
}
}
@@ -1,45 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.MessageRegistry;
//import io.netty.buffer.ByteBuf;
/**
* Simple empty response message.
* This message is not sent automatically.
*/
public class AckMessage implements INetworkMessage
{
public Class<? extends INetworkMessage> messageType;
public AckMessage() { }
public AckMessage(Class<? extends INetworkMessage> messageType) { this.messageType = messageType; }
// @Override
// public void encode(ByteBuf out) { out.writeInt(MessageRegistry.INSTANCE.getMessageId(this.messageType)); }
//
// @Override
// public void decode(ByteBuf in) { this.messageType = MessageRegistry.INSTANCE.getMessageClassById(in.readInt()); }
}
@@ -1,38 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
//import io.netty.buffer.ByteBuf;
/**
* This is not a "real" message, and only used to indicate a disconnection.
* To send a "disconnect reason" message, use {@link CloseReasonMessage}.
*/
public class CloseMessage implements INetworkMessage
{
// @Override
// public void encode(ByteBuf out) { throw new UnsupportedOperationException("CloseMessage is not a real message, and must not be sent."); }
//
// @Override
// public void decode(ByteBuf in) { throw new UnsupportedOperationException("CloseMessage is not a real message, and must not be received."); }
}
@@ -0,0 +1,24 @@
package com.seibel.distanthorizons.core.network.messages;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
public interface ILevelRelatedMessage
{
String getLevelName();
/**
* Checks whether the message's level matches the given level.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
default boolean isSameLevelAs(ILevelWrapper levelWrapper)
{
if (levelWrapper instanceof IServerLevelWrapper)
{
return this.getLevelName().equals(((IServerLevelWrapper) levelWrapper).getKeyedLevelDimensionName());
}
return this.getLevelName().equals(levelWrapper.getDimensionName());
}
}
@@ -0,0 +1,119 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.seibel.distanthorizons.core.network.messages.base.CodecCrashMessage;
import com.seibel.distanthorizons.core.network.messages.base.CurrentLevelKeyMessage;
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataChunkMessage;
import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage;
import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage;
import com.seibel.distanthorizons.core.network.messages.requests.ExceptionMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage;
import com.seibel.distanthorizons.coreapi.ModInfo;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class MessageRegistry
{
public static final boolean DEBUG_ENABLE_CODEC_CRASH_MESSAGE = ModInfo.IS_DEV_BUILD;
public static final MessageRegistry INSTANCE = new MessageRegistry();
private final Map<Integer, Supplier<? extends NetworkMessage>> idToSupplier = new HashMap<>();
private final BiMap<Class<? extends NetworkMessage>, Integer> classToId = HashBiMap.create();
private MessageRegistry()
{
// Note: Messages must have parameterless constructors
// When the communication is about to be stopped, either side can send this message
// There may be messages after this, but they should be ignored if it's possible
this.registerMessage(CloseReasonMessage.class, CloseReasonMessage::new);
// Level keys
this.registerMessage(CurrentLevelKeyMessage.class, CurrentLevelKeyMessage::new);
// Config (for full DH support)
this.registerMessage(SessionConfigMessage.class, SessionConfigMessage::new);
// Requests
this.registerMessage(CancelMessage.class, CancelMessage::new);
this.registerMessage(ExceptionMessage.class, ExceptionMessage::new);
// Full data requests & updates
this.registerMessage(FullDataSourceRequestMessage.class, FullDataSourceRequestMessage::new);
this.registerMessage(FullDataSourceResponseMessage.class, FullDataSourceResponseMessage::new);
this.registerMessage(FullDataPartialUpdateMessage.class, FullDataPartialUpdateMessage::new);
this.registerMessage(FullDataChunkMessage.class, FullDataChunkMessage::new);
// Debug messages are always last, and not included into release builds.
if (DEBUG_ENABLE_CODEC_CRASH_MESSAGE)
{
this.registerMessage(CodecCrashMessage.class, CodecCrashMessage::new);
}
}
public <T extends NetworkMessage> void registerMessage(Class<T> clazz, Supplier<T> supplier)
{
int id = this.idToSupplier.size() + 1;
this.idToSupplier.put(id, supplier);
this.classToId.put(clazz, id);
}
public NetworkMessage createMessage(int messageId) throws IllegalArgumentException
{
try
{
return this.idToSupplier.get(messageId).get();
}
catch (NullPointerException e)
{
throw new IllegalArgumentException("Invalid message ID: " + messageId);
}
}
public int getMessageId(NetworkMessage message)
{
return this.getMessageId(message.getClass());
}
public int getMessageId(Class<? extends NetworkMessage> messageClass)
{
try
{
return this.classToId.get(messageClass);
}
catch (NullPointerException e)
{
throw new IllegalArgumentException("Message does not have ID assigned to it: " + messageClass.getSimpleName());
}
}
}
@@ -0,0 +1,35 @@
package com.seibel.distanthorizons.core.network.messages;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.network.INetworkObject;
import com.seibel.distanthorizons.core.network.session.Session;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
public abstract class NetworkMessage implements INetworkObject
{
private Session session = null;
public IServerPlayerWrapper serverPlayer() { return this.session.serverPlayer; }
public Session getSession()
{
return this.session;
}
public void setSession(Session session)
{
this.session = session;
}
@Override
public String toString()
{
return this.toStringHelper().toString();
}
public MoreObjects.ToStringHelper toStringHelper()
{
return MoreObjects.toStringHelper(this);
}
}
@@ -1,46 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
//import io.netty.buffer.ByteBuf;
import java.util.UUID;
public class PlayerUUIDMessage implements INetworkMessage
{
public UUID playerUUID;
public PlayerUUIDMessage() { }
public PlayerUUIDMessage(UUID playerUUID) { this.playerUUID = playerUUID; }
// @Override
// public void encode(ByteBuf out)
// {
// out.writeLong(this.playerUUID.getMostSignificantBits());
// out.writeLong(this.playerUUID.getLeastSignificantBits());
// }
//
// @Override
// public void decode(ByteBuf in) { this.playerUUID = new UUID(in.readLong(), in.readLong()); }
}
@@ -1,42 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages;
import com.seibel.distanthorizons.core.network.protocol.INetworkObject;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
import com.seibel.distanthorizons.core.network.objects.RemotePlayer;
//import io.netty.buffer.ByteBuf;
public class RemotePlayerConfigMessage implements INetworkMessage
{
public RemotePlayer.Payload payload;
public RemotePlayerConfigMessage() { }
public RemotePlayerConfigMessage(RemotePlayer.Payload payload) { this.payload = payload; }
// @Override
// public void encode(ByteBuf out) { this.payload.encode(out); }
//
// @Override
// public void decode(ByteBuf in) { this.payload = INetworkObject.decode(new RemotePlayer.Payload(), in); }
}
@@ -0,0 +1,98 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.network.messages.requests.ExceptionMessage;
import com.seibel.distanthorizons.core.network.session.Session;
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
import io.netty.buffer.ByteBuf;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class TrackableMessage extends NetworkMessage
{
private static final AtomicInteger lastId = new AtomicInteger();
// 32 bits - Session ID (not transmitted)
// 1 bit - Requesting side (client - 0, server - 1)
// 31 bits - Request ID
public long futureId = lastId.getAndIncrement()
| ((Objects.requireNonNull(SharedApi.getEnvironment()) == EWorldEnvironment.Server_Only ? 1 : 0) << 31);
@Override
public void setSession(Session session)
{
super.setSession(session);
this.futureId |= (long) session.id << 32;
}
public void sendResponse(TrackableMessage responseMessage)
{
responseMessage.futureId = this.futureId;
this.getSession().sendMessage(responseMessage);
}
public void sendResponse(Exception e)
{
this.sendResponse(new ExceptionMessage(e));
}
@Override
public final void encode(ByteBuf out)
{
try
{
out.writeInt((int) this.futureId);
this.encode0(out);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
@Override
public final void decode(ByteBuf in)
{
try
{
this.futureId = in.readInt();
this.decode0(in);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
protected abstract void encode0(ByteBuf out) throws Exception;
protected abstract void decode0(ByteBuf in) throws Exception;
@Override public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("futureId", this.futureId);
}
}
@@ -17,25 +17,34 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages;
package com.seibel.distanthorizons.core.network.messages.base;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
import com.seibel.distanthorizons.core.network.protocol.INetworkObject;
//import io.netty.buffer.ByteBuf;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import io.netty.buffer.ByteBuf;
public class CloseReasonMessage implements INetworkMessage
public class CloseReasonMessage extends NetworkMessage
{
public String reason;
public CloseReasonMessage() { }
public CloseReasonMessage(String reason) { this.reason = reason; }
// @Override
// public void encode(ByteBuf out) { INetworkObject.encodeString(this.reason, out); }
//
// @Override
// public void decode(ByteBuf in) { this.reason = INetworkObject.decodeString(in); }
@Override
public void encode(ByteBuf out)
{
this.writeString(this.reason, out);
}
@Override
public void decode(ByteBuf in) { this.reason = this.readString(in); }
}
@Override
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("reason", this.reason);
}
}
@@ -0,0 +1,62 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages.base;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import io.netty.buffer.ByteBuf;
public class CodecCrashMessage extends NetworkMessage
{
public ECrashPhase crashPhase;
public CodecCrashMessage() { }
public CodecCrashMessage(ECrashPhase crashPhase) { this.crashPhase = crashPhase; }
@Override
public void encode(ByteBuf out)
{
if (this.crashPhase == ECrashPhase.ENCODE)
{
throw new RuntimeException("encode force crash");
}
}
@Override
public void decode(ByteBuf in)
{
throw new RuntimeException("decode force crash");
}
@Override
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("crashPhase", this.crashPhase);
}
public enum ECrashPhase
{
ENCODE,
DECODE
}
}
@@ -0,0 +1,37 @@
package com.seibel.distanthorizons.core.network.messages.base;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import io.netty.buffer.ByteBuf;
public class CurrentLevelKeyMessage extends NetworkMessage
{
public String levelKey;
public CurrentLevelKeyMessage() { }
public CurrentLevelKeyMessage(String levelKey)
{
this.levelKey = levelKey;
}
@Override
public void encode(ByteBuf out)
{
this.writeString(this.levelKey, out);
}
@Override
public void decode(ByteBuf in)
{
this.levelKey = this.readString(in);
}
@Override
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("levelKey", this.levelKey);
}
}
@@ -0,0 +1,49 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages.base;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
import com.seibel.distanthorizons.core.network.INetworkObject;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import io.netty.buffer.ByteBuf;
public class SessionConfigMessage extends NetworkMessage
{
public SessionConfig config;
public SessionConfigMessage() { }
public SessionConfigMessage(SessionConfig config) { this.config = config; }
@Override
public void encode(ByteBuf out) { this.config.encode(out); }
@Override
public void decode(ByteBuf in) { this.config = INetworkObject.decodeToInstance(new SessionConfig(), in); }
@Override
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("config", this.config);
}
}
@@ -0,0 +1,74 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages.fullData;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import io.netty.buffer.ByteBuf;
public class FullDataChunkMessage extends NetworkMessage
{
public int bufferId;
public ByteBuf buffer;
public boolean isFirst;
public FullDataChunkMessage() { }
public FullDataChunkMessage(int bufferId, boolean isFirst, ByteBuf buffer)
{
this.bufferId = bufferId;
this.buffer = buffer;
this.isFirst = isFirst;
}
@Override
public void encode(ByteBuf out)
{
out.writeInt(this.bufferId);
out.writeInt(this.buffer.writerIndex());
out.writeBytes(this.buffer.readerIndex(0));
out.writeBoolean(this.isFirst);
}
@Override
public void decode(ByteBuf in)
{
this.bufferId = in.readInt();
int bufferSize = in.readInt();
this.buffer = in.readBytes(bufferSize);
this.isFirst = in.readBoolean();
}
@Override
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("bufferId", this.bufferId)
.add("buffer", this.buffer)
.add("isFirst", this.isFirst);
}
}
@@ -0,0 +1,69 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages.fullData;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.network.INetworkObject;
import com.seibel.distanthorizons.core.network.messages.ILevelRelatedMessage;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import io.netty.buffer.ByteBuf;
public class FullDataPartialUpdateMessage extends NetworkMessage implements ILevelRelatedMessage
{
private String levelName;
@Override
public String getLevelName() { return this.levelName; }
public FullDataPayload payload;
public FullDataPartialUpdateMessage() { }
public FullDataPartialUpdateMessage(IServerLevelWrapper level, FullDataPayload payload)
{
this.levelName = level.getKeyedLevelDimensionName();
this.payload = payload;
}
@Override
public void encode(ByteBuf out)
{
this.writeString(this.levelName, out);
this.payload.encode(out);
}
@Override
public void decode(ByteBuf in)
{
this.levelName = this.readString(in);
this.payload = INetworkObject.decodeToInstance(new FullDataPayload(), in);
}
@Override
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("levelName", this.levelName)
.add("payload", this.payload);
}
}
@@ -0,0 +1,104 @@
package com.seibel.distanthorizons.core.network.messages.fullData;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.network.INetworkObject;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.TimerUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Objects;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
public class FullDataPayload implements INetworkObject
{
private static final AtomicInteger lastBufferId = new AtomicInteger();
// Reference counting is unreliable here for some reason so this is a "fix"
private static final Timer bufferCleanupTimer = TimerUtil.CreateTimer("FullDataBufferCleanupTimer");
public int dtoBufferId;
public ByteBuf dtoBuffer;
public FullDataPayload() { }
public FullDataPayload(@NotNull FullDataSourceV2 fullDataSource)
{
Objects.requireNonNull(fullDataSource);
this.dtoBufferId = lastBufferId.getAndIncrement();
try
{
EDhApiDataCompressionMode compressionMode = Config.Client.Advanced.LodBuilding.dataCompression.get();
FullDataSourceV2DTO dataSourceDto = FullDataSourceV2DTO.CreateFromDataSource(fullDataSource, compressionMode);
this.dtoBuffer = ByteBufAllocator.DEFAULT.buffer();
dataSourceDto.encode(this.dtoBuffer);
bufferCleanupTimer.schedule(new TimerTask()
{
@Override
public void run()
{
FullDataPayload.this.dtoBuffer.release();
}
}, 5000L);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public void acceptInChunkMessages(int chunkSize, Consumer<FullDataChunkMessage> chunkMessageConsumer)
{
for (int chunkNum = 0; ; chunkNum++)
{
int offset = chunkNum * chunkSize;
int actualChunkSize = Math.min(this.dtoBuffer.writerIndex() - offset, chunkSize);
if (actualChunkSize <= 0)
{
break;
}
FullDataChunkMessage chunk = new FullDataChunkMessage(this.dtoBufferId, chunkNum == 0, this.dtoBuffer.slice(offset, actualChunkSize));
chunkMessageConsumer.accept(chunk);
}
}
@Override
public void encode(ByteBuf out)
{
out.writeInt(this.dtoBufferId);
}
@Override
public void decode(ByteBuf in)
{
this.dtoBufferId = in.readInt();
}
@Override
public String toString()
{
return MoreObjects.toStringHelper(this)
.add("dtoBufferId", this.dtoBufferId)
.add("dtoBuffer", this.dtoBuffer)
.toString();
}
}
@@ -0,0 +1,80 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages.fullData;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.network.messages.ILevelRelatedMessage;
import com.seibel.distanthorizons.core.network.messages.TrackableMessage;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import io.netty.buffer.ByteBuf;
import javax.annotation.Nullable;
public class FullDataSourceRequestMessage extends TrackableMessage implements ILevelRelatedMessage
{
private String levelName;
public long sectionPos;
/** Only present when requesting for changes. */
@Nullable
public Long clientTimestamp;
@Override
public String getLevelName() { return this.levelName; }
public FullDataSourceRequestMessage() {}
public FullDataSourceRequestMessage(ILevelWrapper levelWrapper, long sectionPos, @Nullable Long clientTimestamp)
{
this.levelName = levelWrapper.getDimensionName();
this.sectionPos = sectionPos;
this.clientTimestamp = clientTimestamp;
}
@Override
public void encode0(ByteBuf out)
{
this.writeString(this.levelName, out);
out.writeLong(this.sectionPos);
if (this.writeOptional(out, this.clientTimestamp))
{
out.writeLong(this.clientTimestamp);
}
}
@Override
public void decode0(ByteBuf in)
{
this.levelName = this.readString(in);
this.sectionPos = in.readLong();
this.clientTimestamp = this.readOptional(in, in::readLong);
}
@Override
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("levelName", this.levelName)
.add("sectionPos", this.sectionPos)
.add("clientTimestamp", this.clientTimestamp);
}
}
@@ -0,0 +1,70 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages.fullData;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.network.INetworkObject;
import com.seibel.distanthorizons.core.network.messages.TrackableMessage;
import io.netty.buffer.ByteBuf;
import org.jetbrains.annotations.Nullable;
/**
* Response message, containing the requested full data source,
* or nothing if requested in updates-only mode and the data was not updated.
*/
public class FullDataSourceResponseMessage extends TrackableMessage
{
@Nullable
public FullDataPayload payload;
public FullDataSourceResponseMessage() { }
public FullDataSourceResponseMessage(@Nullable FullDataPayload payload)
{
if (payload != null)
{
this.payload = payload;
}
}
@Override
public void encode0(ByteBuf out)
{
if (this.writeOptional(out, this.payload))
{
this.payload.encode(out);
}
}
@Override
public void decode0(ByteBuf in)
{
this.payload = this.readOptional(in, () -> INetworkObject.decodeToInstance(new FullDataPayload(), in));
}
@Override
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("payload", this.payload);
}
}
@@ -17,24 +17,22 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages;
package com.seibel.distanthorizons.core.network.messages.requests;
import com.seibel.distanthorizons.core.network.protocol.INetworkMessage;
//import io.netty.buffer.ByteBuf;
import com.seibel.distanthorizons.core.network.messages.TrackableMessage;
import io.netty.buffer.ByteBuf;
public class RequestChunksMessage implements INetworkMessage
public class CancelMessage extends TrackableMessage
{
// @Override
// public void encode(ByteBuf out)
// {
//
// }
//
// @Override
// public void decode(ByteBuf in)
// {
//
// }
public CancelMessage() { }
}
@Override
public void encode0(ByteBuf out)
{
}
@Override
public void decode0(ByteBuf in)
{
}
}
@@ -0,0 +1,74 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages.requests;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException;
import com.seibel.distanthorizons.core.network.exceptions.InvalidSectionPosException;
import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException;
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
import com.seibel.distanthorizons.core.network.messages.TrackableMessage;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.List;
// TODO appears to be useless yelling at user
public class ExceptionMessage extends TrackableMessage
{
private static final List<Class<? extends Exception>> exceptionMap = new ArrayList<Class<? extends Exception>>()
{{
// All exceptions here must include constructor: (String)
this.add(RateLimitedException.class);
this.add(InvalidLevelException.class);
this.add(InvalidSectionPosException.class);
this.add(RequestRejectedException.class);
}};
public Exception exception;
public ExceptionMessage() { }
public ExceptionMessage(Exception exception)
{
this.exception = exception;
}
@Override protected void encode0(ByteBuf out)
{
out.writeInt(exceptionMap.indexOf(this.exception.getClass()));
this.writeString(this.exception.getMessage(), out);
}
@Override protected void decode0(ByteBuf in) throws Exception
{
int id = in.readInt();
String message = this.readString(in);
this.exception = exceptionMap.get(id).getDeclaredConstructor(String.class).newInstance(message);
}
@Override
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("exception", this.exception);
}
}
@@ -1,53 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.objects;
import com.seibel.distanthorizons.core.network.protocol.INetworkObject;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
//import io.netty.buffer.ByteBuf;
//import io.netty.channel.ChannelHandlerContext;
public class RemotePlayer
{
public IServerPlayerWrapper serverPlayer;
public Payload payload;
// public ChannelHandlerContext channelContext;
public RemotePlayer(IServerPlayerWrapper serverPlayer) { this.serverPlayer = serverPlayer; }
public static class Payload implements INetworkObject
{
// TODO Replace this example with useful fields,
// this should include any information the server needs to know about the connected client
public int renderDistance;
// @Override
// public void encode(ByteBuf out) { out.writeInt(this.renderDistance); }
//
// @Override
// public void decode(ByteBuf in) { this.renderDistance = in.readInt(); }
}
}
@@ -1,50 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.protocol;
//import io.netty.buffer.ByteBuf;
import java.nio.charset.StandardCharsets;
public interface INetworkObject
{
// void encode(ByteBuf out);
//
// void decode(ByteBuf in);
//
// static <T extends INetworkObject> T decode(T obj, ByteBuf inputByteBuf)
// {
// obj.decode(inputByteBuf);
// return obj;
// }
//
// static void encodeString(String inputString, ByteBuf outputByteBuf)
// {
// outputByteBuf.writeShort(inputString.length());
// outputByteBuf.writeBytes(inputString.getBytes(StandardCharsets.UTF_8));
// }
//
// static String decodeString(ByteBuf inputByteBuf)
// {
// int length = inputByteBuf.readShort();
// return inputByteBuf.readBytes(length).toString(StandardCharsets.UTF_8);
// }
}
@@ -1,37 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.protocol;
//import io.netty.buffer.ByteBuf;
//import io.netty.channel.ChannelHandlerContext;
//import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class MessageDecoder //extends ByteToMessageDecoder
{
// @Override
// protected void decode(ChannelHandlerContext channelContext, ByteBuf inputByteBuf, List<Object> outputDecodedObjectList)
// {
// INetworkMessage message = MessageRegistry.INSTANCE.createMessage(inputByteBuf.readShort());
// outputDecodedObjectList.add(INetworkObject.decode(message, inputByteBuf));
// }
}
@@ -1,35 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.protocol;
//import io.netty.buffer.ByteBuf;
//import io.netty.channel.ChannelHandlerContext;
//import io.netty.handler.codec.MessageToByteEncoder;
public class MessageEncoder //extends MessageToByteEncoder<INetworkMessage>
{
// @Override
// protected void encode(ChannelHandlerContext channelContext, INetworkMessage message, ByteBuf outputByteBuf) throws IllegalArgumentException
// {
// outputByteBuf.writeShort(MessageRegistry.INSTANCE.getMessageId(message));
// message.encode(outputByteBuf);
// }
}
@@ -1,72 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.protocol;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.CloseMessage;
//import io.netty.channel.ChannelHandler;
//import io.netty.channel.ChannelHandlerContext;
//import io.netty.channel.SimpleChannelInboundHandler;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
//@ChannelHandler.Sharable
public class MessageHandler //extends SimpleChannelInboundHandler<INetworkMessage>
{
// private static final Logger LOGGER = DhLoggerBuilder.getLogger();
//
// private final Map<Class<? extends INetworkMessage>, List<BiConsumer<INetworkMessage, ChannelHandlerContext>>> handlers = new HashMap<>();
//
//
//
// public <T extends INetworkMessage> void registerHandler(Class<T> handlerClass, BiConsumer<T, ChannelHandlerContext> handlerImplementation)
// {
// this.handlers.computeIfAbsent(handlerClass, missingHandlerClass -> new LinkedList<>())
// .add((BiConsumer<INetworkMessage, ChannelHandlerContext>) handlerImplementation);
// }
//
// @Override
// protected void channelRead0(ChannelHandlerContext channelContext, INetworkMessage message)
// {
// LOGGER.trace("Received message: "+message.getClass().getSimpleName());
//
// List<BiConsumer<INetworkMessage, ChannelHandlerContext>> handlerList = this.handlers.get(message.getClass());
// if (handlerList == null)
// {
// LOGGER.warn("Unhandled message type: "+message.getClass().getSimpleName());
// return;
// }
//
// for (BiConsumer<INetworkMessage, ChannelHandlerContext> handler : handlerList)
// {
// handler.accept(message, channelContext);
// }
// }
//
// @Override
// public void channelInactive(@NotNull ChannelHandlerContext channelContext) { this.channelRead0(channelContext, new CloseMessage()); }
}
@@ -1,81 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.protocol;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.seibel.distanthorizons.core.network.messages.*;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class MessageRegistry
{
public static final MessageRegistry INSTANCE = new MessageRegistry();
private final Map<Integer, Supplier<? extends INetworkMessage>> idToSupplier = new HashMap<>();
private final BiMap<Class<? extends INetworkMessage>, Integer> classToId = HashBiMap.create();
private MessageRegistry()
{
// Note: Messages must have parameterless constructors
// Keep messages below intact so client/server can disconnect if version does not match
this.registerMessage(HelloMessage.class, HelloMessage::new);
this.registerMessage(CloseReasonMessage.class, CloseReasonMessage::new);
// Define your messages after this line
this.registerMessage(AckMessage.class, AckMessage::new);
this.registerMessage(PlayerUUIDMessage.class, PlayerUUIDMessage::new);
this.registerMessage(RemotePlayerConfigMessage.class, RemotePlayerConfigMessage::new);
this.registerMessage(RequestChunksMessage.class, RequestChunksMessage::new);
}
public <T extends INetworkMessage> void registerMessage(Class<T> clazz, Supplier<T> supplier)
{
int id = this.idToSupplier.size() + 1;
this.idToSupplier.put(id, supplier);
this.classToId.put(clazz, id);
}
public Class<? extends INetworkMessage> getMessageClassById(int messageId) { return this.classToId.inverse().get(messageId); }
public INetworkMessage createMessage(int messageId) throws IllegalArgumentException
{
try
{
return this.idToSupplier.get(messageId).get();
}
catch (NullPointerException e)
{
throw new IllegalArgumentException("Invalid message ID");
}
}
public int getMessageId(INetworkMessage message) { return this.getMessageId(message.getClass()); }
public int getMessageId(Class<? extends INetworkMessage> messageClass) { return this.classToId.get(messageClass); }
}
@@ -1,57 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.protocol;
//import io.netty.channel.*;
//import io.netty.channel.socket.SocketChannel;
//import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
//import io.netty.handler.codec.LengthFieldPrepender;
import org.jetbrains.annotations.NotNull;
/** used when creating a network channel */
public class NetworkChannelInitializer //extends ChannelInitializer<SocketChannel>
{
private final MessageHandler messageHandler;
public NetworkChannelInitializer(MessageHandler messageHandler) { this.messageHandler = messageHandler; }
// @Override
// public void initChannel(@NotNull SocketChannel socketChannel)
// {
// ChannelPipeline pipeline = socketChannel.pipeline();
//
// // Encoder
// pipeline.addLast(new LengthFieldPrepender(Short.BYTES));
// pipeline.addLast(new MessageEncoder());
// pipeline.addLast(new NetworkOutboundExceptionRouter());
//
// // Decoder
// pipeline.addLast(new LengthFieldBasedFrameDecoder(Short.MAX_VALUE, 0, Short.BYTES, 0, Short.BYTES));
// pipeline.addLast(new MessageDecoder());
//
// // Handler
// pipeline.addLast(this.messageHandler);
// pipeline.addLast(new NetworkExceptionHandler());
// }
//
}
@@ -1,38 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.protocol;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
//import io.netty.channel.ChannelHandlerContext;
//import io.netty.channel.ChannelInboundHandlerAdapter;
import org.apache.logging.log4j.Logger;
public class NetworkExceptionHandler //extends ChannelInboundHandlerAdapter
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
// @Override
// public void exceptionCaught(ChannelHandlerContext channelContext, Throwable cause)
// {
// LOGGER.error("Exception caught in channel: ["+channelContext.name()+"].", cause);
// channelContext.close();
// }
}
@@ -1,36 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.protocol;
//import io.netty.channel.ChannelFutureListener;
//import io.netty.channel.ChannelHandlerContext;
//import io.netty.channel.ChannelOutboundHandlerAdapter;
//import io.netty.channel.ChannelPromise;
public class NetworkOutboundExceptionRouter //extends ChannelOutboundHandlerAdapter
{
// @Override
// public void write(ChannelHandlerContext channelContext, Object messageObj, ChannelPromise promise) throws Exception
// {
// promise.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
// super.write(channelContext, messageObj, promise);
// }
}
@@ -0,0 +1,153 @@
package com.seibel.distanthorizons.core.network.session;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.network.event.NetworkEventSource;
import com.seibel.distanthorizons.core.network.event.internal.CloseEvent;
import com.seibel.distanthorizons.core.network.event.internal.ProtocolErrorEvent;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import com.seibel.distanthorizons.core.network.messages.TrackableMessage;
import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import org.apache.logging.log4j.LogManager;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
public class Session extends NetworkEventSource
{
private static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
() -> Config.Client.Advanced.Logging.logNetworkEvent.get());
private static final IPluginPacketSender PACKET_SENDER = SingletonInjector.INSTANCE.get(IPluginPacketSender.class);
private static final AtomicInteger lastId = new AtomicInteger();
public final int id = lastId.getAndIncrement();
/**
* When non-null, any received data will be ignored. <br>
* This does not include wrong versions, which are ignored without setting this flag,
* to allow multi-compat servers.
*/
private final AtomicReference<Throwable> closeReason = new AtomicReference<>();
public Throwable getCloseReason() { return this.closeReason.get(); }
public boolean isClosed() { return this.closeReason.get() != null; }
@Nullable
public final IServerPlayerWrapper serverPlayer;
public Session(@Nullable IServerPlayerWrapper serverPlayer)
{
this.serverPlayer = serverPlayer;
this.registerHandler(CloseReasonMessage.class, msg ->
{
this.close(new SessionClosedException(msg.reason));
});
this.registerHandler(ProtocolErrorEvent.class, event ->
{
if (event.replyWithCloseReason)
{
this.sendMessage(new CloseReasonMessage("Internal error on other side"));
}
this.close(event.reason);
});
}
public void tryHandleMessage(NetworkMessage message)
{
if (this.closeReason.get() != null)
{
return;
}
message.setSession(this);
try
{
LOGGER.debug("Received message: {}", message);
this.handleMessage(message);
}
catch (Throwable e)
{
LOGGER.error("Failed to handle the message. New messages will be ignored.", e);
LOGGER.error("Message: {}", message);
this.close();
}
}
@Override
public <T extends NetworkMessage> void registerHandler(Class<T> handlerClass, Consumer<T> handlerImplementation)
{
if (this.closeReason.get() != null)
{
return;
}
this.registerHandler(this, handlerClass, handlerImplementation);
}
public <TResponse extends TrackableMessage> CompletableFuture<TResponse> sendRequest(TrackableMessage msg, Class<TResponse> responseClass)
{
msg.setSession(this);
CompletableFuture<TResponse> responseFuture = this.createRequest(msg, responseClass);
this.sendMessage(msg);
return responseFuture;
}
public void sendMessage(NetworkMessage message)
{
if (this.closeReason.get() != null)
{
return;
}
LOGGER.debug("Sending message: {}", message);
message.setSession(this);
try
{
if (this.serverPlayer != null)
{
PACKET_SENDER.sendPluginPacketServer(this.serverPlayer, message);
}
else
{
PACKET_SENDER.sendPluginPacketClient(message);
}
}
catch (Throwable throwable)
{
LOGGER.info("Failed to send a message", throwable);
LOGGER.info("Message: {}", message);
this.close(throwable);
}
}
public void close(Throwable closeReason)
{
if (!this.closeReason.compareAndSet(null, closeReason))
{
return;
}
try
{
this.handleMessage(new CloseEvent());
}
catch (Throwable ignored)
{
}
super.close();
}
}
@@ -0,0 +1,11 @@
package com.seibel.distanthorizons.core.network.session;
import java.io.IOException;
public class SessionClosedException extends IOException
{
public SessionClosedException(String message)
{
super(message);
}
}
@@ -1,231 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.util.LodUtil;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public class DhBlockPos
{
public static final boolean DO_CHECKS = false;
// 26 bits wide as that just encompasses the maximum possible value
// of +- 30,000,000 blocks in each direction. Yes this packing method
// is how Minecraft packs it.
// NOTE: Remember to ALWAYS check that DHBlockPos packing is EXACTLY
// the same as Minecraft's!!!!
public static final int PACKED_X_LENGTH = 26;
public static final int PACKED_Z_LENGTH = 26;
public static final int PACKED_Y_LENGTH = 12;
public static final long PACKED_X_MASK = (1L << PACKED_X_LENGTH) - 1L;
public static final long PACKED_Y_MASK = (1L << PACKED_Y_LENGTH) - 1L;
public static final long PACKED_Z_MASK = (1L << PACKED_Z_LENGTH) - 1L;
public static final int PACKED_Y_OFFSET = 0;
public static final int PACKED_Z_OFFSET = PACKED_Y_LENGTH;
public static final int PACKED_X_OFFSET = PACKED_Y_LENGTH + PACKED_Z_LENGTH;
/** Useful for methods that need a position passed in but won't actually be used */
public static final DhBlockPos ZERO = new DhBlockPos(0, 0, 0);
public int x;
public int y;
public int z;
public DhBlockPos(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
public DhBlockPos()
{
this(0, 0, 0);
}
public DhBlockPos(DhBlockPos pos)
{
this(pos.x, pos.y, pos.z);
}
public DhBlockPos(DhBlockPos2D pos, int y)
{
this(pos.x, y, pos.z);
}
public static long asLong(int x, int y, int z)
{
if (DO_CHECKS)
{
if ((x & ~PACKED_X_MASK) != 0)
{
throw new IllegalArgumentException("x is out of range: " + x);
}
if ((y & ~PACKED_Y_MASK) != 0)
{
throw new IllegalArgumentException("y is out of range: " + y);
}
if ((z & ~PACKED_Z_MASK) != 0)
{
throw new IllegalArgumentException("z is out of range: " + z);
}
}
return ((long) x & PACKED_X_MASK) << PACKED_X_OFFSET |
((long) y & PACKED_Y_MASK) << PACKED_Y_OFFSET |
((long) z & PACKED_Z_MASK) << PACKED_Z_OFFSET;
}
public static int getX(long packed)
{ // X is at the top
return (int) (packed << (64 - PACKED_X_OFFSET - PACKED_X_LENGTH) >> (64 - PACKED_X_LENGTH));
}
public static int getY(long packed)
{ // Y is at the bottom
return (int) (packed << (64 - PACKED_Y_OFFSET - PACKED_Y_LENGTH) >> (64 - PACKED_Y_LENGTH));
}
public static int getZ(long packed)
{ // Z is at the middle
return (int) (packed << (64 - PACKED_Z_OFFSET - PACKED_Z_LENGTH) >> (64 - PACKED_Z_LENGTH));
}
public DhBlockPos(long packed)
{
this(getX(packed), getY(packed), getZ(packed));
}
public long asLong()
{
return asLong(x, y, z);
}
/** creates a new {@link DhBlockPos} with the given offset from the current pos. */
public DhBlockPos offset(EDhDirection direction) { return this.mutateOffset(direction, null); }
/** if not null, mutates "mutablePos" so it matches the current pos after being offset. Otherwise creates a new {@link DhBlockPos}. */
public DhBlockPos mutateOffset(EDhDirection direction, @Nullable DhBlockPos mutablePos) { return this.mutateOffset(direction.getNormal().x, direction.getNormal().y, direction.getNormal().z, mutablePos); }
public DhBlockPos offset(int x, int y, int z) { return this.mutateOffset(x,y,z, null); }
public DhBlockPos mutateOffset(int x, int y, int z, @Nullable DhBlockPos mutablePos)
{
int newX = this.x + x;
int newY = this.y + y;
int newZ = this.z + z;
if (mutablePos != null)
{
mutablePos.x = newX;
mutablePos.y = newY;
mutablePos.z = newZ;
return mutablePos;
}
else
{
return new DhBlockPos(newX, newY, newZ);
}
}
/** Returns a new {@link DhBlockPos} limits to a value between 0 and 15 (inclusive) */
public DhBlockPos convertToChunkRelativePos() { return this.mutateToChunkRelativePos(null); }
/**
* Limits the block position to a value between 0 and 15 (inclusive)
* If not null, mutates "mutableBlockPos"
*/
public DhBlockPos mutateToChunkRelativePos(@Nullable DhBlockPos mutableBlockPos)
{
// move the position into the range -15 and +15
int relX = (this.x % LodUtil.CHUNK_WIDTH);
// if the position is negative move it into the range 0 and 15
relX = (relX < 0) ? (relX + LodUtil.CHUNK_WIDTH) : relX;
int relZ = (this.z % LodUtil.CHUNK_WIDTH);
relZ = (relZ < 0) ? (relZ + LodUtil.CHUNK_WIDTH) : relZ;
// the y value shouldn't need to be changed
if (mutableBlockPos != null)
{
mutableBlockPos.x = relX;
mutableBlockPos.y = this.y;
mutableBlockPos.z = relZ;
return mutableBlockPos;
}
else
{
return new DhBlockPos(relX, this.y, relZ);
}
}
/**
* Can be used to quickly determine the rough distance between two points<Br>
* or determine the taxi cab (manhattan) distance between two points. <Br><Br>
*
* Manhattan distance is equivalent to determining the distance between two street intersections,
* where you can only drive along each street, instead of directly to the other point.
*/
public int getManhattanDistance(DhBlockPos otherPos)
{
return Math.abs(this.x - otherPos.x) + Math.abs(this.y - otherPos.y) + Math.abs(this.z - otherPos.z);
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DhBlockPos that = (DhBlockPos) o;
return x == that.x && y == that.y && z == that.z;
}
@Override
public int hashCode()
{
return Objects.hash(x, y, z);
}
@Override
public String toString()
{
return "DHBlockPos[" +
"" + x +
", " + y +
", " + z +
']';
}
public static void _DebugCheckPacker(int x, int y, int z, long expected)
{
long packed = asLong(x, y, z);
if (packed != expected)
{
throw new IllegalArgumentException("Packed values don't match: " + packed + " != " + expected);
}
DhBlockPos pos = new DhBlockPos(packed);
if (pos.x != x || pos.y != y || pos.z != z)
{
throw new IllegalArgumentException("Values after decode don't match: " + pos + " != " + x + ", " + y + ", " + z);
}
}
}
@@ -19,12 +19,24 @@
package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d;
/**
* immutable <br><br>
*
* Dev note: if for some reason we want to store these as longs check the old commits. <br>
* That logic was removed since it wasn't needed at the time
*/
public class DhChunkPos
{
public final int x; // Low 32 bits
public final int z; // High 32 bits
private final int x;
public int getX() { return x; }
private final int z;
public int getZ() { return z; }
/** cached to improve hashing speed */
public final int hashCode;
@@ -45,15 +57,18 @@ public class DhChunkPos
}
public DhChunkPos(DhBlockPos blockPos)
{
// >> 4 is the Same as div 16
this(blockPos.x >> 4, blockPos.z >> 4);
// >> 4 is the Same as divide by 16
this(blockPos.getX() >> 4, blockPos.getZ() >> 4);
}
public DhChunkPos(DhBlockPos2D blockPos)
{
// >> 4 is the Same as div 16
this(blockPos.x >> 4, blockPos.z >> 4);
}
public DhChunkPos(long packed) { this(getXFromPackedLong(packed), getZFromPackedLong(packed)); }
public DhChunkPos(Vec3d pos)
{
this(((int)pos.x) >> 4, ((int)pos.z) >> 4);
}
@@ -61,13 +76,8 @@ public class DhChunkPos
// methods //
//=========//
public DhBlockPos center() { return new DhBlockPos(8 + this.x << 4, 0, 8 + this.z << 4); }
public DhBlockPos corner() { return new DhBlockPos(this.x << 4, 0, this.z << 4); }
public static long toLong(int x, int z) { return ((long) x & 0xFFFFFFFFL) << 32 | (long) z & 0xFFFFFFFFL; }
private static int getXFromPackedLong(long chunkPos) { return (int) (chunkPos >> 32); }
private static int getZFromPackedLong(long chunkPos) { return (int) (chunkPos & 0xFFFFFFFFL); }
public DhBlockPos centerBlockPos() { return new DhBlockPos(8 + this.x << 4, 0, 8 + this.z << 4); }
public DhBlockPos minCornerBlockPos() { return new DhBlockPos(this.x << 4, 0, this.z << 4); }
public int getMinBlockX() { return this.x << 4; }
public int getMinBlockZ() { return this.z << 4; }
@@ -81,11 +91,15 @@ public class DhChunkPos
int maxBlockX = minBlockX + LodUtil.CHUNK_WIDTH;
int maxBlockZ = minBlockZ + LodUtil.CHUNK_WIDTH;
return minBlockX <= pos.x && pos.x <= maxBlockX
&& minBlockZ <= pos.z && pos.z <= maxBlockZ;
return minBlockX <= pos.getX() && pos.getX() < maxBlockX
&& minBlockZ <= pos.getZ() && pos.getZ() < maxBlockZ;
}
public long getLong() { return toLong(this.x, this.z); }
public double distance(DhChunkPos other)
{
return Math.sqrt(Math.pow(x - other.x, 2) + Math.pow(z - other.z, 2));
}
//================//
@@ -116,39 +130,4 @@ public class DhChunkPos
@Override
public String toString() { return "C[" + this.x + "," + this.z + "]"; }
//=======================//
// static helper methods //
//=======================//
public static void _DebugCheckPacker(int x, int z, long expected)
{
long packed = toLong(x, z);
if (packed != expected)
{
throw new IllegalArgumentException("Packed values don't match: " + packed + " != " + expected);
}
DhChunkPos pos = new DhChunkPos(packed);
if (pos.x != x || pos.z != z)
{
throw new IllegalArgumentException("Values after decode don't match: " + pos + " != " + x + ", " + z);
}
}
/** @return true if testPos is within the area defined by the min and max positions. */
public static boolean isChunkPosBetween(DhChunkPos minChunkPos, DhChunkPos testPos, DhChunkPos maxChunkPos)
{
int minChunkX = Math.min(minChunkPos.x, maxChunkPos.x);
int minChunkZ = Math.min(minChunkPos.z, maxChunkPos.z);
int maxChunkX = Math.max(minChunkPos.x, maxChunkPos.x);
int maxChunkZ = Math.max(minChunkPos.z, maxChunkPos.z);
return minChunkX <= testPos.x && testPos.x <= maxChunkX &&
minChunkZ <= testPos.z && testPos.z <= maxChunkZ;
}
}
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import org.jetbrains.annotations.NotNull;
@@ -47,6 +48,10 @@ public class DhLodPos implements Comparable<DhLodPos>
//==============//
// constructors //
//==============//
public DhLodPos(byte detailLevel, int x, int z)
{
this.detailLevel = detailLevel;
@@ -36,6 +36,10 @@ public class DhLodUnit
//==============//
// constructors //
//==============//
public DhLodUnit(byte detailLevel, int numberOfLodSectionsWide)
{
this.detailLevel = detailLevel;
@@ -43,6 +47,11 @@ public class DhLodUnit
}
//=========//
// methods //
//=========//
/** @return the size of this LOD unit in Minecraft blocks */
public int toBlockWidth() { return BitShiftUtil.pow(this.numberOfLodSectionsWide, this.detailLevel); }
/** @return the LOD Unit relative to the given block width and detail level */

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