Compare commits

...

347 Commits

Author SHA1 Message Date
James Seibel 0b802ca362 comment about LOD only depth issue 2026-06-08 21:42:29 -05:00
James Seibel ea4375b215 Check if save folder exists for better error messages 2026-06-08 07:15:44 -05:00
s809 a6e94c39a7 Fix level requests not being sent more than once per session 2026-06-07 14:30:49 +05:00
James Seibel 8bd6b4837c Fix auto updater checking when disabled 2026-06-06 19:38:55 -05:00
James Seibel 679f871788 Remove MemUtil references due to native crashing 2026-06-06 18:10:59 -05:00
James Seibel b46f10755e remove cloud darkness and make clouds less dense 2026-06-06 17:59:11 -05:00
James Seibel 3cf799a10f fix hard fade cut-off with fog at low render dist 2026-06-06 16:56:45 -05:00
s809 4e99594a3f Add original level resource to level init 2026-06-07 01:32:41 +05:00
James Seibel 8a4f991906 lower DH cloud min height slightly 2026-06-06 11:04:49 -05:00
James Seibel ab634b3204 Darken DH cloud layers slightly 2026-06-06 11:04:33 -05:00
James Seibel b836f675e1 fix 3-layer clouds culling at low render distances 2026-06-06 11:04:20 -05:00
James Seibel a4e2003e0e Fix underwater fog rendering 2026-06-06 09:12:08 -05:00
s809 28a8bc39f2 Make level key requests work kinda 2026-06-04 23:05:14 +05:00
James Seibel 4f0a3afd93 Improve render engine config description 2026-06-03 07:44:37 -05:00
James Seibel 4ac56774fb Add error messages if DB becomes corrupted 2026-06-02 21:42:26 -05:00
James Seibel 18b0582152 Improve world load fail error messages 2026-06-02 21:42:05 -05:00
James Seibel b85c504995 add javadoc since to useCameraPositionForQualityDropOff 2026-06-02 19:00:50 -05:00
James Seibel cbff0cd9e9 add useCameraPositionForQualityDropOff config/api 2026-06-02 18:05:16 -05:00
James Seibel c28cf643b3 Fix camera position off-by-one with immersive portals 2026-06-02 17:39:54 -05:00
James Seibel 44aed79e78 add Dh prefix to DH math objects 2026-06-01 22:07:03 -05:00
James Seibel e192abe666 Fix LOD clouds not showing the correct colors 2026-06-01 21:45:20 -05:00
James Seibel 9754943752 Reduce rare generic object VBO upload race condition 2026-06-01 21:44:59 -05:00
James Seibel 299ab0f856 remove unused standalone jar files 2026-06-01 19:11:37 -05:00
James Seibel b97c35387c improve world gen/retrieve message 2026-06-01 19:03:01 -05:00
James Seibel f69ad051d1 merge immersive portals compat
Thanks Acuadragon100!
2026-06-01 07:30:59 -05:00
James Seibel 9231a48998 Clarify the world gen progress message 2026-05-30 19:44:29 -05:00
James Seibel e8f27f7da8 Add the option for 3 layer clouds 2026-05-30 16:38:55 -05:00
James Seibel db47a9e99f Remove deprecated EDhApiVanillaOverdraw 2026-05-30 11:39:30 -05:00
James Seibel 16f72066a8 Pool BufferQuad objects 2026-05-30 11:32:46 -05:00
James Seibel ca4d6f158a RenderDataPointReducingList GC optimization attempt 2026-05-30 11:20:03 -05:00
James Seibel ad9092c45c minor LodQuadTree optimization 2026-05-30 11:17:39 -05:00
James Seibel 95e4db2998 Reduce render event GC pressure 2026-05-30 09:17:21 -05:00
James Seibel 9fe5dcc16e minor GC optimization 2026-05-22 14:37:45 -05:00
James Seibel 8cd926f4ac uniform buffer change prep 2026-05-22 14:08:08 -05:00
James Seibel 7239b51073 fix native GL instanced generic rendering 2026-05-22 12:51:01 -05:00
James Seibel 262dcae36b fix renderingEngine lang 2026-05-22 09:59:42 -05:00
James Seibel 062f86c036 add DhApiBeforeFogRenderEvent 2026-05-22 09:36:10 -05:00
James Seibel 5b4029f0ad update comment 2026-05-20 17:30:53 -05:00
James Seibel faa4fa3782 Fix render API vs Engine enum 2026-05-20 07:27:30 -05:00
James Seibel 9db045d614 document openGL interfaces not used on Blaze3D
Also add API logic to determine if DH is handling the rendering natively or using an interpretation layer
2026-05-19 21:52:11 -05:00
James Seibel c81dc83bb1 clean up config menu, remove fake transparency 2026-05-19 19:18:04 -05:00
James Seibel b14701ef6b Fix unit test compiling 2026-05-19 07:26:05 -05:00
James Seibel 78f84f17cd Fix holes on LOD borders 2026-05-18 22:24:27 -05:00
James Seibel 7739e1cafd improve quadBuilder var name 2026-05-18 21:25:21 -05:00
James Seibel d39c04d9cf add a few api event @see's 2026-05-18 20:20:30 -05:00
James Seibel 492afa7328 add IDhApiLevelWrapper.getBlockColorPreApi() 2026-05-18 19:59:06 -05:00
James Seibel 9465512491 Fix blaze3d shader inputs for Vulkan 2026-05-17 18:12:51 -05:00
James Seibel 51b52a7d2a rename render doc LOD buffer label 2026-05-17 16:06:50 -05:00
James Seibel f106867091 Fix LodQuad tree not using a deamon thread 2026-05-17 16:06:08 -05:00
James Seibel 1c4908bbc5 Merge branch 'detached' into 'main'
Make renderingString show correct value if rendering is disabled for this dimension

See merge request distant-horizons-team/distant-horizons-core!100
2026-05-17 00:07:10 +00:00
James Seibel baa9b94a5d minor format cleanup 2026-05-17 00:06:49 +00:00
James Seibel 21136ba1ef remove unneeded TODO lines 2026-05-16 18:46:19 -05:00
James Seibel ec6f8255b5 IMcRenderWrapper.getMcRenderingApi() 2026-05-16 18:25:15 -05:00
James Seibel 2f9504b167 fix reverseZDepth uniform name 2026-05-16 17:54:30 -05:00
James Seibel f67d9e4e04 blaze3D shaders work on both GL and Vulkan 2026-05-16 15:49:41 -05:00
James Seibel c1644ad419 fix inverted Z clip plane 2026-05-16 14:06:10 -05:00
James Seibel e07557e6e3 add EDhRenderDepth 2026-05-16 10:48:16 -05:00
James Seibel 52b8a91dc5 fix cleanup threadpool preventing JVM shutdown 2026-05-16 10:16:10 -05:00
Vojtěch Šokala 64fe804c40 Make renderingString show correct value if rendering is disabled for this dimension 2026-05-16 13:33:42 +02:00
James Seibel 2dd5b82be3 Merge branch 'Vulkan' 2026-05-15 21:58:21 -05:00
James Seibel d1f0325f87 up api version 6.1.1 -> 7.0.0 2026-05-15 21:41:09 -05:00
James Seibel b10a367ce6 Fix DhApiBlockColorOverrideEvent method name 2026-05-15 21:40:55 -05:00
James Seibel 4dec387ca1 remove a todo 2026-05-15 18:23:55 -05:00
James Seibel fd3a8f7ddf Add MC Version locking to the config 2026-05-15 07:44:00 -05:00
James Seibel e3f586da56 temp comment out PooledDataSourceCheckoutTest 2026-05-12 21:56:18 -05:00
James Seibel 775984f651 add TODO 2026-05-11 22:01:00 -05:00
James Seibel 269f2c30fd Initial changes for Vulkan 2026-05-11 21:56:53 -05:00
James Seibel b674f49600 up version number 3.0.3 -> 3.0.4 2026-05-04 07:41:32 -05:00
James Seibel b592012ba8 remove dev from version number 2026-05-03 18:20:22 -05:00
James Seibel 5d1e8a44fd up api version 6.1.0 -> 6.1.1 2026-05-03 18:20:12 -05:00
James Seibel 40b27335ea Add stack getting for render tasks 2026-05-03 16:45:23 -05:00
James Seibel d0b07a5d2f remove accidental debug code 2026-05-03 16:40:35 -05:00
James Seibel cb0fee9780 fix generic renderer buffer leak on level close 2026-05-03 16:36:32 -05:00
James Seibel 895e9276cd Fix GL buffer GC in RenderContainer canceling 2026-05-03 15:46:01 -05:00
James Seibel 9ee0af8b01 Add BasicPhantomReference for debugging 2026-05-03 15:45:52 -05:00
James Seibel 69941fb7f8 DhApiBlockColorOverrideEvent use default alpha 2026-05-02 21:15:48 -05:00
James Seibel 36862a968f fix rare skylight application bug 2026-05-02 21:14:54 -05:00
James Seibel 27204336b2 cleanup lod buffer container closing 2026-05-02 21:14:14 -05:00
James Seibel 4846cf5019 comment out unnecessary shutdown logging 2026-05-02 21:13:07 -05:00
James Seibel f7f3c1146f separate shared phantom logging logic 2026-05-02 21:12:26 -05:00
James Seibel aaa5e958f0 Fix LOD shading applying incorrectly with Iris 2026-05-02 15:14:25 -05:00
James Seibel 726da953bd Merge branch 'distant-horizons-core-optimizations' 2026-05-02 11:35:26 -05:00
James Seibel c4f4935fdd Remove unused mac render code 2026-05-02 10:36:44 -05:00
James Seibel 3ef8bd7e20 Add position finder debug config 2026-04-29 07:35:16 -05:00
James Seibel ec72762067 use camera pos for detail calculations 2026-04-28 07:09:22 -05:00
James Seibel 4d0ed2a6dc fix null pointer on dedicated server shutdown 2026-04-27 07:48:06 -05:00
James Seibel 7b252b173b Fix wyncraft getting stuck at low LOD quality 2026-04-27 07:27:03 -05:00
James Seibel 7b0c66e3ae up version number 3.0.2 -> 3.0.3 2026-04-24 06:51:39 -05:00
James Seibel 1b066327a8 remove dev from the version number 2026-04-24 06:50:47 -05:00
James Seibel 43d0a971f7 add todo commented code 2026-04-24 06:50:00 -05:00
James Seibel 9e60c698de move before render pass events into render api 2026-04-23 17:54:33 -05:00
James Seibel bf2affa6d1 Fix "fog" rendering when underwater with Iris 2026-04-23 17:39:40 -05:00
James Seibel 98f6cea86a Fix near clip plane to close with shaders 2026-04-23 17:09:24 -05:00
James Seibel 9ae01dc1f8 up api version 6.0.0 -> 6.1.0 2026-04-23 07:42:21 -05:00
James Seibel 40efc5cbf3 Add alpha to DhApiBlockColorOverrideEvent 2026-04-23 07:42:07 -05:00
James Seibel 66bba1c80a add opacity to API block state wrapper 2026-04-23 07:41:51 -05:00
James Seibel d9f3b31cc5 Add timeout to CSV block culling configs 2026-04-22 18:48:21 -05:00
James Seibel e465ef5325 Fix flashing when moving over root node boundaries 2026-04-22 18:36:09 -05:00
s809 225385a43f Clean up received payload buffer check a bit 2026-04-23 00:26:32 +05:00
James Seibel 7d7d07416b Fix quad tree unit tests 2026-04-22 07:41:44 -05:00
James Seibel 5ef308cbee fix rare race condition preventing world gen 2026-04-21 22:19:57 -05:00
James Seibel d61b601c14 fix potential exceptions after world shutdown 2026-04-21 22:19:46 -05:00
James Seibel 246c679a97 Maybe fix native GL crash due to buffer free 2026-04-21 21:40:20 -05:00
James Seibel 4b317a8e00 Fix garbage collector warning not using config 2026-04-21 19:59:17 -05:00
James Seibel 1debd4b875 Improve node out-of-bound logic
This fixes some overlapping rendering issues, fixes LOD generating outside of render distance, and fixes low-detail LODs flashing when moving into previously-explored LODs
2026-04-21 19:49:50 -05:00
James Seibel 5dcda31990 Try fixing LOD flashing/stuck low details 2026-04-21 07:48:07 -05:00
James Seibel ae16ed2341 Revert "Fix LODs loading outside render distance"
This reverts commit 2c266d2495.
2026-04-20 21:32:00 -05:00
James Seibel 2c266d2495 Fix LODs loading outside render distance
Fixes !1233
2026-04-19 21:48:26 -05:00
James Seibel 7e40546bc5 fix world gen not canceling for far away pos 2026-04-19 21:10:20 -05:00
James Seibel 5d391c83ea quad tree region/comment cleanup 2026-04-19 21:07:42 -05:00
James Seibel 0895bf53e3 renderDataPointUtil toString cleanup 2026-04-18 21:45:24 -05:00
James Seibel a7203f8f33 up version number 3.0.1 -> 3.0.2 2026-04-18 21:44:17 -05:00
James Seibel 22efbb211a remove dev from version number 2026-04-18 21:43:37 -05:00
James Seibel 95c4459d8a compile separate combined API jar 2026-04-18 15:47:23 -05:00
James Seibel 0ef11caaf2 API jar auto include sources 2026-04-18 12:07:24 -05:00
James Seibel 2e3dfab6c3 fix api javadoc compiling 2026-04-18 12:06:14 -05:00
James Seibel 42be139e94 remove google-collect 2026-04-18 11:07:12 -05:00
James Seibel f866e7f8e3 up version number 3.0.0 -> 3.0.1 2026-04-18 10:36:52 -05:00
James Seibel 53fcce9d7c remove dev from version number 2026-04-18 10:34:57 -05:00
James Seibel 7b8b22fd5a terrain data cash override close without exception 2026-04-15 07:46:49 -05:00
James Seibel 6f54cfacb5 fix unit test compiling 2026-04-14 20:38:05 -05:00
James Seibel 61eaf43ba0 Add DhApiBlockColorOverrideEvent 2026-04-14 20:35:38 -05:00
James Seibel 1d368e3adc Add DhApiBlockStateWrapperCreatedEvent 2026-04-14 19:43:37 -05:00
James Seibel 9e65e2dd4c comment/deprecate a few API events 2026-04-14 19:03:00 -05:00
James Seibel 2d878338cb Merge branch 'change/channel_name_compat' 2026-04-14 17:08:12 -05:00
James Seibel d62d21776d fix IBO buffer creation size 2026-04-12 15:17:48 -05:00
James Seibel a44c5d562d Move native dialog to common
Native dialog was changed with LWJGL 3.4.1
2026-04-12 13:53:31 -05:00
James Seibel 1d9bffe64e only run world gen for rendering levels
Also fix world gen progress getting stuck on a single level
2026-04-11 21:40:28 -05:00
James Seibel b4e0687e2a fix gitlab auto updater failing for MC 26 2026-04-11 16:58:23 -05:00
James Seibel 89804f1ba1 add documentation about nullable IDhApiUnsafeWrapper 2026-04-11 15:23:00 -05:00
James Seibel 9dbc5ef525 fix white beacons colored incorrectly 2026-04-11 12:34:10 -05:00
James Seibel e5dcb0999d minor optifine optimization 2026-04-11 11:11:48 -05:00
James Seibel 50e0e940d1 profile wrapper try-finally for pushes 2026-04-11 11:03:56 -05:00
James Seibel cb3e42fac4 Merge branch 'client-updates' into 'main'
Don't drop client updates if level is not loaded yet

See merge request distant-horizons-team/distant-horizons-core!98
2026-04-09 19:44:12 +00:00
Fabian Maurer 34cdaf02eb Don't drop client updates if level is not loaded yet 2026-04-08 22:53:58 +02:00
James Seibel 71e6b9b58e remove unused getFov() 2026-04-05 17:17:28 -05:00
Ran-Mewo 1ec342928f fix api tests 2026-03-30 17:06:05 +11:00
James Seibel d45a1379bd Fix running fabric via gradle/IDE 2026-03-29 16:04:37 -05:00
Ran-Mewo ecb3dce963 wao 2026-03-29 19:23:25 +11:00
James Seibel 2b8cddd424 disable thread pausing when render tasks exist 2026-03-27 07:00:38 -05:00
James Seibel 13895fec51 start cleaning up config 2026-03-24 19:37:52 -05:00
James Seibel 88c7245be6 change EDhApiRendererMode DEBUG -> DEBUG_TRIANGLE 2026-03-24 19:36:57 -05:00
James Seibel 6aad156a32 Fix Iris using the wrong far clip plane
https://github.com/IrisShaders/Iris/issues/2534
2026-03-24 07:16:31 -05:00
James Seibel 94535a213e LodRequestModule TODO cleanup 2026-03-22 21:40:32 -05:00
James Seibel 63d6d42356 ignore already closed repo close calls 2026-03-22 21:36:31 -05:00
James Seibel acecbede8e fix phantom log when there is sufficient memory 2026-03-21 16:57:38 -05:00
James Seibel e2a8953e4c minor cloud render cleanup 2026-03-21 16:35:33 -05:00
James Seibel 32eae23963 stage VBO/IBO upload and allow global IBO 2026-03-21 15:33:36 -05:00
James Seibel 480c3b3ec5 Drastically improve frame stability 2026-03-21 15:20:28 -05:00
James Seibel 6c0736a2a0 minor renderer refactoring 2026-03-19 07:28:41 -05:00
James Seibel c89abd414b Add render task profiling 2026-03-18 07:45:42 -05:00
James Seibel da2454b249 Add the ability to cull lilly pads, seaGrass, etc. 2026-03-15 20:53:21 -05:00
James Seibel 5933ef8245 Fix MC complaining about GL shader file names 2026-03-15 16:52:03 -05:00
James Seibel b9984c7723 remove unneeded chunk update warnings 2026-03-15 16:46:40 -05:00
James Seibel 2de50475f1 reduce the max time for render thread tasks 2026-03-15 16:19:33 -05:00
James Seibel bb838328a7 Show RenderApi config in UI 2026-03-15 16:15:34 -05:00
James Seibel 1980e64b2f Fix vertex buffer count calculation 2026-03-15 14:55:49 -05:00
James Seibel c3df26f5cb Disable Mac initial startup delay 2026-03-15 14:54:06 -05:00
James Seibel 17df533fa6 Improve warning logs for chunkUpdateManager 2026-03-14 15:55:38 -05:00
James Seibel 2393bdd8e8 clean up todo comments 2026-03-14 15:33:23 -05:00
James Seibel 082b364f52 default renderable box group to clean 2026-03-14 15:25:44 -05:00
James Seibel 8a39610b8c move beacon render enabling to LodQuadTree
Fixes beacons not always showing/hiding correctly
2026-03-14 14:32:19 -05:00
James Seibel aa53835772 fix generic rendering not uploading in some cases 2026-03-14 14:30:58 -05:00
James Seibel cd5a3ce52b Try fix concurrency issue with render closing 2026-03-14 10:18:40 -05:00
James Seibel 38e104a9fc Render task handler name tracking 2026-03-14 09:20:01 -05:00
James Seibel e27cee1f71 SharedApi set world lock 2026-03-14 09:19:41 -05:00
James Seibel 15d1d78954 Fix render task auto cleanup timer 2026-03-14 09:18:28 -05:00
s809 e2421c97ed Stop pregen on server shutdown 2026-03-13 00:47:44 +05:00
s809 8b72920652 Show real pregen speed quicker 2026-03-13 00:01:01 +05:00
s809 212031a05f Fix world generation not running with 0 players connected 2026-03-12 22:48:27 +05:00
s809 804293e291 Replace fix with debug wireframe stub 2026-03-12 22:38:37 +05:00
s809 cfdd464f30 Revert "Fix server not loading levels"
This reverts commit 4c9d703e15.
2026-03-12 21:12:04 +05:00
s809 4c9d703e15 Fix server not loading levels 2026-03-12 19:20:26 +05:00
James Seibel 0eba376e70 up Dh version 2.4.6 -> 3.0.0 2026-03-10 19:07:39 -05:00
James Seibel 89e6355d73 Merge branch 'blazeRender' 2026-03-10 19:06:58 -05:00
James Seibel 31b6d2dd05 Fix debug wireframes not rendering 2026-03-10 19:06:02 -05:00
James Seibel d75b65e6e7 hide render API config from UI 2026-03-10 18:44:48 -05:00
James Seibel 1bbe41c068 move GL shaders into the correct folder 2026-03-10 17:56:05 -05:00
James Seibel e426fc2380 Move lightmap wrapper methods into common 2026-03-10 17:33:55 -05:00
James Seibel 4e908b5b15 Make render interfaces consistent 2026-03-10 17:20:19 -05:00
James Seibel b51ab3d9cd clear up render task logging 2026-03-10 17:03:13 -05:00
James Seibel 9e7d0a1538 method rename 2026-03-10 16:58:57 -05:00
James Seibel 5b0bf59f00 fix iris rendering 2026-03-10 16:44:32 -05:00
James Seibel 0362d89173 Separate out some rendering logic 2026-03-10 14:50:53 -05:00
James Seibel 1b0f93db07 Changes for blaze/gl rendering config 2026-03-10 11:40:36 -05:00
James Seibel 559bad5676 Add rendering API definition 2026-03-09 20:12:40 -05:00
James Seibel f145444fd4 fix race condition in SharedApi setup 2026-03-09 20:07:31 -05:00
James Seibel 10b08ca116 Fix ClientApi profiler adding an incorrect layer 2026-03-09 19:14:21 -05:00
James Seibel 1dd244e889 Rename and reorganize render pass interfaces 2026-03-09 18:59:19 -05:00
James Seibel 2ea3d645e8 remove deprication warnings 2026-03-09 17:39:58 -05:00
James Seibel 49e34d78a5 update debug wireframe renderer 2026-03-09 17:35:24 -05:00
James Seibel 17cdb0f745 re-add some core rendering handlers 2026-03-09 16:35:17 -05:00
James Seibel 67b2467bee change where vertex size is found 2026-03-09 16:34:25 -05:00
James Seibel 82c832a4af Add RenderThreadTaskHandler 2026-03-09 16:30:45 -05:00
James Seibel c84dbfceaf update some imports 2026-03-09 16:26:48 -05:00
James Seibel 27b66940be Start moving OpenGL rendering to common 1 2026-03-09 16:22:55 -05:00
James Seibel 8240101a46 javadoc 2026-03-09 13:58:29 -05:00
James Seibel e1a932cf38 Only upload unique LOD uniforms once 2026-03-09 13:51:44 -05:00
James Seibel 39dd1c8509 renderable box cleanup 2026-03-09 12:28:49 -05:00
James Seibel fee0aadcbe MC -> blaze renaming 2026-03-09 12:19:53 -05:00
James Seibel a8c15d22c3 rename vbo containers 2026-03-09 11:16:50 -05:00
James Seibel bd833ba510 move blaze shader files 2026-03-09 09:57:03 -05:00
James Seibel a5a9a62e89 Fix ssao application 2026-03-08 21:14:48 -05:00
James Seibel e790bfb7e8 merge apply shaders 2026-03-07 14:32:02 -06:00
James Seibel 0f539f3a6f start separating out uniform logic 2026-03-05 17:32:19 -06:00
James Seibel d3f28f064b fix chunk update queue count flipped 2026-03-05 07:23:13 -06:00
James Seibel 40149bc1d1 fix chunk update queue count flipped 2026-03-05 07:23:02 -06:00
James Seibel 40703db763 uncomment deferred rendering 2026-03-05 07:22:22 -06:00
James Seibel 2a554395f7 debug rendering 2026-03-04 18:07:49 -06:00
James Seibel c3dce412fa Add far fade 2026-03-04 07:38:49 -06:00
James Seibel 225c2df8df add fog 2026-03-03 07:47:58 -06:00
James Seibel 84c212a780 add SSAO 2026-03-02 07:45:25 -06:00
James Seibel 3d9ae5f088 generic object rendering 2026-03-01 19:32:54 -06:00
James Seibel 200e089a33 re-add lighting 2026-02-28 12:13:34 -06:00
James Seibel 3f509be195 remove unneeded partial ticks from old vanilla fade 2026-02-28 11:34:48 -06:00
James Seibel 1a2c0b0be1 re-add vanilla fading 2026-02-28 11:34:39 -06:00
James Seibel c6a4355718 minor render cleanup 2026-02-28 08:13:26 -06:00
James Seibel 3e80961d18 Rough initial LOD renderering 2026-02-26 16:55:54 -06:00
James Seibel 9f483ee07a Fix fade test shader 2026-02-24 22:03:43 -06:00
James Seibel d1ba402f4d start of vanilla fade logic 2026-02-24 09:57:03 -06:00
James Seibel 87cead607f move test shader folder 2026-02-24 07:02:05 -06:00
James Seibel 1b2992f1dc proof-of-concept test renderer 2026-02-23 12:31:21 -06:00
James Seibel ed0e94ccb7 Fix chunks applying to the wrong dimension 2026-02-18 22:05:23 -06:00
James Seibel 364f8f7afc improve transformer comment 2026-02-18 07:08:44 -06:00
James Seibel 962f20460c Fix dark LODs with false tintWithAvoidedBlocks 2026-02-17 07:47:30 -06:00
James Seibel ce17ac71c6 Remove buffer mapping 2026-02-17 07:16:42 -06:00
James Seibel 5adb9afa0a always disable instanced rendering on mac 2026-02-16 07:16:32 -06:00
James Seibel 3c80c11888 comment on a partially broken unit test 2026-02-14 22:34:17 -06:00
James Seibel 0714697e05 Fix beacon update locks 2026-02-14 22:01:25 -06:00
James Seibel df52f41b87 Fix beacons not reloading (and improve logic) 2026-02-14 21:32:30 -06:00
James Seibel 66a1d0296e maybe fix deprecated warn in OptifineAcessor 2026-02-14 21:31:51 -06:00
James Seibel c827817a94 ClientLevelModule cleanup 2026-02-14 11:10:35 -06:00
James Seibel 9895676b63 Increase startup timeout for MAC 2026-02-14 08:32:44 -06:00
James Seibel 5d5c94e652 Force enable fog if MC is rendering fog
done to fix underwater/blindness rendering
2026-02-14 08:24:22 -06:00
James Seibel 0a568571d9 Add logs when changing vanilla settings 2026-02-13 07:49:31 -06:00
James Seibel 291c0470ad auto disable fancy graphics if enabled 2026-02-13 07:35:50 -06:00
James Seibel 2f4587579f Change quadElementBuffer to only use DATA 2026-02-13 07:35:38 -06:00
James Seibel 2d70388587 Maybe fix Mac crashing with sodium on world start? 2026-02-12 07:27:11 -06:00
James Seibel a758c0bb3f regions to initalizer 2026-02-12 07:13:05 -06:00
James Seibel dd6aed273d Hopefully fix a rare concurrency issue in buffer Builder 2026-02-11 07:45:42 -06:00
James Seibel e2d663ee34 remove some unneeded concurrency checks in render setup 2026-02-11 07:34:55 -06:00
James Seibel dfc920d9bb Fix a harmless error message 2026-02-11 07:15:49 -06:00
James Seibel c91631809e re-add chat warning if G1GC is used 2026-02-11 07:09:33 -06:00
James Seibel 457bbebbdd Reduce memory allocation slightly during LOD loading 2026-02-10 07:32:13 -06:00
James Seibel 4d3242a370 improve concurrency handling in LOD render loading 2026-02-09 07:44:03 -06:00
James Seibel f0d71027f1 Fix loosing some thread pool tasks 2026-02-09 07:43:53 -06:00
James Seibel a98d9239e6 document Vbo.getVertexCount() 2 2026-02-08 21:37:33 -06:00
James Seibel 365edd48f8 document Vbo.getVertexCount() 2026-02-08 21:37:17 -06:00
James Seibel 9f22e3f2b3 Improve render load error handling 2026-02-08 21:06:01 -06:00
James Seibel 78f83197d7 Hide non-rendering levels in the F3 screen 2026-02-08 20:42:32 -06:00
James Seibel 42ae79b76c remove deprecated OS enum (replaced by EPlatform) 2026-02-08 20:12:26 -06:00
James Seibel 1178ef0706 Remove unused ID mappings after data update
Requires re-downsampling all LODs
2026-02-08 19:56:24 -06:00
James Seibel a506d2ef1f FullDataSource regions 2026-02-08 19:55:09 -06:00
James Seibel 92d9e631a7 Fix update propagator only re-queueing once queue empty 2026-02-08 19:53:03 -06:00
James Seibel 8afe388eb6 DataIdMap to string 2026-02-08 19:52:32 -06:00
James Seibel df1dae70c2 FullDataPointUtil reformat 2026-02-08 19:52:23 -06:00
James Seibel 777ed9215a datasource v1 mapping decode cleanup 2026-02-08 17:25:27 -06:00
James Seibel 25f4bde825 delayed full data cache regions 2026-02-07 18:37:21 -06:00
James Seibel fbb1beb359 ConfigEntry tostring for debugging 2026-02-07 18:15:12 -06:00
James Seibel f0852235b2 LodQuadTree cleanup and todo remove 2026-02-07 18:13:27 -06:00
James Seibel 4faa82e895 DhServerLevel use base shouldDoWorldGen()
@pshsh @s809 do you know why DhServerLevel overrode AbstractDhServerLevel?
In a quick test it appears using the super method worked just fine.
2026-02-07 17:59:08 -06:00
James Seibel 7201df1eff retrieval queue cleanup 2026-02-07 17:57:03 -06:00
James Seibel b6b13843eb shader constructor cleanup 2026-02-07 17:46:30 -06:00
James Seibel 10208d4dfa fix vanilla fade order 2026-02-07 17:00:30 -06:00
James Seibel d04e156dd1 Move some world gen queue limiting into the LodQuadTree 2026-02-07 16:37:56 -06:00
James Seibel 19412f80c5 cleanup cloud render handler 2026-02-07 16:13:33 -06:00
James Seibel dea3557546 Add colors to the F3 screen 2026-02-07 14:14:13 -06:00
James Seibel a3c72dbcbc clientApi remove finished todo comments 2026-02-07 13:28:22 -06:00
James Seibel dffad16d27 Clean up ColumnArrayView 2026-02-07 13:20:06 -06:00
James Seibel 0a3756eb9d Remove swing UI classes 2026-02-07 13:19:44 -06:00
James Seibel 821acaa0b9 MovableGridRingList cleanup 2026-02-07 12:05:18 -06:00
James Seibel 0fc5c55712 ArrayGridList pos object merge 2026-02-07 11:55:16 -06:00
James Seibel c13d04ff2b vanillaFade shader cleanup 2026-02-07 11:54:44 -06:00
James Seibel c9925b0b6c full data point ID map regions 2026-02-07 11:00:51 -06:00
James Seibel 725602e61f handle extra sql db closed 2026-02-07 10:18:40 -06:00
James Seibel bdeeb4c93b remove TODO for LWJGL versioning 2026-02-07 10:01:05 -06:00
James Seibel b3185c00d6 Replace DhLodPos with DhSectionPos 2026-02-07 08:40:17 -06:00
James Seibel ce20d3da32 debug render cleanup 2026-02-07 08:31:51 -06:00
James Seibel 69653336bf Remove most SSAO configs
No one seems to need or use them so we can just hard-code in the values we know look good.
2026-02-05 21:58:30 -06:00
James Seibel 062f11df21 Change graphics configs from doubles to floats 2026-02-05 21:57:17 -06:00
James Seibel 9e68d11608 re-add ignored blocks to config UI 2026-02-05 21:37:37 -06:00
James Seibel 3d349cb292 Only allow rendering clouds in config set dimensions 2026-02-05 21:37:29 -06:00
James Seibel d13bd9c4d3 clean up QuadTree unit tests 2026-02-05 21:12:25 -06:00
James Seibel 4a256683ab comments and region for update prop threading 2026-02-05 18:01:21 -06:00
James Seibel 7325d57e88 Make GLState AutoClosable for more robustness 2026-02-05 17:36:40 -06:00
James Seibel 7348cb1c37 Remove incomplete standalone GUI and comment incomplete DB parsing 2026-02-05 17:29:19 -06:00
James Seibel 007a3bdefa remove legacy logo SVG
Still available in the core repo's misc folder
2026-02-05 17:11:29 -06:00
James Seibel 6d95eb1099 fix magic numbers for lightmap binding 2026-02-05 17:11:09 -06:00
James Seibel 86e21cdaf6 remove unused VertexFormats 2026-02-05 17:02:15 -06:00
James Seibel d0bb03288d Fix fog falloff config not being used 2026-02-05 16:57:39 -06:00
James Seibel be3e618aca shader reformatting 2026-02-05 16:49:30 -06:00
James Seibel 35d3614c88 add a TODO to GenFullDataSourceProvider 2026-02-05 07:40:47 -06:00
James Seibel ba32af2c58 improve priority task picker task counting 2026-02-05 07:40:34 -06:00
James Seibel 2a681ef8e6 Pool ZStd decompress contexts 2026-02-05 07:40:11 -06:00
James Seibel 24684b7760 Fix render loading queuing incorrectly 2026-02-05 07:39:58 -06:00
James Seibel d3d4312ff6 Handle cave block culling more generically 2026-02-04 07:47:54 -06:00
James Seibel 6086a8a957 null check to AbstractConfigBase 2026-02-04 07:47:18 -06:00
James Seibel cd2530ca88 cleanup TODO comments 2026-02-04 07:47:08 -06:00
James Seibel af0e5699e2 Fix !1088 (API config.getApiValue() not returning null) 2026-02-04 07:19:50 -06:00
James Seibel d0e14ac408 LZMA cache commenting 2026-02-03 21:32:16 -06:00
James Seibel 0636712f80 Fix a rare world gen phantom closing issue 2026-02-03 21:32:10 -06:00
James Seibel 9bfd2812a6 fix near clip plane flickering 2026-02-03 21:30:16 -06:00
James Seibel 5f12c9e1f3 rename normal.vert -> quadApply.vert 2026-02-03 20:50:45 -06:00
James Seibel f8f0e3f4b9 move phantom pool into a new namespace 2026-02-03 20:48:07 -06:00
James Seibel ac36ce4560 refactoring and TODO cleanup 2026-02-03 20:47:06 -06:00
James Seibel f2612f39c0 cleanup world gen queue todo comments 2026-02-03 20:35:15 -06:00
James Seibel e7e7bc866d Clean up RenderUtil near clip methods 2026-02-03 20:34:54 -06:00
James Seibel 39eed05d83 Fix Debug renderer on newer MC versions 2026-02-03 19:59:12 -06:00
James Seibel b0624c8714 up the api version 5.1.0 -> 6.0.0 2026-02-03 07:05:50 -06:00
James Seibel 40351d1894 Require a data cache for API Repo methods 2026-02-03 07:05:13 -06:00
James Seibel a488478b1d null check in level loading 2026-02-02 07:18:00 -06:00
James Seibel c4b49ef308 Remove MC Texture LodBias config 2026-02-02 07:17:49 -06:00
James Seibel b511ab4fb3 Add RenderWrapper.getPartialTickTime() 2026-02-02 07:09:06 -06:00
James Seibel bbf69c7911 Make generic object updating async 2026-01-31 17:33:03 -06:00
James Seibel 6de41cd384 LodRenderSection remove unused code 2026-01-31 16:01:34 -06:00
James Seibel 7e7ccf1f38 unindent RenberableBoxGroup 2026-01-31 12:50:16 -06:00
James Seibel 5a6aa00ae7 RenberableBoxGroup regions 2026-01-31 12:49:58 -06:00
James Seibel 9c71540928 clean up bufferQuad arg names 2 2026-01-31 12:49:49 -06:00
James Seibel ff1859de8d clean up BufferQuad arg names 2026-01-31 12:42:03 -06:00
James Seibel 00be9a3c4f Handle MC running at 0 FPS 2026-01-31 10:22:23 -06:00
James Seibel dfed2c8966 GLproxy regions 2026-01-31 09:16:44 -06:00
James Seibel b20aebb09c generic renderer regions 2026-01-31 09:15:22 -06:00
James Seibel 9cbe98f81b clean up more TODO comments 2026-01-30 07:49:24 -06:00
James Seibel a9bfc3fbe1 Clean up more TODOs 2026-01-29 07:48:10 -06:00
James Seibel c30e410132 Clean up some API TODOs 2026-01-29 07:14:30 -06:00
James Seibel 11cd36cdec Fix TODO in DependencyInjector 2026-01-28 07:20:05 -06:00
James Seibel 59e35ffad3 Fix beacons not rendering 2026-01-27 20:50:53 -06:00
James Seibel 7b26c0baeb Fix paused executors not running tasks 2026-01-27 20:49:54 -06:00
James Seibel 60e53ff20b remove debug log 2026-01-27 20:49:42 -06:00
James Seibel 0f756a370b Queue gen tasks outside the tree update thread 2026-01-27 20:06:25 -06:00
James Seibel f0f525ef79 Fix ZStd decompressions streams being to long 2026-01-27 20:05:50 -06:00
James Seibel 59a8e31507 Reduce GC pressure when loading LODs from disk 2026-01-27 18:21:08 -06:00
James Seibel 6d3288ec42 LodRenderSection remove rate limit and improve task ordering 2026-01-26 20:29:55 -06:00
James Seibel 4e979b1f00 LodQuadBuilder regions 2026-01-26 06:52:45 -06:00
James Seibel 396545340e Move Thread Pool stat string method 2026-01-26 06:52:07 -06:00
James Seibel 87bfbf1afe Add a bunch of TODO comments 2026-01-26 06:51:05 -06:00
James Seibel 4346a2e803 Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons-core 2026-01-24 13:39:00 -06:00
James Seibel 6c228c1a17 Move Quad tree related objects 2026-01-24 13:38:02 -06:00
James Seibel d90361af59 Change LOD loading to start at lowest detail 2026-01-24 13:37:23 -06:00
s809 a0f06e4451 Merge branch 'feature/split-generation-toggles' 2026-01-18 22:39:43 +05:00
James Seibel 37d08b6dfa pause world gen when moving quickly 2026-01-17 17:03:43 -06:00
James Seibel eadf19405e add changes from DhRenderState 2026-01-17 16:15:46 -06:00
James Seibel f0757296f8 Add dynamic overdraw distance based on camera speed 2026-01-17 16:12:06 -06:00
James Seibel 3e29b361e6 Improve clientApi code regions 2026-01-17 10:46:45 -06:00
James Seibel 7b6fd03d78 Change render wrapper get Texture error returns 2026-01-17 09:56:21 -06:00
Jim C K Flaten b0e7c31964 Comment 2026-01-05 21:32:41 +01:00
Jim C K Flaten 2e906b57c4 Channel name should be 20 chars or shorter for compatibility with old version of Minecraft. 2026-01-03 23:36:46 +01:00
346 changed files with 16395 additions and 19472 deletions
+59 -23
View File
@@ -2,48 +2,63 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins { plugins {
id "java" id "java"
id "com.gradleup.shadow"
id "com.github.johnrengelman.shadow" version '8.1.1' apply false
} }
repositories {
shadowJar { mavenCentral()
// required for basic shadowJar setup
configurations = [project.configurations.shadow]
} }
task addSourcesToCompiledJar(type: ShadowJar) { tasks.withType(JavaCompile).configureEach {
options.release = 8
options.encoding = "UTF-8"
}
def sourceJarPath = "build/libs/DistantHorizons-api-${rootProject.versionStr}-sources.jar" configurations {
def secondJarFile = file(sourceJarPath) testImplementation.extendsFrom compileOnly
}
dependencies {
compileOnly "org.apache.logging.log4j:log4j-api:${rootProject.log4j_version}"
testImplementation "junit:junit:4.13"
}
java {
withSourcesJar()
}
task createReleaseApiJar(type: ShadowJar) {
mustRunAfter sourcesJar
dependsOn shadowJar
// the compiled "-all" jar is used since it's available at the time this task is run
def compiledJarPath = "build/libs/DistantHorizonsApi-${rootProject.api_version}-all.jar"
def compiledJarFile = file(compiledJarPath)
// doFirst is so these only run when the task is actually executed // doFirst is so these only run when the task is actually executed
doFirst { doFirst {
System.out.println("Adding source files from: \n" + System.out.println("Adding class files from: \n" +
"[" + sourceJarPath + "] to compiled API jar: \n" + "[" + compiledJarPath + "] to source API jar: \n" +
"[" + shadowJar.archivePath + "]") "[" + shadowJar.archiveFile.get().asFile + "]")
// Validate the input JAR file // Validate the input JAR file
if (!secondJarFile.exists()) { if (!compiledJarFile.exists()) {
throw new GradleException("Second JAR file not found: [${secondJarFile}]") throw new GradleException("Compiled JAR file not found: [${compiledJarFile}]")
} }
} }
// Set the name of the combined JAR file archiveFileName.set("DistantHorizonsApi-${rootProject.api_version}-combined.jar") // jar name
archiveFileName.set("DistantHorizonsApi-${rootProject.api_version}.jar") destinationDirectory = file('build/libs/') // jar location
// Set the destination directory for the combined JAR file
destinationDirectory = file('build/libs/merged/')
// Set the input JAR files to be combined // Set the input JAR files to be combined
from sourceSets.main.allJava from sourceSets.main.allJava
from { from {
configurations.shadow.collect { it.isDirectory() ? it : zipTree(it) } project.configurations.shadow.collect { it.isDirectory() ? it : zipTree(it) }
} }
// set the jars to merge // add the class files
from shadowJar.archivePath from zipTree(compiledJarFile)
from secondJarFile
// alternative method to Include the source files in the combined JAR // alternative method to Include the source files in the combined JAR
// and/or see which files are being included // and/or see which files are being included
@@ -77,6 +92,13 @@ task addSourcesToCompiledJar(type: ShadowJar) {
} }
} }
// always create a combined jar for easy deployment
assemble.dependsOn(createReleaseApiJar)
shadowJar {
// required for basic shadowJar setup
configurations = [project.configurations.shadow]
}
javadoc { javadoc {
options { options {
@@ -87,3 +109,17 @@ javadoc {
addStringOption('Xdoclint:all,-missing', '-quiet') addStringOption('Xdoclint:all,-missing', '-quiet')
} }
} }
// set the jar name
def configureJar = { task ->
// outputs in the format:
// "DistantHorizonsApi-6.0.0.jar"
// "DistantHorizonsApi-6.0.0-sources.jar"
task.archiveBaseName = rootProject.api_name
task.archiveVersion = rootProject.api_version
}
configureJar(tasks.named("jar").get())
configureJar(tasks.named("sourcesJar").get())
configureJar(tasks.named("shadowJar").get())
@@ -35,29 +35,12 @@ public enum EDhApiGpuUploadMethod
/** Picks the best option based on the GPU the user has. */ /** Picks the best option based on the GPU the user has. */
AUTO(false, false), AUTO(false, false),
// commented out since it isn't currently in use
//BUFFER_STORAGE_MAPPING(true, true),
/** Fast rendering, no stuttering. */ /** Fast rendering, no stuttering. */
BUFFER_STORAGE(false, true), BUFFER_STORAGE(false, true),
/** Fast rendering but may stutter when uploading. */ /** Fast rendering but may stutter when uploading. */
SUB_DATA(false, false), SUB_DATA(false, false),
///** Don't upload, only should be used for debugging */
//@Deprecated
//NONE(false, false),
/**
* May end up storing buffers in System memory. <br>
* Fast rending if in GPU memory, slow if in system memory, <br>
* but won't stutter when uploading.
*
* @deprecated not currently supported
*/
@Deprecated
BUFFER_MAPPING(true, false),
/** Fast rendering but may stutter when uploading. */ /** Fast rendering but may stutter when uploading. */
DATA(false, false); DATA(false, false);
@@ -0,0 +1,17 @@
package com.seibel.distanthorizons.api.enums.config;
/**
* VULKAN, <br>
* OPEN_GL, <br>
*
* @see EDhApiRenderingEngine
*
* @since API 7.0.0
* @version 2026-3-10
*/
public enum EDhApiRenderingApi
{
VULKAN,
OPEN_GL;
}
@@ -0,0 +1,19 @@
package com.seibel.distanthorizons.api.enums.config;
/**
* AUTO, <br>
* OPEN_GL, <br>
* BLAZE_3D, <br><br>
*
* @see EDhApiRenderingApi
*
* @since API 7.0.0
* @version 2026-3-10
*/
public enum EDhApiRenderingEngine
{
AUTO,
OPEN_GL,
BLAZE_3D;
}
@@ -53,7 +53,7 @@ public enum EDhApiVerticalQuality
public int calculateMaxVerticalData(byte dataDetail) public int calculateMaxNumberOfVerticalSlicesAtDetailLevel(byte dataDetail)
{ {
// for detail levels lower than what the enum defines, use the lowest quality item // for detail levels lower than what the enum defines, use the lowest quality item
int index = MathUtil.clamp(0, dataDetail, this.maxVerticalData.length - 1); int index = MathUtil.clamp(0, dataDetail, this.maxVerticalData.length - 1);
@@ -43,11 +43,7 @@ public enum EDhApiDebugRendering
SHOW_BLOCK_MATERIAL, SHOW_BLOCK_MATERIAL,
/** Only draw overlapping LOD quads. */ /** Only draw overlapping LOD quads. */
SHOW_OVERLAPPING_QUADS, SHOW_OVERLAPPING_QUADS;
/** LOD colors are based on renderSource flags. */
SHOW_RENDER_SOURCE_FLAG;
public static EDhApiDebugRendering next(EDhApiDebugRendering type) public static EDhApiDebugRendering next(EDhApiDebugRendering type)
{ {
@@ -60,7 +56,7 @@ public enum EDhApiDebugRendering
case SHOW_BLOCK_MATERIAL: case SHOW_BLOCK_MATERIAL:
return SHOW_OVERLAPPING_QUADS; return SHOW_OVERLAPPING_QUADS;
case SHOW_OVERLAPPING_QUADS: case SHOW_OVERLAPPING_QUADS:
return SHOW_RENDER_SOURCE_FLAG; return OFF;
default: default:
return OFF; return OFF;
} }
@@ -71,8 +67,6 @@ public enum EDhApiDebugRendering
switch (type) switch (type)
{ {
case OFF: case OFF:
return SHOW_RENDER_SOURCE_FLAG;
case SHOW_RENDER_SOURCE_FLAG:
return SHOW_OVERLAPPING_QUADS; return SHOW_OVERLAPPING_QUADS;
case SHOW_OVERLAPPING_QUADS: case SHOW_OVERLAPPING_QUADS:
return SHOW_DETAIL; return SHOW_DETAIL;
@@ -20,17 +20,17 @@
package com.seibel.distanthorizons.api.enums.rendering; package com.seibel.distanthorizons.api.enums.rendering;
/** /**
* Default <br> * DEFAULT <br>
* Debug <br> * DEBUG_TRIANGLE <br>
* Disabled <br> * DISABLED <br>
* *
* @since API 2.0.0 * @since API 2.0.0
* @version 2024-4-6 * @version 2026-03-23
*/ */
public enum EDhApiRendererMode public enum EDhApiRendererMode
{ {
DEFAULT, DEFAULT,
DEBUG, DEBUG_TRIANGLE,
DISABLED; DISABLED;
@@ -40,8 +40,8 @@ public enum EDhApiRendererMode
switch (type) switch (type)
{ {
case DEFAULT: case DEFAULT:
return DEBUG; return DEBUG_TRIANGLE;
case DEBUG: case DEBUG_TRIANGLE:
return DISABLED; return DISABLED;
default: default:
return DEFAULT; return DEFAULT;
@@ -55,10 +55,10 @@ public enum EDhApiRendererMode
{ {
case DEFAULT: case DEFAULT:
return DISABLED; return DISABLED;
case DEBUG: case DEBUG_TRIANGLE:
return DEFAULT; return DEFAULT;
default: default:
return DEBUG; return DEBUG_TRIANGLE;
} }
} }
@@ -21,25 +21,13 @@ package com.seibel.distanthorizons.api.enums.rendering;
/** /**
* DISABLED, <br> * DISABLED, <br>
* FAKE, <br>
* COMPLETE, <br> * COMPLETE, <br>
* *
* @since API 2.0.0 * @since API 2.0.0
* @version 2024-4-6 * @version 2026-05-19
*/ */
public enum EDhApiTransparency public enum EDhApiTransparency
{ {
DISABLED(false, false), DISABLED,
FAKE(true, true), COMPLETE;
COMPLETE(true, false);
public final boolean transparencyEnabled;
public final boolean fakeTransparencyEnabled;
EDhApiTransparency(boolean transparencyEnabled, boolean fakeTransparencyEnabled)
{
this.transparencyEnabled = transparencyEnabled;
this.fakeTransparencyEnabled = fakeTransparencyEnabled;
}
} }
@@ -19,6 +19,8 @@
package com.seibel.distanthorizons.api.interfaces; package com.seibel.distanthorizons.api.interfaces;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
/** /**
* Implemented by wrappers so developers can * Implemented by wrappers so developers can
* access the underlying Minecraft object(s). * access the underlying Minecraft object(s).
@@ -38,7 +40,11 @@ public interface IDhApiUnsafeWrapper
* In order to cast this object to something usable, you may want * In order to cast this object to something usable, you may want
* to use <code>obj.getClass()</code> when in your IDE * to use <code>obj.getClass()</code> when in your IDE
* in order to determine what object this method returns for * in order to determine what object this method returns for
* the specific version of Minecraft you are developing for. * the specific version of Minecraft you are developing for. <br><br>
*
* Note:<br>
* This method may return null in some cases, IE {@link IDhApiBlockStateWrapper}
* when it is wrapping air.
*/ */
Object getWrappedMcObject(); Object getWrappedMcObject();
@@ -23,7 +23,10 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper; import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
/** /**
* A Minecraft version independent way of handling Blocks. * A Minecraft version independent way of handling Blocks. <br><br>
*
* Note: the wrapped object (IE the object returned by {@link IDhApiUnsafeWrapper#getWrappedMcObject})
* will be null if this object is wrapping air.
* *
* @author James Seibel * @author James Seibel
* @version 2023-6-11 * @version 2023-6-11
@@ -39,6 +42,12 @@ public interface IDhApiBlockStateWrapper extends IDhApiUnsafeWrapper
/** @since API 1.0.0 */ /** @since API 1.0.0 */
boolean isLiquid(); boolean isLiquid();
/**
* Returns a value between 0 (fully transparent) and 16 (fully opaque).
* @since 6.1.0
*/
int getOpacity();
/** /**
* Returns the full serialized form of the given block * Returns the full serialized form of the given block
* as defined by DH's serialization methods. * as defined by DH's serialization methods.
@@ -23,10 +23,10 @@ import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue; import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
/** /**
* Distant Horizons' fog configuration. <br><br> * Distant Horizons' SSAO configuration. <br><br>
* *
* @author James Seibel * @author James Seibel
* @version 2022-9-6 * @version 2026-02-05
* @since API 1.0.0 * @since API 1.0.0
*/ */
public interface IDhApiAmbientOcclusionConfig extends IDhApiConfigGroup public interface IDhApiAmbientOcclusionConfig extends IDhApiConfigGroup
@@ -34,32 +34,4 @@ public interface IDhApiAmbientOcclusionConfig extends IDhApiConfigGroup
/** Determines if Ambient Occlusion is rendered */ /** Determines if Ambient Occlusion is rendered */
IDhApiConfigValue<Boolean> enabled(); IDhApiConfigValue<Boolean> enabled();
/**
* Determines how many points in space are sampled for the occlusion test.
* Higher numbers will improve quality and reduce banding, but will increase GPU load.
*/
IDhApiConfigValue<Integer> sampleCount();
/** Determines the radius Screen Space Ambient Occlusion is applied, measured in blocks. */
IDhApiConfigValue<Double> radius();
/** Determines how dark the Screen Space Ambient Occlusion effect will be. */
IDhApiConfigValue<Double> strength();
/** Increasing the value can reduce banding at the cost of reducing the strength of the effect. */
IDhApiConfigValue<Double> bias();
/**
* Determines how dark the occlusion shadows can be. <br>
* 0 = totally black at the corners <br>
* 1 = no shadow
*/
IDhApiConfigValue<Double> minLight();
/**
* The radius, measured in pixels, that blurring is calculated. <br>
* Higher numbers will reduce banding at the cost of GPU performance.
*/
IDhApiConfigValue<Integer> blurRadius();
} }
@@ -44,7 +44,7 @@ public interface IDhApiFarFogConfig extends IDhApiConfigGroup
* 0.0 = fog starts at the camera <br> * 0.0 = fog starts at the camera <br>
* 1.0 = fog starts at the edge of the fake chunk render distance <br> * 1.0 = fog starts at the edge of the fake chunk render distance <br>
*/ */
IDhApiConfigValue<Double> farFogStartDistance(); IDhApiConfigValue<Float> farFogStartDistance();
/** /**
* Defines where the fog ends as a percent of the radius * Defines where the fog ends as a percent of the radius
@@ -54,18 +54,18 @@ public interface IDhApiFarFogConfig extends IDhApiConfigGroup
* 0.0 = fog ends at the camera <br> * 0.0 = fog ends at the camera <br>
* 1.0 = fog ends at the edge of the fake chunk render distance <br> * 1.0 = fog ends at the edge of the fake chunk render distance <br>
*/ */
IDhApiConfigValue<Double> farFogEndDistance(); IDhApiConfigValue<Float> farFogEndDistance();
/** Defines how opaque the fog is at its thinnest point. */ /** Defines how opaque the fog is at its thinnest point. */
IDhApiConfigValue<Double> farFogMinThickness(); IDhApiConfigValue<Float> farFogMinThickness();
/** Defines how opaque the fog is at its thickest point. */ /** Defines how opaque the fog is at its thickest point. */
IDhApiConfigValue<Double> farFogMaxThickness(); IDhApiConfigValue<Float> farFogMaxThickness();
/** Defines how the fog changes in thickness. */ /** Defines how the fog changes in thickness. */
IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff(); IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff();
/** Defines the fog density. */ /** Defines the fog density. */
IDhApiConfigValue<Double> farFogDensity(); IDhApiConfigValue<Float> farFogDensity();
} }
@@ -85,6 +85,18 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
/** Modifies the quadratic function fake chunks use for horizontal quality drop-off. */ /** Modifies the quadratic function fake chunks use for horizontal quality drop-off. */
IDhApiConfigValue<EDhApiHorizontalQuality> horizontalQuality(); IDhApiConfigValue<EDhApiHorizontalQuality> horizontalQuality();
/**
* If true DH will try to use the camera position when
* determining LOD quality drop-off. <br>
* If false DH will use the player's position.
* <br><br>
* Enabling helps free-cam mods render correctly. <br>
* Disabling helps multi-camera mods render correctly (ie Immersive Portals or camera mods).
*
* @since API 7.0.0
*/
IDhApiConfigValue<Boolean> useCameraPositionForQualityDropOff();
IDhApiConfigValue<EDhApiTransparency> transparency(); IDhApiConfigValue<EDhApiTransparency> transparency();
/** Defines what blocks won't be rendered as LODs. */ /** Defines what blocks won't be rendered as LODs. */
@@ -124,19 +136,19 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
* *
* @since API 2.0.0 * @since API 2.0.0
*/ */
IDhApiConfigValue<Double> overdrawPreventionRadius(); IDhApiConfigValue<Float> overdrawPreventionRadius();
/** /**
* Modifies how bright fake chunks are. <br> * Modifies how bright fake chunks are. <br>
* This is done when generating the vertex data and is applied before any shaders. * This is done when generating the vertex data and is applied before any shaders.
*/ */
IDhApiConfigValue<Double> brightnessMultiplier(); IDhApiConfigValue<Float> brightnessMultiplier();
/** /**
* Modifies how saturated fake chunks are. <br> * Modifies how saturated fake chunks are. <br>
* This is done when generating the vertex data and is applied before any shaders. * This is done when generating the vertex data and is applied before any shaders.
*/ */
IDhApiConfigValue<Double> saturationMultiplier(); IDhApiConfigValue<Float> saturationMultiplier();
/** Defines if Distant Horizons should attempt to cull fake chunk cave geometry. */ /** Defines if Distant Horizons should attempt to cull fake chunk cave geometry. */
IDhApiConfigValue<Boolean> caveCullingEnabled(); IDhApiConfigValue<Boolean> caveCullingEnabled();
@@ -150,12 +162,6 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
/** If enabled vanilla chunk rendering is disabled and only fake chunks are rendered. */ /** If enabled vanilla chunk rendering is disabled and only fake chunks are rendered. */
IDhApiConfigValue<Boolean> lodOnlyMode(); IDhApiConfigValue<Boolean> lodOnlyMode();
/**
* Setting this to a non-zero number will modify vanilla Minecraft's LOD Bias,
* increasing how quickly its textures fade away.
*/
IDhApiConfigValue<Double> lodBias();
/** /**
* Determines how LODs should be shaded. * Determines how LODs should be shaded.
* *
@@ -52,24 +52,24 @@ public interface IDhApiHeightFogConfig extends IDhApiConfigGroup
* Defines the height fog's base height if {@link IDhApiHeightFogConfig#heightFogDirection()} * Defines the height fog's base height if {@link IDhApiHeightFogConfig#heightFogDirection()}
* is set to use a specific height. * is set to use a specific height.
*/ */
IDhApiConfigValue<Double> heightFogBaseHeight(); IDhApiConfigValue<Float> heightFogBaseHeight();
/** Defines the height fog's starting height as a percent of the world height. */ /** Defines the height fog's starting height as a percent of the world height. */
IDhApiConfigValue<Double> heightFogStartingHeightPercent(); IDhApiConfigValue<Float> heightFogStartingHeightPercent();
/** Defines the height fog's ending height as a percent of the world height. */ /** Defines the height fog's ending height as a percent of the world height. */
IDhApiConfigValue<Double> heightFogEndingHeightPercent(); IDhApiConfigValue<Float> heightFogEndingHeightPercent();
/** Defines how opaque the height fog is at its thinnest point. */ /** Defines how opaque the height fog is at its thinnest point. */
IDhApiConfigValue<Double> heightFogMinThickness(); IDhApiConfigValue<Float> heightFogMinThickness();
/** Defines how opaque the height fog is at its thickest point. */ /** Defines how opaque the height fog is at its thickest point. */
IDhApiConfigValue<Double> heightFogMaxThickness(); IDhApiConfigValue<Float> heightFogMaxThickness();
/** Defines how the height fog changes in thickness. */ /** Defines how the height fog changes in thickness. */
IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff(); IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff();
/** Defines the height fog's density. */ /** Defines the height fog's density. */
IDhApiConfigValue<Double> heightFogDensity(); IDhApiConfigValue<Float> heightFogDensity();
} }
@@ -37,8 +37,8 @@ public interface IDhApiNoiseTextureConfig extends IDhApiConfigGroup
/** Defines how many steps of noise should be applied. */ /** Defines how many steps of noise should be applied. */
IDhApiConfigValue<Integer> noiseSteps(); IDhApiConfigValue<Integer> noiseSteps();
/** Defines how intense the noise will be. */ /** Defines how intense the noise will be, between 0.0 and 1.0. */
IDhApiConfigValue<Double> noiseIntensity(); IDhApiConfigValue<Float> noiseIntensity();
/** /**
* Defines how far should the noise texture render before it fades away. (in blocks) <br> * Defines how far should the noise texture render before it fades away. (in blocks) <br>
@@ -1,15 +1,18 @@
package com.seibel.distanthorizons.api.interfaces.data; package com.seibel.distanthorizons.api.interfaces.data;
/** /**
* Can be used to drastically speed up repeat read operations in {@link IDhApiTerrainDataRepo}. * Can be used to drastically speed up repeat read operations in {@link IDhApiTerrainDataRepo}. <br><br>
*
* Once you are done with this cache, closing it will free up any objects
* the cache is holding. This can reduce Garbage Collector overhead and reduce stuttering.
* *
* @see IDhApiTerrainDataRepo * @see IDhApiTerrainDataRepo
* *
* @author James Seibel * @author James Seibel
* @version 2024-7-14 * @version 2026-1-29
* @since API 3.0.0 * @since API 3.0.0
*/ */
public interface IDhApiTerrainDataCache // TODO should this be AutoClosable? public interface IDhApiTerrainDataCache extends AutoCloseable
{ {
/** /**
* Removes any data that's currently stored in this cache. * Removes any data that's currently stored in this cache.
@@ -21,4 +24,10 @@ public interface IDhApiTerrainDataCache // TODO should this be AutoClosable?
*/ */
void clear(); void clear();
// override without an exception
@Override
void close();
} }
@@ -32,7 +32,7 @@ import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint;
* @see IDhApiTerrainDataCache * @see IDhApiTerrainDataCache
* *
* @author James Seibel * @author James Seibel
* @version 2023-6-22 * @version 2026-02-03
* @since API 1.0.0 * @since API 1.0.0
*/ */
public interface IDhApiTerrainDataRepo public interface IDhApiTerrainDataRepo
@@ -42,24 +42,17 @@ public interface IDhApiTerrainDataRepo
// getters // // getters //
//=========// //=========//
/** @see IDhApiTerrainDataRepo#getSingleDataPointAtBlockPos(IDhApiLevelWrapper, int, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ) { return this.getSingleDataPointAtBlockPos(levelWrapper, blockPosX, blockPosY, blockPosZ, null); }
/** /**
* Returns the terrain datapoint at the given block position, at/or containing the given Y position. * Returns the terrain datapoint at the given block position, at/or containing the given Y position.
* @since API 3.0.0 * @since API 3.0.0
*/ */
DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, IDhApiTerrainDataCache dataCache); DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, IDhApiTerrainDataCache dataCache);
/** @see IDhApiTerrainDataRepo#getColumnDataAtBlockPos(IDhApiLevelWrapper, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ) { return this.getColumnDataAtBlockPos(levelWrapper, blockPosX, blockPosZ, null); }
/** /**
* Returns every datapoint in the column located at the given block X and Z position top to bottom. * Returns every datapoint in the column located at the given block X and Z position top to bottom.
* @since API 3.0.0 * @since API 3.0.0
*/ */
DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, IDhApiTerrainDataCache dataCache); DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, IDhApiTerrainDataCache dataCache);
/** @see IDhApiTerrainDataRepo#getAllTerrainDataAtChunkPos(IDhApiLevelWrapper, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ) { return this.getAllTerrainDataAtChunkPos(levelWrapper, chunkPosX, chunkPosZ, null); }
/** /**
* Returns every datapoint in the given chunk's X and Z position. <br><br> * Returns every datapoint in the given chunk's X and Z position. <br><br>
* *
@@ -71,8 +64,6 @@ public interface IDhApiTerrainDataRepo
*/ */
DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, IDhApiTerrainDataCache dataCache); DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, IDhApiTerrainDataCache dataCache);
/** @see IDhApiTerrainDataRepo#getAllTerrainDataAtRegionPos(IDhApiLevelWrapper, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ) { return this.getAllTerrainDataAtRegionPos(levelWrapper, regionPosX, regionPosZ, null); }
/** /**
* Returns every datapoint in the given region's X and Z position. <br><br> * Returns every datapoint in the given region's X and Z position. <br><br>
* *
@@ -84,8 +75,6 @@ public interface IDhApiTerrainDataRepo
*/ */
DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, IDhApiTerrainDataCache dataCache); DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, IDhApiTerrainDataCache dataCache);
/** @see IDhApiTerrainDataRepo#getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper, byte, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ) { return this.getAllTerrainDataAtDetailLevelAndPos(levelWrapper, detailLevel, posX, posZ, null); }
/** /**
* Returns every datapoint in the column located at the given detail level and X/Z position. <br> * Returns every datapoint in the column located at the given detail level and X/Z position. <br>
* This can be used to return terrain data for non-standard sizes (IE 2x2 blocks or 2x2 chunks). * This can be used to return terrain data for non-standard sizes (IE 2x2 blocks or 2x2 chunks).
@@ -101,21 +90,6 @@ public interface IDhApiTerrainDataRepo
/** @see IDhApiTerrainDataRepo#raycast(IDhApiLevelWrapper, double, double, double, float, float, float, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiRaycastResult> raycast(
IDhApiLevelWrapper levelWrapper,
double rayOriginX, double rayOriginY, double rayOriginZ,
float rayDirectionX, float rayDirectionY, float rayDirectionZ,
int maxRayBlockLength)
{
return this.raycast(
levelWrapper,
rayOriginX, rayOriginY, rayOriginZ,
rayDirectionX, rayDirectionY, rayDirectionZ,
maxRayBlockLength,
null);
}
/** /**
* Returns the datapoint and position of the LOD * Returns the datapoint and position of the LOD
* at the end of the given ray. <br><br> * at the end of the given ray. <br><br>
@@ -40,8 +40,7 @@ public interface IDhApiEventInjector extends IDependencyInjector<IDhApiEvent>
* @return true if the handler was unbound, false if the handler wasn't bound. * @return true if the handler was unbound, false if the handler wasn't bound.
* @throws IllegalArgumentException if the implementation object doesn't implement the interface * @throws IllegalArgumentException if the implementation object doesn't implement the interface
*/ */
// Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it // Note to DH Devs: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it
// TODO why are we removing the class instead of an instance?
boolean unbind(Class<? extends IDhApiEvent> dependencyInterface, Class<? extends IDhApiEvent> dependencyClassToRemove) throws IllegalArgumentException; boolean unbind(Class<? extends IDhApiEvent> dependencyInterface, Class<? extends IDhApiEvent> dependencyClassToRemove) throws IllegalArgumentException;
@@ -19,9 +19,21 @@
package com.seibel.distanthorizons.api.interfaces.override.rendering; package com.seibel.distanthorizons.api.interfaces.override.rendering;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingEngine;
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable; import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
/** /**
* <b>Note: </b><br>
* This is only used if Distant Horizons'
* {@link IDhApiRenderProxy#getRenderingApi()} returns {@link EDhApiRenderingEngine#OPEN_GL}
* and {@link IDhApiRenderProxy#isNativeRenderer()} returns true.
* ie this is only used when DH is doing native OpenGL rendering,
* if DH is using Blaze3D this interface will be ignored.
*
* @see IDhApiRenderProxy#getRenderingApi()
* @see IDhApiRenderProxy#isNativeRenderer()
*
* @author James Seibel * @author James Seibel
* @version 2024-1-24 * @version 2024-1-24
* @since API 2.0.0 * @since API 2.0.0
@@ -19,7 +19,9 @@
package com.seibel.distanthorizons.api.interfaces.override.rendering; package com.seibel.distanthorizons.api.interfaces.override.rendering;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingEngine;
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable; import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup; import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d; import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
@@ -27,7 +29,16 @@ import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading; import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
/** /**
* <b>Note: </b><br>
* This is only used if Distant Horizons'
* {@link IDhApiRenderProxy#getRenderingApi()} returns {@link EDhApiRenderingEngine#OPEN_GL}
* and {@link IDhApiRenderProxy#isNativeRenderer()} returns true.
* ie this is only used when DH is doing native OpenGL rendering,
* if DH is using Blaze3D this interface will be ignored.
*
* @see IDhApiShaderProgram * @see IDhApiShaderProgram
* @see IDhApiRenderProxy#getRenderingApi()
* @see IDhApiRenderProxy#isNativeRenderer()
* *
* @author James Seibel * @author James Seibel
* @version 2024-7-11 * @version 2024-7-11
@@ -19,12 +19,23 @@
package com.seibel.distanthorizons.api.interfaces.override.rendering; package com.seibel.distanthorizons.api.interfaces.override.rendering;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingEngine;
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable; import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f; import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
/** /**
* <b>Note: </b><br>
* This is only used if Distant Horizons'
* {@link IDhApiRenderProxy#getRenderingApi()} returns {@link EDhApiRenderingEngine#OPEN_GL}
* and {@link IDhApiRenderProxy#isNativeRenderer()} returns true.
* ie this is only used when DH is doing native OpenGL rendering,
* if DH is using Blaze3D this interface will be ignored.
*
* @see IDhApiGenericObjectShaderProgram * @see IDhApiGenericObjectShaderProgram
* @see IDhApiRenderProxy#getRenderingApi()
* @see IDhApiRenderProxy#isNativeRenderer()
* *
* @author James Seibel * @author James Seibel
* @version 2024-1-24 * @version 2024-1-24
@@ -3,6 +3,7 @@ package com.seibel.distanthorizons.api.interfaces.render;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d; import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f; import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox; import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import java.util.List; import java.util.List;
@@ -18,7 +19,7 @@ import java.util.List;
* @version 2024-7-3 * @version 2024-7-3
* @since API 3.0.0 * @since API 3.0.0
*/ */
public interface IDhApiCustomRenderObjectFactory public interface IDhApiCustomRenderObjectFactory extends IBindable
{ {
/** /**
* Creates a {@link IDhApiRenderableBoxGroup} from for the given {@link DhApiRenderableBox} * Creates a {@link IDhApiRenderableBoxGroup} from for the given {@link DhApiRenderableBox}
@@ -19,6 +19,9 @@
package com.seibel.distanthorizons.api.interfaces.render; package com.seibel.distanthorizons.api.interfaces.render;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingApi;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingEngine;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiAfterDhInitEvent;
import com.seibel.distanthorizons.api.objects.DhApiResult; import com.seibel.distanthorizons.api.objects.DhApiResult;
@@ -44,6 +47,33 @@ public interface IDhApiRenderProxy
*/ */
DhApiResult<Boolean> clearRenderDataCache(); DhApiResult<Boolean> clearRenderDataCache();
/**
* Returns which specific {@link EDhApiRenderingApi}
* Distant Horizons will use for rendering. <br><br>
*
* @throws IllegalStateException if no renderer has been bound yet,
* wait till after {@link DhApiAfterDhInitEvent} has been fired
*
* @see DhApiAfterDhInitEvent
* @since API 7.0.0
*/
EDhApiRenderingApi getRenderingApi() throws IllegalStateException;
/**
* Returns true if the current renderer
* is calling the base rendering API's method calls. <br>
* ie GL.drawArrays() for OpenGL. <Br><br>
*
* If DH is using a rendering interpretation layer like Blaze3D (Mojang's rendering API)
* this will return false.
*
* @throws IllegalStateException if no renderer has been bound yet,
* wait till after {@link DhApiAfterDhInitEvent} has been fired
*
* @see DhApiAfterDhInitEvent
* @since API 7.0.0
*/
boolean isNativeRenderer() throws IllegalStateException;
//=======================// //=======================//
@@ -51,14 +81,16 @@ public interface IDhApiRenderProxy
//=======================// //=======================//
/** /**
* Returns the name of Distant Horizons' depth texture. <br> * Returns the OpenGL name of Distant Horizons' depth texture. <br>
* Will return {@link DhApiResult#success} = false and {@link DhApiResult#payload} = -1 if the texture hasn't been created yet. * Will return {@link DhApiResult#success} = false and {@link DhApiResult#payload} = -1 if the texture hasn't been created yet
* or a rendering API other than OpenGL is in use.
*/ */
DhApiResult<Integer> getDhDepthTextureId(); DhApiResult<Integer> getDhDepthTextureId();
/** /**
* Returns the name of Distant Horizons' color texture. <br> * Returns the OpenGL name of Distant Horizons' color texture. <br>
* Will return {@link DhApiResult#success} = false and {@link DhApiResult#payload} = -1 if the texture hasn't been created yet. * Will return {@link DhApiResult#success} = false and {@link DhApiResult#payload} = -1 if the texture hasn't been created yet
* or a rendering API other than OpenGL is in use
*/ */
DhApiResult<Integer> getDhColorTextureId(); DhApiResult<Integer> getDhColorTextureId();
@@ -73,7 +73,7 @@ public interface IDhApiRenderableBoxGroup extends List<DhApiRenderableBox>
* This is a good place to change the origin or notify of any box changes. * This is a good place to change the origin or notify of any box changes.
*/ */
void setPreRenderFunc(Consumer<DhApiRenderParam> renderEventParam); void setPreRenderFunc(Consumer<DhApiRenderParam> renderEventParam);
void setPostRenderFunc(Consumer<DhApiRenderParam> renderEventParam); // TODO name? void setPostRenderFunc(Consumer<DhApiRenderParam> renderEventParam);
/** /**
* If a cube's color, position, or other property is changed this method * If a cube's color, position, or other property is changed this method
@@ -21,8 +21,14 @@ package com.seibel.distanthorizons.api.interfaces.world;
import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper; import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiLevelType; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiLevelType;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegister; import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegister;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBlockColorOverrideEvent;
import com.seibel.distanthorizons.api.objects.DhApiResult;
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
import java.awt.*;
import java.io.File; import java.io.File;
/** /**
@@ -90,6 +96,26 @@ public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
*/ */
File getDhSaveFolder(); File getDhSaveFolder();
/**
* Returns the color DH would use for the given block/biome
* pair at the given world position before any API color overrides
* are considered. <br>
* API color overrides are ignored to prevent infinite
* loops if this event is triggered inside said API override.
* <br><br>
*
* Returns {@link DhApiResult#success} = false if {@link IDhApiLevelWrapper#getLevelType()} returns a {@link EDhApiLevelType#SERVER_LEVEL}
* (server levels have no concept of textures or colors).
*
* @see DhApiBlockColorOverrideEvent
* @since API 7.0.0
*/
DhApiResult<Color> getBlockColorPreApi(
IDhApiBlockStateWrapper blockStateWrapper,
IDhApiBiomeWrapper biomeWrapper,
int blockWorldPosX, int blockWorldPosY, int blockWorldPosZ,
IDhApiFullDataSource dataSource);
} }
@@ -27,7 +27,7 @@ import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
/** /**
* Called before Distant Horizons starts rendering a buffer. <br> * Called before Distant Horizons starts rendering a buffer. <br>
* This event cannot be cancelled, use {@link DhApiBeforeRenderEvent} if you want to cancel rendering. * This event cannot be canceled, use {@link DhApiBeforeRenderEvent} if you want to cancel rendering.
* *
* @author James Seibel * @author James Seibel
* @version 2023-1-31 * @version 2023-1-31
@@ -59,23 +59,25 @@ public abstract class DhApiBeforeBufferRenderEvent implements IDhApiEvent<DhApiB
* Measured in blocks. * Measured in blocks.
* Should be applied to the model view matrix to move the buffer into its proper place. * Should be applied to the model view matrix to move the buffer into its proper place.
*/ */
public final DhApiVec3f modelPos; public DhApiVec3f modelPos;
public EventParam(DhApiRenderParam parent, DhApiVec3f modelPos)
public EventParam() { }
public void update(DhApiRenderParam parent, DhApiVec3f modelPos)
{ {
super(parent); super.update(parent);
this.modelPos = modelPos; this.modelPos = modelPos;
} }
@Override @Override
public EventParam copy() public boolean getCopyBeforeFire() { return false; }
{
return new EventParam( @Override
this, this.modelPos.copy() public EventParam copy() { return this; }
);
}
} }
} }
@@ -31,7 +31,10 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
* @author James Seibel * @author James Seibel
* @version 2025-6-9 * @version 2025-6-9
* @since API 4.1.0 * @since API 4.1.0
* @deprecated Only used for the legacy OpenGL renderer <Br>
* Using {@link DhApiAfterColorDepthTextureCreatedEvent} instead is recommended.
*/ */
@Deprecated
public abstract class DhApiBeforeColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiTextureCreatedParam> public abstract class DhApiBeforeColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiTextureCreatedParam>
{ {
/** Fired before Distant Horizons creates. */ /** Fired before Distant Horizons creates. */
@@ -0,0 +1,128 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiCancelableEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiFogRenderParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiMutableFogRenderParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
/**
* Fired before DH renders its fog.
* Canceling this event disables fog for that frame.
*
* @author James Seibel
* @version 2026-05-20
* @since API 7.0.0
*/
public abstract class DhApiBeforeFogRenderEvent implements IDhApiCancelableEvent<DhApiBeforeFogRenderEvent.EventParam>
{
/** Fired before fog is generated. */
public abstract void beforeRender(DhApiCancelableEventParam<DhApiBeforeFogRenderEvent.EventParam> event);
//=========================//
// internal DH API methods //
//=========================//
@Override
public final void fireEvent(DhApiCancelableEventParam<DhApiBeforeFogRenderEvent.EventParam> event) { this.beforeRender(event); }
//==================//
// parameter object //
//==================//
public static class EventParam implements IDhApiEventParam
{
private DhApiRenderParam renderParam;
private DhApiFogRenderParam originalFogRenderParam;
private DhApiMutableFogRenderParam fogRenderParam;
//=============//
// constructor //
//=============//
//region
public EventParam() {}
public void update(DhApiRenderParam renderParam, DhApiFogRenderParam fogRenderParam)
{
this.renderParam = renderParam;
this.originalFogRenderParam = fogRenderParam;
this.fogRenderParam = new DhApiMutableFogRenderParam(fogRenderParam);
}
//endregion
//=================//
// getters/setters //
//=================//
//region
public DhApiRenderParam getRenderParam() { return this.renderParam; }
/** immutable, stores what DH would do without API intervention so API users have a reference point */
public DhApiFogRenderParam getOriginalFogRenderParam() { return this.originalFogRenderParam; }
/** mutable, can be modified by API users */
public DhApiMutableFogRenderParam getFogRenderParam() { return this.fogRenderParam; }
//endregion
//==========================//
// base api event overrides //
//==========================//
//region
/**
* Returns the same instance of this event.
* Copying this event isn't supported
* since the internal parameters must be mutated
* by API users in order to be tracked by DH's internal
* logic.
*/
@Override
public DhApiBeforeFogRenderEvent.EventParam copy() { return this; }
@Override
public boolean getCopyBeforeFire() { return false; }
//endregion
}
}
@@ -54,44 +54,45 @@ public abstract class DhApiBeforeGenericObjectRenderEvent implements IDhApiCance
public static class EventParam extends DhApiRenderParam implements IDhApiEventParam public static class EventParam extends DhApiRenderParam implements IDhApiEventParam
{ {
public final long boxGroupId; public long boxGroupId;
public final String resourceLocationNamespace; public String resourceLocationNamespace;
public final String resourceLocationPath; public String resourceLocationPath;
public EventParam(
DhApiRenderParam renderParam, //==============//
IDhApiRenderableBoxGroup boxGroup // constructors //
) //==============//
//region
public EventParam() { }
/** internal DH method */
public void update(DhApiRenderParam renderParam, IDhApiRenderableBoxGroup boxGroup)
{ {
super(renderParam); super.update(renderParam);
this.boxGroupId = boxGroup.getId(); this.boxGroupId = boxGroup.getId();
this.resourceLocationNamespace = boxGroup.getResourceLocationNamespace(); this.resourceLocationNamespace = boxGroup.getResourceLocationNamespace();
this.resourceLocationPath = boxGroup.getResourceLocationPath(); this.resourceLocationPath = boxGroup.getResourceLocationPath();
} }
public EventParam(
DhApiRenderParam renderParam,
long boxGroupId, String resourceLocationNamespace, String resourceLocationPath
)
{
super(renderParam);
this.boxGroupId = boxGroupId; //endregion
this.resourceLocationNamespace = resourceLocationNamespace;
this.resourceLocationPath = resourceLocationPath;
}
//================//
// base overrides //
//================//
//region
@Override @Override
public EventParam copy() public EventParam copy() { return this; }
{
return new EventParam( //endregion
this,
this.boxGroupId, this.resourceLocationNamespace, this.resourceLocationPath
);
}
} }
} }
@@ -0,0 +1,187 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
/**
* Performance note: this event will be fired millions of times on concurrent threads,
* make it thread safe and as fast as possible. <br>
* (If every LOD block goes through this event, On a 512 render distance world,
* at the medium quality preset, it will be triggered around 40,000,000 times.)
* <Br><Br>
*
* This event is fired when DH needs to convert a {@link IDhApiBlockStateWrapper}
* into a color for rendering. This event is fired after DH attempts to determine
* the color itself (using the base color, tinting, etc.). <Br>
* Using this event will override the tinting config for this block
* unless you re-implement that logic yourself.
* <Br><Br>
*
* This event will only trigger for {@link IDhApiBlockStateWrapper}s that have been registered
* via {@link DhApiBlockStateWrapperCreatedEvent.EventParam#setAllowApiColorOverride(boolean)}.
*
* @author James Seibel
* @version 2026-05-18
* @since API 6.0.0
* @see IDhApiBlockStateWrapper
*/
public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent<DhApiBlockColorOverrideEvent.EventParam>
{
public abstract void onBlockColorOverridden(DhApiEventParam<EventParam> event);
//=========================//
// internal DH API methods //
//=========================//
@Override
public final void fireEvent(DhApiEventParam<EventParam> event) { this.onBlockColorOverridden(event); }
//==================//
// parameter object //
//==================//
public static class EventParam implements IDhApiEventParam
{
private IDhApiLevelWrapper levelWrapper;
private IDhApiFullDataSource dataSource;
private IDhApiBlockStateWrapper blockStateWrapper = null;
private IDhApiBiomeWrapper biomeWrapper = null;
private int colorAsInt = -1;
private int blockPosX = 0, blockPosY = 0, blockPosZ = 0;
//=============//
// constructor //
//=============//
//region
public EventParam() {}
public void update(
IDhApiLevelWrapper levelWrapper,
IDhApiFullDataSource dataSource,
IDhApiBlockStateWrapper blockStateWrapper,
IDhApiBiomeWrapper biomeWrapper,
int colorAsInt,
int blockPosX, int blockPosY, int blockPosZ)
{
this.levelWrapper = levelWrapper;
this.dataSource = dataSource;
this.blockStateWrapper = blockStateWrapper;
this.biomeWrapper = biomeWrapper;
this.colorAsInt = colorAsInt;
this.blockPosX = blockPosX;
this.blockPosY = blockPosY;
this.blockPosZ = blockPosZ;
}
//endregion
//=================//
// getters/setters //
//=================//
//region
public IDhApiBlockStateWrapper getBlockStateWrapper() { return this.blockStateWrapper; }
/** @since API 7.0.0 */
public IDhApiBiomeWrapper getBiomeWrapper() { return this.biomeWrapper; }
/** the level DH is resolving this block's color in. */
public IDhApiLevelWrapper getLevelWrapper() { return this.levelWrapper; }
/**
* The DH datasource that contains this block's position. Can be used to access adjacent
* {@link IDhApiBlockStateWrapper}'s and {@link IDhApiBiomeWrapper}'s for adjacent aware tinting.
* @since API 7.0.0
*/
public IDhApiFullDataSource getDataSource() { return this.dataSource; }
public int getColorAsInt() { return this.colorAsInt; }
public int getAlpha() { return ColorUtil.getAlpha(this.colorAsInt); }
public int getRed() { return ColorUtil.getRed(this.colorAsInt); }
public int getGreen() { return ColorUtil.getGreen(this.colorAsInt); }
public int getBlue() { return ColorUtil.getBlue(this.colorAsInt); }
public void setColor(int red, int green, int blue) throws IllegalArgumentException { this.setColor(this.getAlpha(), red, green, blue); }
/**
* Note: when if you set a partially transparent alpha channel the underlying {@link IDhApiBlockStateWrapper#getOpacity()}
* method should also return a non-opaque value.
* Otherwise LODs may behave incorrectly.
*/
public void setColor(int alpha, int red, int green, int blue) throws IllegalArgumentException
{
ColorUtil.throwIfColorValueOutOfIntRange("alpha", alpha);
ColorUtil.throwIfColorValueOutOfIntRange("red", red);
ColorUtil.throwIfColorValueOutOfIntRange("green", green);
ColorUtil.throwIfColorValueOutOfIntRange("blue", blue);
this.colorAsInt = ColorUtil.argbToInt(alpha, red, green, blue);
}
/** @return the block's X value in the world */
public int getBlockPosX() { return blockPosX; }
/** @return the block's Y value in the world */
public int getBlockPosY() { return blockPosY; }
/** @return the block's Z value in the world */
public int getBlockPosZ() { return blockPosZ; }
//endregion
//==========================//
// base api event overrides //
//==========================//
//region
/**
* Returns the same instance of this event.
* Copying this event isn't supported
* since the internal parameters must be mutated
* by API users in order to be tracked by DH's internal
* logic.
*/
@Override
public EventParam copy() { return this; }
@Override
public boolean getCopyBeforeFire() { return false; }
//endregion
}
}
@@ -0,0 +1,131 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
/**
* Can be used to modify {@link IDhApiBlockStateWrapper}'s as they're created.
* This can be helpful for modded blocks that are mis-categorized by DH's base logic. <br><br>
*
* Note: this is only fired once per {@link IDhApiBlockStateWrapper} that is created
* and those {@link IDhApiBlockStateWrapper} will only be created once per JVM session.
*
* @author James Seibel
* @version 2026-04-14
* @since API 6.0.0
* @see IDhApiBlockStateWrapper
*/
public abstract class DhApiBlockStateWrapperCreatedEvent implements IDhApiEvent<DhApiBlockStateWrapperCreatedEvent.EventParam>
{
public abstract void blockStateWrapperCreated(DhApiEventParam<EventParam> event);
//=========================//
// internal DH API methods //
//=========================//
@Override
public final void fireEvent(DhApiEventParam<EventParam> event) { this.blockStateWrapperCreated(event); }
//==================//
// parameter object //
//==================//
public static class EventParam implements IDhApiEventParam
{
/**
* A copy of the wrapper that will be created. <Br>
* Note: modifying this object won't change anything
* a new wrapper will be created after this event finishes.
*/
private final IDhApiBlockStateWrapper blockStateWrapper;
private boolean overridesSet = false;
private EDhApiBlockMaterial blockMaterial = null;
private Integer opacity = null;
private Boolean allowApiColorOverride = null;
//=============//
// constructor //
//=============//
public EventParam(IDhApiBlockStateWrapper blockStateWrapper) { this.blockStateWrapper = blockStateWrapper; }
//=================//
// getters/setters //
//=================//
public IDhApiBlockStateWrapper getBlockStateWrapper() { return this.blockStateWrapper; }
/** if set this will override the value currently set in the given {@link IDhApiBlockStateWrapper} */
public void setBlockMaterial(EDhApiBlockMaterial blockMaterial)
{
this.blockMaterial = blockMaterial;
this.overridesSet = true;
}
public EDhApiBlockMaterial getBlockMaterial() { return this.blockMaterial; }
/** if set this will override the value currently set in the given {@link IDhApiBlockStateWrapper} */
public void setOpacity(int opacity)
{
this.opacity = opacity;
this.overridesSet = true;
}
public Integer getOpacity() { return this.opacity; }
/** if set to true this {@link IDhApiBlockStateWrapper} will trigger {@link DhApiBlockColorOverrideEvent} */
public void setAllowApiColorOverride(boolean allowApiColorOverride)
{
this.allowApiColorOverride = allowApiColorOverride;
this.overridesSet = true;
}
public Boolean getAllowApiColorOverride() { return this.allowApiColorOverride; }
/** If true then one or more options for this block were set to be changed */
public boolean getOverridesSet() { return this.overridesSet; }
/**
* Returns the same instance of this event.
* Copying this event isn't supported
* since the internal parameters must be mutated
* by API users in order to be tracked by DH's internal
* logic.
*/
@Override
public EventParam copy() { return this; }
@Override
public boolean getCopyBeforeFire() { return false; }
}
}
@@ -34,6 +34,7 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
* @author James Seibel * @author James Seibel
* @version 2023-6-23 * @version 2023-6-23
* @see IDhApiTerrainDataRepo * @see IDhApiTerrainDataRepo
* @see DhApiChunkProcessingEvent
* @since API 1.0.0 * @since API 1.0.0
*/ */
public abstract class DhApiChunkModifiedEvent implements IDhApiEvent<DhApiChunkModifiedEvent.EventParam> public abstract class DhApiChunkModifiedEvent implements IDhApiEvent<DhApiChunkModifiedEvent.EventParam>
@@ -51,6 +51,7 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
* *
* @author James Seibel * @author James Seibel
* @version 2025-09-29 * @version 2025-09-29
* @see DhApiChunkModifiedEvent
* @since API 4.1.0 * @since API 4.1.0
*/ */
public abstract class DhApiChunkProcessingEvent implements IDhApiEvent<DhApiChunkProcessingEvent.EventParam> public abstract class DhApiChunkProcessingEvent implements IDhApiEvent<DhApiChunkProcessingEvent.EventParam>
@@ -31,7 +31,9 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
* @author James Seibel * @author James Seibel
* @version 2024-3-2 * @version 2024-3-2
* @since API 2.0.0 * @since API 2.0.0
* @deprecated Replaced by {@link DhApiBeforeColorDepthTextureCreatedEvent} since this event's name isn't obvious when it fires. * @deprecated Only used for the legacy OpenGL renderer <Br>
* Replaced by {@link DhApiBeforeColorDepthTextureCreatedEvent} since this event's name isn't obvious when it fires.
* Using {@link DhApiAfterColorDepthTextureCreatedEvent} instead is recommended
*/ */
@Deprecated // internal notes: this method must be kept around due to Iris using it and we don't want to break old Iris support @Deprecated // internal notes: this method must be kept around due to Iris using it and we don't want to break old Iris support
public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiColorDepthTextureCreatedEvent.EventParam> public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiColorDepthTextureCreatedEvent.EventParam>
@@ -0,0 +1,202 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.api.methods.events.sharedParameterObjects;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogFalloff;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogDirection;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMixMode;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiFarFogConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiFogConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiHeightFogConfig;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import java.awt.*;
/**
* Contains all the information needed to render Distant Horizons' fog.
*
* @see IDhApiFogConfig
* @see IDhApiFarFogConfig
* @see IDhApiHeightFogConfig
*
* @author James Seibel
* @version 2026-05-20
* @since API 7.0.0
*/
public class DhApiFogRenderParam implements IDhApiEventParam
{
protected Color fogColor;
public Color getFogColor() { return this.fogColor; }
// far fog //
//region
protected EDhApiFogFalloff farFogFalloff;
/** @see IDhApiFarFogConfig#farFogFalloff() */
public EDhApiFogFalloff getFarFogFalloff() { return this.farFogFalloff; }
protected float farFogStartPercent;
/** @see IDhApiFarFogConfig#farFogStartDistance() */
public float getFarFogStartPercent() { return this.farFogStartPercent; }
protected float farFogEndPercent;
/** @see IDhApiFarFogConfig#farFogEndDistance() */
public float getFarFogEndPercent() { return this.farFogEndPercent; }
protected float farFogMinThickness;
/** @see IDhApiFarFogConfig#farFogMinThickness() */
public float getFarFogMinThickness() { return this.farFogMinThickness; }
protected float farFogMaxThickness;
/** @see IDhApiFarFogConfig#farFogMaxThickness() */
public float getFarFogMaxThickness() { return this.farFogMaxThickness; }
protected float farFogDensity;
/** @see IDhApiFarFogConfig#farFogDensity() */
public float getFarFogDensity() { return this.farFogDensity; }
//endregion
// height fog //
//region
protected EDhApiFogFalloff heightFogFalloff;
/** @see IDhApiHeightFogConfig#heightFogFalloff() */
public EDhApiFogFalloff getHeightFogFalloff() { return this.heightFogFalloff; }
protected EDhApiHeightFogMixMode heightFogMixingMode;
/** @see IDhApiHeightFogConfig#heightFogMixMode() */
public EDhApiHeightFogMixMode getHeightFogMixingMode() { return this.heightFogMixingMode; }
protected EDhApiHeightFogDirection heightFogDirection;
/** @see IDhApiHeightFogConfig#heightFogDirection() */
public EDhApiHeightFogDirection getHeightFogDirection() { return this.heightFogDirection; }
protected float heightFogBaseHeight;
/** @see IDhApiHeightFogConfig#heightFogBaseHeight() */
public float getHeightFogBaseHeight() { return this.heightFogBaseHeight; }
protected float heightFogStartPercent;
/** @see IDhApiHeightFogConfig#heightFogStartingHeightPercent() */
public float getHeightFogStartPercent() { return this.heightFogStartPercent; }
protected float heightFogEndPercent;
/** @see IDhApiHeightFogConfig#heightFogEndingHeightPercent() */
public float getHeightFogEndPercent() { return this.heightFogEndPercent; }
protected float heightFogMinThickness;
/** @see IDhApiHeightFogConfig#heightFogMinThickness() */
public float getHeightFogMinThickness() { return this.heightFogMinThickness; }
protected float heightFogMaxThickness;
/** @see IDhApiHeightFogConfig#heightFogMaxThickness() */
public float getHeightFogMaxThickness() { return this.heightFogMaxThickness; }
protected float heightFogDensity;
/** @see IDhApiHeightFogConfig#heightFogDensity() */
public float getHeightFogDensity() { return this.heightFogDensity; }
//endregion
//==============//
// constructors //
//==============//
//region
public DhApiFogRenderParam(DhApiFogRenderParam parent)
{
this(
parent.fogColor,
// far fog
parent.farFogFalloff,
parent.farFogStartPercent, parent.farFogEndPercent,
parent.farFogMinThickness, parent.farFogEndPercent,
parent.farFogDensity,
// height fog
parent.heightFogFalloff,
parent.heightFogMixingMode, parent.heightFogDirection,
parent.heightFogBaseHeight,
parent.heightFogStartPercent, parent.heightFogEndPercent,
parent.heightFogMinThickness, parent.heightFogMaxThickness,
parent.heightFogDensity
);
}
public DhApiFogRenderParam(
Color fogColor,
// far fog
EDhApiFogFalloff farFogFalloff,
float farFogStartPercent, float farFogEndPercent,
float farFogMinThickness, float farFogMaxThickness,
float farFogDensity,
// height fog
EDhApiFogFalloff heightFogFalloff,
EDhApiHeightFogMixMode heightFogMixingMode, EDhApiHeightFogDirection heightFogDirection,
float heightFogBaseHeight,
float heightFogStartPercent, float heightFogEndPercent,
float heightFogMinThickness, float heightFogMaxThickness,
float heightFogDensity
)
{
this.fogColor = fogColor;
// far fog
this.farFogFalloff = farFogFalloff;
this.farFogStartPercent = farFogStartPercent;
this.farFogEndPercent = farFogEndPercent;
this.farFogMinThickness = farFogMinThickness;
this.farFogMaxThickness = farFogMaxThickness;
this.farFogDensity = farFogDensity;
// height fog
this.heightFogFalloff = heightFogFalloff;
this.heightFogMixingMode = heightFogMixingMode;
this.heightFogDirection = heightFogDirection;
this.heightFogBaseHeight = heightFogBaseHeight;
this.heightFogStartPercent = heightFogStartPercent;
this.heightFogEndPercent = heightFogEndPercent;
this.heightFogMinThickness = heightFogMinThickness;
this.heightFogMaxThickness = heightFogMaxThickness;
this.heightFogDensity = heightFogDensity;
}
//endregion
//================//
// base overrides //
//================//
//region
@Override
public DhApiFogRenderParam copy() { return new DhApiFogRenderParam(this); }
//endregion
}
@@ -0,0 +1,117 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.api.methods.events.sharedParameterObjects;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogFalloff;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogDirection;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMixMode;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiFarFogConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiFogConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiHeightFogConfig;
import java.awt.*;
/**
* A mutable version of {@link DhApiFogRenderParam} to allow
* API modification of DH's fog rendering.
*
* @see IDhApiFogConfig
* @see IDhApiFarFogConfig
* @see IDhApiHeightFogConfig
* @see DhApiFogRenderParam
*
* @author James Seibel
* @version 2026-05-20
* @since API 7.0.0
*/
public class DhApiMutableFogRenderParam extends DhApiFogRenderParam
{
public void setFogColor(Color fogColor) { this.fogColor = fogColor; }
// far fog //
//region
/** @see IDhApiFarFogConfig#farFogFalloff() */
public void setFarFogFalloff(EDhApiFogFalloff farFogFalloff) { this.farFogFalloff = farFogFalloff; }
/** @see IDhApiFarFogConfig#farFogStartDistance() */
public void setFarFogStartPercent(float farFogStartPercent) { this.farFogStartPercent = farFogStartPercent; }
/** @see IDhApiFarFogConfig#farFogEndDistance() */
public void setFarFogEndPercent(float farFogEndPercent) { this.farFogEndPercent = farFogEndPercent; }
/** @see IDhApiFarFogConfig#farFogMinThickness() */
public void setFarFogMinThickness(float farFogMinThickness) { this.farFogMinThickness = farFogMinThickness; }
/** @see IDhApiFarFogConfig#farFogMaxThickness() */
public void setFarFogMaxThickness(float farFogMaxThickness) { this.farFogMaxThickness = farFogMaxThickness; }
/** @see IDhApiFarFogConfig#farFogDensity() */
public void setFarFogDensity(float farFogDensity) { this.farFogDensity = farFogDensity; }
//endregion
// height fog //
//region
/** @see IDhApiHeightFogConfig#heightFogFalloff() */
public void setHeightFogFalloff(EDhApiFogFalloff heightFogFalloff) { this.heightFogFalloff = heightFogFalloff; }
/** @see IDhApiHeightFogConfig#heightFogMixMode() */
public void setHeightFogMixingMode(EDhApiHeightFogMixMode heightFogMixingMode) { this.heightFogMixingMode = heightFogMixingMode; }
/** @see IDhApiHeightFogConfig#heightFogDirection() */
public void setHeightFogDirection(EDhApiHeightFogDirection heightFogDirection) { this.heightFogDirection = heightFogDirection; }
/** @see IDhApiHeightFogConfig#heightFogBaseHeight() */
public void setHeightFogBaseHeight(float heightFogBaseHeight) { this.heightFogBaseHeight = heightFogBaseHeight; }
/** @see IDhApiHeightFogConfig#heightFogStartingHeightPercent() */
public void setHeightFogStartPercent(float heightFogStartPercent) { this.heightFogStartPercent = heightFogStartPercent; }
/** @see IDhApiHeightFogConfig#heightFogEndingHeightPercent() */
public void setHeightFogEndPercent(float heightFogEnd) { this.heightFogEndPercent = heightFogEnd; }
/** @see IDhApiHeightFogConfig#heightFogMinThickness() */
public void setHeightFogMinThickness(float heightFogMinThickness) { this.heightFogMinThickness = heightFogMinThickness; }
/** @see IDhApiHeightFogConfig#heightFogMaxThickness() */
public void setHeightFogMaxThickness(float heightFogMaxThickness) { this.heightFogMaxThickness = heightFogMaxThickness; }
/** @see IDhApiHeightFogConfig#heightFogDensity() */
public void setHeightFogDensity(float heightFogDensity) { this.heightFogDensity = heightFogDensity; }
//endregion
//==============//
// constructors //
//==============//
//region
public DhApiMutableFogRenderParam(DhApiFogRenderParam parent)
{ super(parent); }
//endregion
//================//
// base overrides //
//================//
//region
@Override
public DhApiMutableFogRenderParam copy() { return new DhApiMutableFogRenderParam(this); }
//endregion
}
@@ -35,60 +35,69 @@ import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
public class DhApiRenderParam implements IDhApiEventParam public class DhApiRenderParam implements IDhApiEventParam
{ {
/** Indicates what render pass DH is currently rendering */ /** Indicates what render pass DH is currently rendering */
public final EDhApiRenderPass renderPass; public EDhApiRenderPass renderPass;
/** Indicates how far into this tick the frame is. */ /** Indicates how far into this tick the frame is. */
public final float partialTicks; public float partialTicks;
/** /**
* Indicates DH's near clip plane, measured in blocks. * Indicates DH's near clip plane, measured in blocks.
* Note: this may change based on time, player speed, and other factors. * Note: this may change based on time, player speed, and other factors.
*/ */
public final float nearClipPlane; public float nearClipPlane;
/** /**
* Indicates DH's far clip plane, measured in blocks. * Indicates DH's far clip plane, measured in blocks.
* Note: this may change based on time, player speed, and other factors. * Note: this may change based on time, player speed, and other factors.
*/ */
public final float farClipPlane; public float farClipPlane;
/** The projection matrix Minecraft is using to render this frame. */ /** The projection matrix Minecraft is using to render this frame. */
public final DhApiMat4f mcProjectionMatrix; public final DhApiMat4f mcProjectionMatrix = new DhApiMat4f();
/** The model view matrix Minecraft is using to render this frame. */ /** The model view matrix Minecraft is using to render this frame. */
public final DhApiMat4f mcModelViewMatrix; public final DhApiMat4f mcModelViewMatrix = new DhApiMat4f();
public final DhApiMat4f mcInverseMvmProjectionMatrix = new DhApiMat4f();
/** The projection matrix Distant Horizons is using to render this frame. */ /** The projection matrix Distant Horizons is using to render this frame. */
public final DhApiMat4f dhProjectionMatrix; public final DhApiMat4f dhProjectionMatrix = new DhApiMat4f();
/** The model view matrix Distant Horizons is using to render this frame. */ /** The model view matrix Distant Horizons is using to render this frame. */
public final DhApiMat4f dhModelViewMatrix; public final DhApiMat4f dhModelViewMatrix = new DhApiMat4f();
/** combination of the MVM and projection matrices */
public final DhApiMat4f dhMvmProjMatrix = new DhApiMat4f();
public final DhApiMat4f dhInverseMvmProjectionMatrix = new DhApiMat4f();
public final int worldYOffset; public int worldYOffset;
/** /**
* The level currently being rendered. * The level currently being rendered.
* *
* @since API 5.1.0 * @since API 5.1.0
*/ */
public final IDhApiLevelWrapper clientLevelWrapper; public IDhApiLevelWrapper clientLevelWrapper;
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
//region
public DhApiRenderParam(DhApiRenderParam parent) public DhApiRenderParam() {}
/** Internal DH method */
public void update(DhApiRenderParam param)
{ {
this( this.update(
parent.renderPass, param.renderPass,
parent.partialTicks, param.partialTicks,
parent.nearClipPlane, parent.farClipPlane, param.nearClipPlane, param.farClipPlane,
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(), param.mcProjectionMatrix, param.mcModelViewMatrix,
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(), param.dhProjectionMatrix, param.mcModelViewMatrix,
parent.worldYOffset, param.worldYOffset,
parent.clientLevelWrapper param.clientLevelWrapper
); );
} }
public DhApiRenderParam( /** Internal DH method */
public void update(
EDhApiRenderPass renderPass, EDhApiRenderPass renderPass,
float newPartialTicks, float newPartialTicks,
float nearClipPlane, float farClipPlane, float nearClipPlane, float farClipPlane,
@@ -105,25 +114,51 @@ public class DhApiRenderParam implements IDhApiEventParam
this.farClipPlane = farClipPlane; this.farClipPlane = farClipPlane;
this.nearClipPlane = nearClipPlane; this.nearClipPlane = nearClipPlane;
this.mcProjectionMatrix = newMcProjectionMatrix; // mc matricies
this.mcModelViewMatrix = newMcModelViewMatrix; {
this.mcProjectionMatrix.set(newMcProjectionMatrix);
this.mcModelViewMatrix.set(newMcModelViewMatrix);
this.dhProjectionMatrix = newDhProjectionMatrix; // inverse mvm Proj
this.dhModelViewMatrix = newDhModelViewMatrix; this.mcInverseMvmProjectionMatrix.set(newMcProjectionMatrix);
this.mcInverseMvmProjectionMatrix.invert();
}
// dh matricies
{
this.dhProjectionMatrix.set(newDhProjectionMatrix);
this.dhModelViewMatrix.set(newDhModelViewMatrix);
// proj
this.dhMvmProjMatrix.set(this.dhProjectionMatrix);
this.dhMvmProjMatrix.multiply(this.dhModelViewMatrix);
// inverse mvm Proj
this.dhInverseMvmProjectionMatrix.set(this.dhMvmProjMatrix);
this.dhInverseMvmProjectionMatrix.invert();
}
this.worldYOffset = worldYOffset; this.worldYOffset = worldYOffset;
this.clientLevelWrapper = clientLevelWrapper; this.clientLevelWrapper = clientLevelWrapper;
} }
//endregion
//================// //================//
// base overrides // // base overrides //
//================// //================//
//region
@Override @Override
public DhApiRenderParam copy() { return new DhApiRenderParam(this); } public boolean getCopyBeforeFire() { return false; }
@Override
public DhApiRenderParam copy() { return this; }
//endregion
@@ -46,7 +46,7 @@ public class DhApiResult<T>
// these constructors are private because the create... methods below are easier to understand // these constructors are private because the create methods below are easier to understand
private DhApiResult(boolean success, String message) { this(success, message, null); } private DhApiResult(boolean success, String message) { this(success, message, null); }
private DhApiResult(boolean success, String message, T payload) private DhApiResult(boolean success, String message, T payload)
{ {
@@ -21,6 +21,8 @@ package com.seibel.distanthorizons.api.objects.math;
import com.seibel.distanthorizons.api.interfaces.util.IDhApiCopyable; import com.seibel.distanthorizons.api.interfaces.util.IDhApiCopyable;
import java.nio.FloatBuffer;
/** /**
* An (almost) exact copy of Minecraft's 1.16.5 * An (almost) exact copy of Minecraft's 1.16.5
* implementation of a 4x4 float matrix. <br><br> * implementation of a 4x4 float matrix. <br><br>
@@ -33,7 +35,7 @@ import com.seibel.distanthorizons.api.interfaces.util.IDhApiCopyable;
* </code> * </code>
* *
* @author James Seibel * @author James Seibel
* @version 2024-6-30 * @version 2026-05-22
*/ */
public class DhApiMat4f implements IDhApiCopyable public class DhApiMat4f implements IDhApiCopyable
{ {
@@ -62,6 +64,7 @@ public class DhApiMat4f implements IDhApiCopyable
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
//region
public DhApiMat4f() { /* all values are 0 */ } public DhApiMat4f() { /* all values are 0 */ }
@@ -71,14 +74,17 @@ public class DhApiMat4f implements IDhApiCopyable
this.m01 = sourceMatrix.m01; this.m01 = sourceMatrix.m01;
this.m02 = sourceMatrix.m02; this.m02 = sourceMatrix.m02;
this.m03 = sourceMatrix.m03; this.m03 = sourceMatrix.m03;
this.m10 = sourceMatrix.m10; this.m10 = sourceMatrix.m10;
this.m11 = sourceMatrix.m11; this.m11 = sourceMatrix.m11;
this.m12 = sourceMatrix.m12; this.m12 = sourceMatrix.m12;
this.m13 = sourceMatrix.m13; this.m13 = sourceMatrix.m13;
this.m20 = sourceMatrix.m20; this.m20 = sourceMatrix.m20;
this.m21 = sourceMatrix.m21; this.m21 = sourceMatrix.m21;
this.m22 = sourceMatrix.m22; this.m22 = sourceMatrix.m22;
this.m23 = sourceMatrix.m23; this.m23 = sourceMatrix.m23;
this.m30 = sourceMatrix.m30; this.m30 = sourceMatrix.m30;
this.m31 = sourceMatrix.m31; this.m31 = sourceMatrix.m31;
this.m32 = sourceMatrix.m32; this.m32 = sourceMatrix.m32;
@@ -109,12 +115,74 @@ public class DhApiMat4f implements IDhApiCopyable
this.m33 = values[15]; this.m33 = values[15];
} }
//endregion
//=========// //=========//
// methods // // methods //
//=========// //=========//
//region
/** Returns the values of this matrix in row major order (AKA rows then columns) */
public float[] getValuesAsArray()
{
float[] array = new float[16];
this.putValuesInArray(array);
return array;
}
/**
* Returns the values of this matrix in row major order (AKA rows then columns)
* @since API 7.0.0
*/
public void putValuesInArray(float[] array)
{
array[0] = this.m00;
array[1] = this.m01;
array[2] = this.m02;
array[3] = this.m03;
array[4] = this.m10;
array[5] = this.m11;
array[6] = this.m12;
array[7] = this.m13;
array[8] = this.m20;
array[9] = this.m21;
array[10] = this.m22;
array[11] = this.m23;
array[12] = this.m30;
array[13] = this.m31;
array[14] = this.m32;
array[15] = this.m33;
}
/** @since API 7.0.0 */
public void set(DhApiMat4f mat)
{
this.m00 = mat.m00;
this.m01 = mat.m01;
this.m02 = mat.m02;
this.m03 = mat.m03;
this.m10 = mat.m10;
this.m11 = mat.m11;
this.m12 = mat.m12;
this.m13 = mat.m13;
this.m20 = mat.m20;
this.m21 = mat.m21;
this.m22 = mat.m22;
this.m23 = mat.m23;
this.m30 = mat.m30;
this.m31 = mat.m31;
this.m32 = mat.m32;
this.m33 = mat.m33;
}
public void setIdentity() public void setIdentity()
{ {
@@ -279,47 +347,14 @@ public class DhApiMat4f implements IDhApiCopyable
this.m33 *= scalar; this.m33 *= scalar;
} }
//endregion
//==================//
// Distant Horizons //
// methods //
//==================//
private static int getArrayIndex(int xIndex, int zIndex) { return (zIndex * 4) + xIndex; }
/** Returns the values of this matrix in row major order (AKA rows then columns) */
public float[] getValuesAsArray()
{
return new float[]{
this.m00,
this.m01,
this.m02,
this.m03,
this.m10,
this.m11,
this.m12,
this.m13,
this.m20,
this.m21,
this.m22,
this.m23,
this.m30,
this.m31,
this.m32,
this.m33,
};
}
//================// //================//
// base overrides // // base overrides //
//================// //================//
//region
@Override @Override
public boolean equals(Object obj) public boolean equals(Object obj)
@@ -388,4 +423,8 @@ public class DhApiMat4f implements IDhApiCopyable
@Override @Override
public DhApiMat4f copy() { return new DhApiMat4f(this); } public DhApiMat4f copy() { return new DhApiMat4f(this); }
//endregion
} }
@@ -23,6 +23,7 @@ public class DhApiRenderableBox
public DhApiVec3d maxPos; public DhApiVec3d maxPos;
public Color color; public Color color;
/** @see EDhApiBlockMaterial */
public byte material; public byte material;
@@ -35,7 +35,19 @@ import java.util.Map;
*/ */
public class DependencyInjector<BindableType extends IBindable> implements IDependencyInjector<BindableType> // Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it public class DependencyInjector<BindableType extends IBindable> implements IDependencyInjector<BindableType> // Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it
{ {
protected final Map<Class<? extends BindableType>, ArrayList<BindableType>> dependencies = new HashMap<>(); /**
* empty list is to reduce GC pressure slightly in the common path
* that {@link DependencyInjector#getInternalLogic(Class, boolean)} is called
* when nothing has been bound.
*/
private static final ArrayList<?> EMPTY_GET_ALL_LIST = new ArrayList<>();
static
{
EMPTY_GET_ALL_LIST.add(null);
}
protected final HashMap<Class<? extends BindableType>, ArrayList<BindableType>> dependencies = new HashMap<>();
/** Internal class reference to BindableType since we can't get it any other way. */ /** Internal class reference to BindableType since we can't get it any other way. */
protected final Class<? extends BindableType> bindableInterface; protected final Class<? extends BindableType> bindableInterface;
@@ -43,9 +55,11 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
protected final boolean allowDuplicateBindings; protected final boolean allowDuplicateBindings;
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
//region
public DependencyInjector(Class<BindableType> newBindableInterface, boolean newAllowDuplicateBindings) public DependencyInjector(Class<BindableType> newBindableInterface, boolean newAllowDuplicateBindings)
{ {
@@ -53,11 +67,14 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
this.allowDuplicateBindings = newAllowDuplicateBindings; this.allowDuplicateBindings = newAllowDuplicateBindings;
} }
//endregion
//=========// //=========//
// binding // // binding //
//=========// //=========//
//region
@Override @Override
public void bind(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException public void bind(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException
@@ -69,6 +86,11 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
throw new IllegalStateException("The dependency [" + dependencyInterface.getSimpleName() + "] has already been bound."); throw new IllegalStateException("The dependency [" + dependencyInterface.getSimpleName() + "] has already been bound.");
} }
if (dependencyImplementation == null)
{
throw new NullPointerException("Can't bind null to ["+dependencyInterface.getSimpleName()+"]");
}
// make sure the given dependency implements the necessary interfaces // make sure the given dependency implements the necessary interfaces
boolean implementsInterface = this.checkIfClassImplements(dependencyImplementation.getClass(), dependencyInterface) || boolean implementsInterface = this.checkIfClassImplements(dependencyImplementation.getClass(), dependencyInterface) ||
@@ -131,13 +153,27 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
@Override @Override
public boolean checkIfClassExtends(Class<?> classToTest, Class<?> extensionToLookFor) { return extensionToLookFor.isAssignableFrom(classToTest); } public boolean checkIfClassExtends(Class<?> classToTest, Class<?> extensionToLookFor) { return extensionToLookFor.isAssignableFrom(classToTest); }
//endregion
//===========// //===========//
// unbinding // // unbinding //
//===========// //===========//
//region
public void replaceBinding(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException
{
this.unbindAll(dependencyInterface);
this.bind(dependencyInterface, dependencyImplementation);
}
public void unbindAll(Class<? extends BindableType> dependencyInterface) throws IllegalStateException, IllegalArgumentException
{
// remove the dependency if present
this.dependencies.remove(dependencyInterface);
}
// TODO having a bindOrReplace method would probably be better since it wouldn't have the possiblity of having nothing bound
public void unbind(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException public void unbind(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException
{ {
// check if this object is bound // check if this object is bound
@@ -174,30 +210,27 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
this.dependencies.remove(dependencyInterface); this.dependencies.remove(dependencyInterface);
} }
//endregion
//=========// //=========//
// getters // // getters //
//=========// //=========//
//region
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public <T extends BindableType> T get(Class<T> interfaceClass) throws ClassCastException public <T extends BindableType> T get(Class<T> interfaceClass) throws ClassCastException
{ { return (T) this.getInternalLogic(interfaceClass, false).get(0); }
return (T) this.getInternalLogic(interfaceClass, false).get(0);
}
@Override @Override
public <T extends BindableType> ArrayList<T> getAll(Class<T> interfaceClass) throws ClassCastException public <T extends BindableType> ArrayList<T> getAll(Class<T> interfaceClass) throws ClassCastException
{ { return this.getInternalLogic(interfaceClass, false); }
return this.getInternalLogic(interfaceClass, false);
}
@Override @Override
public <T extends BindableType> T get(Class<T> interfaceClass, boolean allowIncompleteDependencies) throws ClassCastException public <T extends BindableType> T get(Class<T> interfaceClass, boolean allowIncompleteDependencies) throws ClassCastException
{ { return (T) this.getInternalLogic(interfaceClass, allowIncompleteDependencies).get(0); }
return (T) this.getInternalLogic(interfaceClass, allowIncompleteDependencies).get(0);
}
/** /**
* Always returns a list of size 1 or greater, * Always returns a list of size 1 or greater,
@@ -225,11 +258,10 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
// return an empty list to prevent null pointers // return an empty list to prevent null pointers
ArrayList<T> emptyList = new ArrayList<T>(); return (ArrayList<T>)EMPTY_GET_ALL_LIST;
emptyList.add(null);
return emptyList;
} }
//endregion
/** Removes all bound dependencies. */ /** Removes all bound dependencies. */
@@ -31,26 +31,31 @@ public final class ModInfo
public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial"; public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial";
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */ /** Incremented every time any packets are added, changed or removed, with a few exceptions. */
public static final int PROTOCOL_VERSION = 13; public static final int PROTOCOL_VERSION = 15;
public static final String WRAPPER_PACKET_PATH = "message";
/**
* The full plugin channel name (RESOURCE_NAMESPACE:WRAPPER_PACKET_PATH)
* must be 20 characters or fewer for compatibility with MC 1.13 and older.
*/
public static final String WRAPPER_PACKET_PATH = "msg";
/** The internal mod name */ /** The internal mod name */
public static final String NAME = "DistantHorizons"; public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */ /** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons"; public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.4.6-b-dev"; public static final String VERSION = "3.0.4-b-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */ /** Returns true if the current build is an unstable developer build, false otherwise. */
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev"); public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
/** This version should only be updated when breaking changes are introduced to the DH API */ /** This version should only be updated when breaking changes are introduced to the DH API */
public static final int API_MAJOR_VERSION = 5; public static final int API_MAJOR_VERSION = 7;
/** This version should be updated whenever new methods are added to the DH API */ /** This version should be updated whenever new methods are added to the DH API */
public static final int API_MINOR_VERSION = 1; public static final int API_MINOR_VERSION = 0;
/** This version should be updated whenever non-breaking fixes are added to the DH API */ /** This version should be updated whenever non-breaking fixes are added to the DH API */
public static final int API_PATCH_VERSION = 0; public static final int API_PATCH_VERSION = 0;
/** If the config file has an older version it'll be re-created from scratch. */ /** If the config file has an older version it'll be re-created from scratch. */
public static final int CONFIG_FILE_VERSION = 3; public static final int CONFIG_FILE_VERSION = 4;
/** All DH owned threads should start with this string to allow for easier debugging and profiling. */ /** All DH owned threads should start with this string to allow for easier debugging and profiling. */
public static final String THREAD_NAME_PREFIX = "DH-"; public static final String THREAD_NAME_PREFIX = "DH-";
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.seibel.distanthorizons.core.util; package com.seibel.distanthorizons.coreapi.util;
import java.awt.*; import java.awt.*;
@@ -89,6 +89,16 @@ public class ColorUtil
/** @param newBlue should be a value between 0 and 255 */ /** @param newBlue should be a value between 0 and 255 */
public static int setBlue(int color, int newBlue) { return (getAlpha(color) << 24) | (getRed(color) << 16) | (getGreen(color) << 8) | newBlue; } public static int setBlue(int color, int newBlue) { return (getAlpha(color) << 24) | (getRed(color) << 16) | (getGreen(color) << 8) | newBlue; }
/** @throws IllegalArgumentException if the given int value is out of the range 0 - 255 (exclusive) */
public static void throwIfColorValueOutOfIntRange(String colorName, int value) throws IllegalArgumentException
{
if (value < 0
|| value > 255)
{
throw new IllegalArgumentException("["+colorName+"] with the value ["+value+"] is out of the expected range 0 - 255 (exclusive).");
}
}
public static int applyShade(int color, int shade) public static int applyShade(int color, int shade)
+44 -30
View File
@@ -1,18 +1,23 @@
plugins { plugins {
id "java" id "java"
id "com.github.johnrengelman.shadow" version '8.1.1' apply false // Set this to true if you're using the standalone Core project id "com.gradleup.shadow"
} }
apply plugin: "application" repositories {
mavenCentral()
maven { url "https://repo.enonic.com/public/" }
}
application { tasks.withType(JavaCompile).configureEach {
mainClass.set("com.seibel.distanthorizons.core.jar.JarMain") options.release = 8
options.encoding = "UTF-8"
} }
configurations { configurations {
shadowedArtifact // Used by DH to specify that we want to implement the shadowed core JAR file instead of the regular JAR file shadowedArtifact // Used by DH to specify that we want to implement the shadowed core JAR file instead of the regular JAR file
shade shade
implementation.extendsFrom shade implementation.extendsFrom shade
testImplementation.extendsFrom compileOnly
} }
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem; OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
@@ -20,36 +25,45 @@ OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePl
// Set the OS lwjgl is using to the current os // Set the OS lwjgl is using to the current os
project.ext.lwjglNatives = "natives-" + os.toFamilyName() project.ext.lwjglNatives = "natives-" + os.toFamilyName()
dependencies { // All of these dependencies are in Vanilla Minecraft, but we need to depend on it as we arent importing Minecraft in the core dependencies {
// Imports most of lwjgl's libraries (well, only the ones that we need) // API project dependency
implementation platform("org.lwjgl:lwjgl-bom:${rootProject.lwjgl_version}") // TODO: Use Minecraft's version for lwjgl_version (which changes in nearly every version) instead of a hard defined version for all versions implementation project(":api")
// REMEMBER: Dont shadow stuff here, these are just the libs that are included in Minecraft so that the core can use // MC-provided libraries (available at runtime via Minecraft)
implementation "org.lwjgl:lwjgl" compileOnly platform("org.lwjgl:lwjgl-bom:${rootProject.lwjgl_version}")
implementation "org.lwjgl:lwjgl-assimp" compileOnly "org.lwjgl:lwjgl"
implementation "org.lwjgl:lwjgl-glfw" compileOnly "org.lwjgl:lwjgl-assimp"
implementation "org.lwjgl:lwjgl-openal" compileOnly "org.lwjgl:lwjgl-glfw"
implementation "org.lwjgl:lwjgl-opengl" compileOnly "org.lwjgl:lwjgl-stb"
implementation "org.lwjgl:lwjgl-stb" compileOnly "org.lwjgl:lwjgl-tinyfd"
implementation "org.lwjgl:lwjgl-tinyfd" testRuntimeOnly platform("org.lwjgl:lwjgl-bom:${rootProject.lwjgl_version}")
runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives" testRuntimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-assimp::$lwjglNatives" testRuntimeOnly "org.lwjgl:lwjgl-assimp::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives" testRuntimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives" testRuntimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives" testRuntimeOnly "org.lwjgl:lwjgl-tinyfd::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-tinyfd::$lwjglNatives"
// FIXME for some reason this line doesn't actually shade in the library compileOnly("org.apache.logging.log4j:log4j-api:${rootProject.log4j_version}")
// shade "it.unimi.dsi:fastutil:${rootProject.fastutil_version}" // Add our own fastutil version compileOnly("org.apache.logging.log4j:log4j-core:${rootProject.log4j_version}")
compileOnly("it.unimi.dsi:fastutil:${rootProject.fastutil_version}")
compileOnly("org.joml:joml:${rootProject.joml_version}")
compileOnly("io.netty:netty-buffer:${rootProject.netty_version}")
compileOnly("org.jetbrains:annotations:16.0.2")
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
compileOnly("com.google.guava:guava:31.1-jre")
// DH's bundled libraries (shadowed + relocated in loader jars)
implementation("com.github.luben:zstd-jni:${rootProject.zstd_version}")
implementation("org.lz4:lz4-java:${rootProject.lz4_version}")
implementation("org.tukaani:xz:${rootProject.xz_version}")
implementation("org.xerial:sqlite-jdbc:${rootProject.sqlite_jdbc_version}")
implementation("com.electronwill.night-config:toml:${rootProject.nightconfig_version}")
implementation("com.electronwill.night-config:json:${rootProject.nightconfig_version}")
// Some other dependencies // JUnit (core tests only)
implementation("org.jetbrains:annotations:16.0.2") compileOnly("junit:junit:4.13")
implementation("com.google.code.findbugs:jsr305:3.0.2") compileOnly("org.junit.jupiter:junit-jupiter:5.8.2")
implementation("com.google.common:google-collect:0.5") compileOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2")
implementation("com.google.guava:guava:31.1-jre")
} }
artifacts { artifacts {
@@ -20,12 +20,14 @@
package com.seibel.distanthorizons.core; package com.seibel.distanthorizons.core;
import com.github.luben.zstd.ZstdOutputStream; import com.github.luben.zstd.ZstdOutputStream;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderObjectFactory;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent; import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler; import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
import com.seibel.distanthorizons.core.sql.DatabaseUpdater; import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.world.DhApiWorldProxy; import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
@@ -34,7 +36,6 @@ import com.seibel.distanthorizons.core.api.external.methods.data.DhApiTerrainDat
import com.seibel.distanthorizons.api.DhApi; import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy; import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import net.jpountz.lz4.LZ4FrameOutputStream; import net.jpountz.lz4.LZ4FrameOutputStream;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.sqlite.SQLiteJDBCLoader; import org.sqlite.SQLiteJDBCLoader;
@@ -53,8 +54,13 @@ public class Initializer
public static void init() public static void preConfigInit()
{ {
//============================//
// check referenced libraries //
//============================//
//region
LOGGER.info("Running library validation..."); LOGGER.info("Running library validation...");
// confirm that all referenced libraries are available to use // confirm that all referenced libraries are available to use
@@ -94,7 +100,15 @@ public class Initializer
MC_CLIENT.crashMinecraft("Distant Horizons critical setup error: One or more libraries are either in-accessible, corrupted, or overwritten by another mod. Error: [" + e.getMessage() + "].", e); MC_CLIENT.crashMinecraft("Distant Horizons critical setup error: One or more libraries are either in-accessible, corrupted, or overwritten by another mod. Error: [" + e.getMessage() + "].", e);
} }
// confirm the resource directory is present //endregion
//==========================//
// check resource directory //
//==========================//
//region
try try
{ {
int scriptCount = DatabaseUpdater.getAutoUpdateScriptCount(); int scriptCount = DatabaseUpdater.getAutoUpdateScriptCount();
@@ -108,6 +122,15 @@ public class Initializer
MC_CLIENT.crashMinecraft("Critical programmer error: Can't read SQL Scripts resource folder is either missing or malformed. Error: [" + e.getMessage() + "].", e); MC_CLIENT.crashMinecraft("Critical programmer error: Can't read SQL Scripts resource folder is either missing or malformed. Error: [" + e.getMessage() + "].", e);
} }
//endregion
//===========================//
// Java AWT Headless setting //
//===========================//
//region
// This code has been disabled since it can cause Mac // This code has been disabled since it can cause Mac
// to lock up and refuse the load (there's a bug with Java.awt texture loading) // to lock up and refuse the load (there's a bug with Java.awt texture loading)
//if (MC_CLIENT != null) //if (MC_CLIENT != null)
@@ -124,18 +147,48 @@ public class Initializer
// } // }
//} //}
//endregion
//===================//
// API delayed setup //
//===================//
//region
// link Core's config to the API // link Core's config to the API
DhApi.Delayed.configs = DhApiConfig.INSTANCE; DhApi.Delayed.configs = DhApiConfig.INSTANCE;
DhApi.Delayed.terrainRepo = DhApiTerrainDataRepo.INSTANCE; DhApi.Delayed.terrainRepo = DhApiTerrainDataRepo.INSTANCE;
DhApi.Delayed.worldProxy = DhApiWorldProxy.INSTANCE; DhApi.Delayed.worldProxy = DhApiWorldProxy.INSTANCE;
DhApi.Delayed.renderProxy = DhApiRenderProxy.INSTANCE; DhApi.Delayed.renderProxy = DhApiRenderProxy.INSTANCE;
DhApi.Delayed.customRenderObjectFactory = GenericRenderObjectFactory.INSTANCE;
DhApi.Delayed.wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class); DhApi.Delayed.wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
if (DhApi.Delayed.wrapperFactory == null) if (DhApi.Delayed.wrapperFactory == null)
{ {
LOGGER.error("Programmer Error: No ["+IWrapperFactory.class.getSimpleName()+"] assigned to the DhApi."); MC_CLIENT.crashMinecraft("Programmer Error: No ["+IWrapperFactory.class.getSimpleName()+"] assigned to the DhApi.", new Exception());
} }
DhApi.Delayed.customRenderObjectFactory = SingletonInjector.INSTANCE.get(IDhApiCustomRenderObjectFactory.class);
if (DhApi.Delayed.customRenderObjectFactory == null)
{
MC_CLIENT.crashMinecraft("Programmer Error: No ["+IDhApiCustomRenderObjectFactory.class.getSimpleName()+"] assigned to the DhApi.", new Exception());
}
DhApi.events.bind(DhApiBeforeRenderEvent.class, IgnoredDimensionCsvHandler.INSTANCE);
//endregion
}
/** fired after DH's config has been set up */
public static void postConfigInit()
{
//==============================//
// G1 Garbage collector warning //
//==============================//
//region
// log a warning if G1GC is being used // log a warning if G1GC is being used
// (this garbage collector is known to cause stuttering) // (this garbage collector is known to cause stuttering)
{ {
@@ -160,21 +213,37 @@ public class Initializer
LOGGER.info("Garbage collectors: ["+garbageCollectorNames+"]"); LOGGER.info("Garbage collectors: ["+garbageCollectorNames+"]");
if (g1GcInUse if (g1GcInUse)
&& Config.Common.Logging.Warning.logGarbageCollectorWarning.get()) {
String warningMessageHeader = "Distant Horizons: G1 Garbage collector detected.";
String warningMessageBody =
"This can cause FPS stuttering. \n" +
"It's recommended to use a concurrent garbage collector \n" +
"like ZGC (Java 21+) or Shenandoah (Java 8 through 17) \n" +
"for a smoother experience."
;
if (Config.Common.Logging.Warning.logGarbageCollectorWarning.get())
{ {
LOGGER.warn( LOGGER.warn(
"Distant Horizons: G1 Garbage collector detected. \n" + warningMessageHeader + "\n" +
"This garbage collector can cause FPS stuttering. \n" + warningMessageBody +
"It's recommended to use a concurrent garbage collector \n" +
"like ZGC (Java 21+) or Shenandoah (Java 8 through 17) for a smoother experience. \n" +
""); "");
}
if (Config.Common.Logging.Warning.showGarbageCollectorWarning.get())
{
ClientApi.INSTANCE.showChatMessageNextFrame(
MinecraftTextFormat.ORANGE + warningMessageHeader + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
warningMessageBody +
"");
}
} }
} }
DhApi.events.bind(DhApiBeforeRenderEvent.class, IgnoredDimensionCsvHandler.INSTANCE); //endregion
} }
} }
@@ -35,30 +35,6 @@ public class DhApiAmbientOcclusionConfig implements IDhApiAmbientOcclusionConfig
@Override @Override
public IDhApiConfigValue<Boolean> enabled() public IDhApiConfigValue<Boolean> enabled()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Ssao.enableSsao); } { return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.enableSsao); }
@Override
public IDhApiConfigValue<Integer> sampleCount()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.Ssao.sampleCount); }
@Override
public IDhApiConfigValue<Double> radius()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Ssao.radius); }
@Override
public IDhApiConfigValue<Double> strength()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Ssao.strength); }
@Override
public IDhApiConfigValue<Double> bias()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Ssao.bias); }
@Override
public IDhApiConfigValue<Double> minLight()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Ssao.minLight); }
@Override
public IDhApiConfigValue<Integer> blurRadius()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.Ssao.blurRadius); }
} }
@@ -34,7 +34,7 @@ public class DhApiDebuggingConfig implements IDhApiDebuggingConfig
public IDhApiConfigValue<EDhApiDebugRendering> debugRendering() public IDhApiConfigValue<EDhApiDebugRendering> debugRendering()
{ return new DhApiConfigValue<EDhApiDebugRendering, EDhApiDebugRendering>(Config.Client.Advanced.Debugging.debugRendering); } { return new DhApiConfigValue<EDhApiDebugRendering, EDhApiDebugRendering>(Config.Client.Advanced.Debugging.debugRenderingColors); }
public IDhApiConfigValue<Boolean> debugKeybindings() public IDhApiConfigValue<Boolean> debugKeybindings()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.enableDebugKeybindings); } { return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.enableDebugKeybindings); }
@@ -34,27 +34,27 @@ public class DhApiFarFogConfig implements IDhApiFarFogConfig
@Override @Override
public IDhApiConfigValue<Double> farFogStartDistance() public IDhApiConfigValue<Float> farFogStartDistance()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogStart); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogStart); }
@Override @Override
public IDhApiConfigValue<Double> farFogEndDistance() public IDhApiConfigValue<Float> farFogEndDistance()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogEnd); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogEnd); }
@Override @Override
public IDhApiConfigValue<Double> farFogMinThickness() public IDhApiConfigValue<Float> farFogMinThickness()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogMin); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogMin); }
@Override @Override
public IDhApiConfigValue<Double> farFogMaxThickness() public IDhApiConfigValue<Float> farFogMaxThickness()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogMax); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogMax); }
@Override @Override
public IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff() public IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff()
{ return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.farFogFalloff); } { return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.farFogFalloff); }
@Override @Override
public IDhApiConfigValue<Double> farFogDensity() public IDhApiConfigValue<Float> farFogDensity()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogDensity); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogDensity); }
} }
@@ -85,17 +85,21 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
public IDhApiConfigValue<EDhApiHorizontalQuality> horizontalQuality() public IDhApiConfigValue<EDhApiHorizontalQuality> horizontalQuality()
{ return new DhApiConfigValue<EDhApiHorizontalQuality, EDhApiHorizontalQuality>(Config.Client.Advanced.Graphics.Quality.horizontalQuality); } { return new DhApiConfigValue<EDhApiHorizontalQuality, EDhApiHorizontalQuality>(Config.Client.Advanced.Graphics.Quality.horizontalQuality); }
@Override
public IDhApiConfigValue<Boolean> useCameraPositionForQualityDropOff()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Quality.useCameraPositionForQualityDropOff); }
@Override @Override
public IDhApiConfigValue<EDhApiTransparency> transparency() public IDhApiConfigValue<EDhApiTransparency> transparency()
{ return new DhApiConfigValue<EDhApiTransparency, EDhApiTransparency>(Config.Client.Advanced.Graphics.Quality.transparency); } { return new DhApiConfigValue<EDhApiTransparency, EDhApiTransparency>(Config.Client.Advanced.Graphics.Quality.transparency); }
@Override @Override
public IDhApiConfigValue<EDhApiBlocksToAvoid> blocksToAvoid() public IDhApiConfigValue<EDhApiBlocksToAvoid> blocksToAvoid()
{ return new DhApiConfigValue<EDhApiBlocksToAvoid, EDhApiBlocksToAvoid>(Config.Client.Advanced.Graphics.Quality.blocksToIgnore); } { return new DhApiConfigValue<EDhApiBlocksToAvoid, EDhApiBlocksToAvoid>(Config.Client.Advanced.Graphics.Culling.blocksToIgnore); }
@Override @Override
public IDhApiConfigValue<Boolean> tintWithAvoidedBlocks() public IDhApiConfigValue<Boolean> tintWithAvoidedBlocks()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks); } { return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Culling.tintWithAvoidedBlocks); }
@Override @Override
public IDhApiConfigValue<Integer> getBiomeBlending() public IDhApiConfigValue<Integer> getBiomeBlending()
@@ -108,16 +112,16 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
//===========================// //===========================//
@Override @Override
public IDhApiConfigValue<Double> overdrawPreventionRadius() public IDhApiConfigValue<Float> overdrawPreventionRadius()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Culling.overdrawPrevention); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Culling.overdrawPrevention); }
@Override @Override
public IDhApiConfigValue<Double> brightnessMultiplier() public IDhApiConfigValue<Float> brightnessMultiplier()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Quality.brightnessMultiplier); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Quality.brightnessMultiplier); }
@Override @Override
public IDhApiConfigValue<Double> saturationMultiplier() public IDhApiConfigValue<Float> saturationMultiplier()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Quality.saturationMultiplier); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Quality.saturationMultiplier); }
@Override @Override
public IDhApiConfigValue<Boolean> caveCullingEnabled() public IDhApiConfigValue<Boolean> caveCullingEnabled()
@@ -135,10 +139,6 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
public IDhApiConfigValue<Boolean> lodOnlyMode() public IDhApiConfigValue<Boolean> lodOnlyMode()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.lodOnlyMode); } { return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.lodOnlyMode); }
@Override
public IDhApiConfigValue<Double> lodBias()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Quality.lodBias); }
@Override @Override
public IDhApiConfigValue<EDhApiLodShading> lodShading() public IDhApiConfigValue<EDhApiLodShading> lodShading()
{ return new DhApiConfigValue<EDhApiLodShading, EDhApiLodShading>(Config.Client.Advanced.Graphics.Quality.lodShading); } { return new DhApiConfigValue<EDhApiLodShading, EDhApiLodShading>(Config.Client.Advanced.Graphics.Quality.lodShading); }
@@ -44,31 +44,31 @@ public class DhApiHeightFogConfig implements IDhApiHeightFogConfig
{ return new DhApiConfigValue<EDhApiHeightFogDirection, EDhApiHeightFogDirection>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDirection); } { return new DhApiConfigValue<EDhApiHeightFogDirection, EDhApiHeightFogDirection>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDirection); }
@Override @Override
public IDhApiConfigValue<Double> heightFogBaseHeight() public IDhApiConfigValue<Float> heightFogBaseHeight()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogBaseHeight); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogBaseHeight); }
@Override @Override
public IDhApiConfigValue<Double> heightFogStartingHeightPercent() public IDhApiConfigValue<Float> heightFogStartingHeightPercent()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogStart); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogStart); }
@Override @Override
public IDhApiConfigValue<Double> heightFogEndingHeightPercent() public IDhApiConfigValue<Float> heightFogEndingHeightPercent()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogEnd); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogEnd); }
@Override @Override
public IDhApiConfigValue<Double> heightFogMinThickness() public IDhApiConfigValue<Float> heightFogMinThickness()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMin); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMin); }
@Override @Override
public IDhApiConfigValue<Double> heightFogMaxThickness() public IDhApiConfigValue<Float> heightFogMaxThickness()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMax); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMax); }
@Override @Override
public IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff() public IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff()
{ return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogFalloff); } { return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogFalloff); }
@Override @Override
public IDhApiConfigValue<Double> heightFogDensity() public IDhApiConfigValue<Float> heightFogDensity()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDensity); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDensity); }
} }
@@ -41,8 +41,8 @@ public class DhApiNoiseTextureConfig implements IDhApiNoiseTextureConfig
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.NoiseTexture.noiseSteps); } { return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.NoiseTexture.noiseSteps); }
@Override @Override
public IDhApiConfigValue<Double> noiseIntensity() public IDhApiConfigValue<Float> noiseIntensity()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.NoiseTexture.noiseIntensity); } { return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.NoiseTexture.noiseIntensity); }
@Override @Override
public IDhApiConfigValue<Integer> noiseDropoff() public IDhApiConfigValue<Integer> noiseDropoff()
@@ -13,7 +13,7 @@ import java.lang.ref.SoftReference;
public class DhApiTerrainDataCache implements IDhApiTerrainDataCache public class DhApiTerrainDataCache implements IDhApiTerrainDataCache
{ {
private final Object modificationLock = new Object(); private final Object modificationLock = new Object();
private Long2ReferenceOpenHashMap<SoftReference<FullDataSourceV2>> posToFullDataRef = new Long2ReferenceOpenHashMap<>(); private final Long2ReferenceOpenHashMap<SoftReference<FullDataSourceV2>> posToFullDataRef = new Long2ReferenceOpenHashMap<>();
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@@ -22,6 +22,7 @@ public class DhApiTerrainDataCache implements IDhApiTerrainDataCache
//==================// //==================//
// internal methods // // internal methods //
//==================// //==================//
//region
public void add(long pos, FullDataSourceV2 dataSource) public void add(long pos, FullDataSourceV2 dataSource)
{ {
@@ -48,11 +49,14 @@ public class DhApiTerrainDataCache implements IDhApiTerrainDataCache
} }
} }
//endregion
//=============// //=============//
// API methods // // API methods //
//=============// //=============//
//region
@Override @Override
public void clear() public void clear()
@@ -82,6 +86,23 @@ public class DhApiTerrainDataCache implements IDhApiTerrainDataCache
} }
} }
//endregion
//================//
// base overrides //
//================//
//region
@Override
public void close() { this.clear(); }
@Override
public String toString() { return "Size: " + this.posToFullDataRef.size(); }
//endregion
} }
@@ -32,22 +32,22 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhLodPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
import com.seibel.distanthorizons.core.util.DhApiTerrainDataPointUtil; import com.seibel.distanthorizons.core.util.DhApiTerrainDataPointUtil;
import com.seibel.distanthorizons.core.util.FullDataPointUtil; import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RayCastUtil; import com.seibel.distanthorizons.core.util.RayCastUtil;
import com.seibel.distanthorizons.core.util.math.Vec3f; import com.seibel.distanthorizons.core.util.math.DhVec3f;
import com.seibel.distanthorizons.core.world.AbstractDhWorld; import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.DhVec3d;
import com.seibel.distanthorizons.core.util.math.Vec3i; import com.seibel.distanthorizons.core.util.math.DhVec3i;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -66,53 +66,62 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final AbstractDebugWireframeRenderer DEBUG_RENDERER = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
// debugging values // debugging values
private static volatile boolean debugThreadRunning = false; private static volatile boolean debugThreadRunning = false;
private static DhApiTerrainDataCache debugDataCache = new DhApiTerrainDataCache(); private static DhApiTerrainDataCache debugDataCache = new DhApiTerrainDataCache();
private static DhApiVec3i currentDebugVec3i = new Vec3i(); private static DhApiVec3i currentDebugVec3i = new DhVec3i();
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
private DhApiTerrainDataRepo() private DhApiTerrainDataRepo()
{ {
} }
//endregion
//================// //================//
// Getter Methods // // Getter Methods //
//================// //================//
//region
@Override @Override
public DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, @Nullable IDhApiTerrainDataCache dataCache) public DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, IDhApiTerrainDataCache dataCache)
{ return getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, dataCache); } { return getTerrainDataAtBlockYPos(levelWrapper, DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, dataCache); }
@Override @Override
public DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, @Nullable IDhApiTerrainDataCache dataCache) public DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, IDhApiTerrainDataCache dataCache)
{ return getTerrainDataColumnArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, dataCache); } { return getTerrainDataColumnArray(levelWrapper, DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, dataCache); }
@Override @Override
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, @Nullable IDhApiTerrainDataCache dataCache) public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPosX, chunkPosZ), dataCache); } { return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, DhSectionPos.encode(LodUtil.CHUNK_DETAIL_LEVEL, chunkPosX, chunkPosZ), dataCache); }
@Override @Override
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, @Nullable IDhApiTerrainDataCache dataCache) public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ), dataCache); } { return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, DhSectionPos.encode(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ), dataCache); }
@Override @Override
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ, @Nullable IDhApiTerrainDataCache dataCache) public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ, IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(detailLevel, posX, posZ), dataCache); } { return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, DhSectionPos.encode(detailLevel, posX, posZ), dataCache); }
//endregion
// private getters // // private getters //
//region
/** Returns a single API terrain datapoint that contains the given Y block position */ /** Returns a single API terrain datapoint that contains the given Y block position */
private static DhApiResult<DhApiTerrainDataPoint> getTerrainDataAtBlockYPos(IDhApiLevelWrapper levelWrapper, DhLodPos requestedColumnPos, Integer blockYPos, @Nullable IDhApiTerrainDataCache dataCache) private static DhApiResult<DhApiTerrainDataPoint> getTerrainDataAtBlockYPos(IDhApiLevelWrapper levelWrapper, long requestedColumnPos, Integer blockYPos, IDhApiTerrainDataCache dataCache)
{ {
DhApiResult<DhApiTerrainDataPoint[]> result = getTerrainDataColumnArray(levelWrapper, requestedColumnPos, blockYPos, dataCache); DhApiResult<DhApiTerrainDataPoint[]> result = getTerrainDataColumnArray(levelWrapper, requestedColumnPos, blockYPos, dataCache);
if (result.success && result.payload.length > 0) if (result.success && result.payload.length > 0)
@@ -126,7 +135,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
} }
/** /**
* Returns all the block columns represented by the given {@link DhLodPos}. <br> * Returns all the block columns represented by the given {@link DhSectionPos}. <br>
* IE, A position with the detail level: <br> * IE, A position with the detail level: <br>
* 0 (block): will return a 1x1 matrix of data. (don't do this, we have a specific method for that.) <br> * 0 (block): will return a 1x1 matrix of data. (don't do this, we have a specific method for that.) <br>
* 1 (2 blocks): will return a 2x2 matrix of data. <br> * 1 (2 blocks): will return a 2x2 matrix of data. <br>
@@ -135,11 +144,14 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
* will stop and return the in progress data if any errors are encountered. * will stop and return the in progress data if any errors are encountered.
*/ */
private static DhApiResult<DhApiTerrainDataPoint[][][]> getTerrainDataOverAreaForPositionDetailLevel( private static DhApiResult<DhApiTerrainDataPoint[][][]> getTerrainDataOverAreaForPositionDetailLevel(
IDhApiLevelWrapper levelWrapper, DhLodPos requestedAreaPos, IDhApiLevelWrapper levelWrapper, long requestedAreaPos,
@Nullable IDhApiTerrainDataCache dataCache) IDhApiTerrainDataCache dataCache)
{ {
DhLodPos startingBlockPos = requestedAreaPos.getCornerLodPos(LodUtil.BLOCK_DETAIL_LEVEL); byte requestedDetailLevel = DhSectionPos.getDetailLevel(requestedAreaPos);
int widthOfAreaInBlocks = BitShiftUtil.powerOfTwo(requestedAreaPos.detailLevel); long startingBlockPos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL,
DhSectionPos.getX(requestedAreaPos) * BitShiftUtil.powerOfTwo(requestedDetailLevel - LodUtil.BLOCK_DETAIL_LEVEL),
DhSectionPos.getZ(requestedAreaPos) * BitShiftUtil.powerOfTwo(requestedDetailLevel - LodUtil.BLOCK_DETAIL_LEVEL));
int widthOfAreaInBlocks = BitShiftUtil.powerOfTwo(requestedDetailLevel);
DhApiTerrainDataPoint[][][] returnArray = new DhApiTerrainDataPoint[widthOfAreaInBlocks][widthOfAreaInBlocks][]; DhApiTerrainDataPoint[][][] returnArray = new DhApiTerrainDataPoint[widthOfAreaInBlocks][widthOfAreaInBlocks][];
int dataColumnsReturned = 0; int dataColumnsReturned = 0;
@@ -149,7 +161,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
{ {
for (int z = 0; z < widthOfAreaInBlocks; z++) for (int z = 0; z < widthOfAreaInBlocks; z++)
{ {
DhLodPos blockColumnPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, startingBlockPos.x + x, startingBlockPos.z + z); long blockColumnPos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, DhSectionPos.getX(startingBlockPos) + x, DhSectionPos.getZ(startingBlockPos) + z);
DhApiResult<DhApiTerrainDataPoint[]> result = getTerrainDataColumnArray(levelWrapper, blockColumnPos, null, dataCache); DhApiResult<DhApiTerrainDataPoint[]> result = getTerrainDataColumnArray(levelWrapper, blockColumnPos, null, dataCache);
if (result.success) if (result.success)
{ {
@@ -175,8 +187,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
*/ */
private static DhApiResult<DhApiTerrainDataPoint[]> getTerrainDataColumnArray( private static DhApiResult<DhApiTerrainDataPoint[]> getTerrainDataColumnArray(
IDhApiLevelWrapper levelWrapper, IDhApiLevelWrapper levelWrapper,
DhLodPos requestedColumnPos, Integer nullableBlockYPos, long requestedColumnPos, Integer nullableBlockYPos,
@Nullable IDhApiTerrainDataCache apiDataCache) IDhApiTerrainDataCache apiDataCache)
{ {
//============// //============//
// validation // // validation //
@@ -197,9 +209,14 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper; ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper;
// the data cache can be null, but must be our own implementation // require a data cache to prevent horrible performance (especially on ray-casts)
if (apiDataCache != null if (apiDataCache == null)
&& !(apiDataCache instanceof DhApiTerrainDataCache)) {
return DhApiResult.createFail("Missing [" + IDhApiTerrainDataCache.class.getSimpleName() + "], if a cache isn't provided your repo operations will be significantly slower.");
}
// the data cache must be our own implementation
if (!(apiDataCache instanceof DhApiTerrainDataCache))
{ {
return DhApiResult.createFail("Unsupported [" + IDhApiTerrainDataCache.class.getSimpleName() + "] implementation, only the core class [" + DhApiTerrainDataCache.class.getSimpleName() + "] is a valid parameter."); return DhApiResult.createFail("Unsupported [" + IDhApiTerrainDataCache.class.getSimpleName() + "] implementation, only the core class [" + DhApiTerrainDataCache.class.getSimpleName() + "] is a valid parameter.");
} }
@@ -213,12 +230,12 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
} }
// get the detail levels for this request // get the detail levels for this request
byte requestedDetailLevel = requestedColumnPos.detailLevel; byte requestedDetailLevel = DhSectionPos.getDetailLevel(requestedColumnPos);
byte sectionDetailLevel = (byte) (requestedDetailLevel + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); byte sectionDetailLevel = (byte) (requestedDetailLevel + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
// get the positions for this request // get the positions for this request
long sectionPos = requestedColumnPos.getSectionPosWithSectionDetailLevel(sectionDetailLevel); long sectionPos = DhSectionPos.convertToDetailLevel(requestedColumnPos, sectionDetailLevel);
DhLodPos relativePos = requestedColumnPos.getDhSectionRelativePositionForDetailLevel(); long relativePos = DhSectionPos.getDhSectionRelativePositionForDetailLevel(requestedColumnPos, DhSectionPos.getDetailLevel(requestedColumnPos));
@@ -258,7 +275,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
//===============================// //===============================//
FullDataPointIdMap mapping = dataSource.mapping; FullDataPointIdMap mapping = dataSource.mapping;
LongArrayList dataColumn = dataSource.getColumnAtRelPos(relativePos.x, relativePos.z); LongArrayList dataColumn = dataSource.getColumnAtRelPos(DhSectionPos.getX(relativePos), DhSectionPos.getZ(relativePos));
if (dataColumn != null) if (dataColumn != null)
{ {
int dataColumnIndexCount = dataColumn.size(); int dataColumnIndexCount = dataColumn.size();
@@ -330,11 +347,14 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
} }
} }
//endregion
//====================// //====================//
// raycasting methods // // raycasting methods //
//====================// //====================//
//region
@Override @Override
public DhApiResult<DhApiRaycastResult> raycast( public DhApiResult<DhApiRaycastResult> raycast(
@@ -346,8 +366,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
IDhApiTerrainDataCache dataCache) IDhApiTerrainDataCache dataCache)
{ {
return this.raycastLodData(levelWrapper, return this.raycastLodData(levelWrapper,
new Vec3d(rayOriginX, rayOriginY, rayOriginZ), new DhVec3d(rayOriginX, rayOriginY, rayOriginZ),
new Vec3f(rayDirectionX, rayDirectionY, rayDirectionZ), new DhVec3f(rayDirectionX, rayDirectionY, rayDirectionZ),
maxRayBlockLength, dataCache); maxRayBlockLength, dataCache);
} }
@@ -359,7 +379,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
*/ */
private DhApiResult<DhApiRaycastResult> raycastLodData( private DhApiResult<DhApiRaycastResult> raycastLodData(
IDhApiLevelWrapper levelWrapper, IDhApiLevelWrapper levelWrapper,
Vec3d rayOrigin, Vec3f rayDirection, DhVec3d rayOrigin, DhVec3f rayDirection,
int maxRayBlockLength, int maxRayBlockLength,
@Nullable @Nullable
IDhApiTerrainDataCache dataCache) IDhApiTerrainDataCache dataCache)
@@ -376,9 +396,9 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
int currentLength = 0; int currentLength = 0;
// the exact position of this step // the exact position of this step
Vec3d exactPos = new Vec3d(rayOrigin.x, rayOrigin.y, rayOrigin.z); DhVec3d exactPos = new DhVec3d(rayOrigin.x, rayOrigin.y, rayOrigin.z);
// the block position for this step // the block position for this step
Vec3i blockPos = new Vec3i((int) Math.round(rayOrigin.x), (int) Math.round(rayOrigin.y), (int) Math.round(rayOrigin.z)); DhVec3i blockPos = new DhVec3i((int) Math.round(rayOrigin.x), (int) Math.round(rayOrigin.y), (int) Math.round(rayOrigin.z));
DhApiRaycastResult closetFoundDataPoint = null; DhApiRaycastResult closetFoundDataPoint = null;
@@ -388,8 +408,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
&& currentLength <= maxRayBlockLength) && currentLength <= maxRayBlockLength)
{ {
// get the LOD columns around this position // get the LOD columns around this position
ArrayList<Vec3i> columnPositions = getIntersectingColumnsAtPosition(blockPos, rayDirection); ArrayList<DhVec3i> columnPositions = getIntersectingColumnsAtPosition(blockPos, rayDirection);
for (Vec3i columnPos : columnPositions) for (DhVec3i columnPos : columnPositions)
{ {
// check each column // check each column
DhApiResult<DhApiTerrainDataPoint[]> result = this.getColumnDataAtBlockPos(levelWrapper, columnPos.x, columnPos.z, dataCache); DhApiResult<DhApiTerrainDataPoint[]> result = this.getColumnDataAtBlockPos(levelWrapper, columnPos.x, columnPos.z, dataCache);
@@ -406,7 +426,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
if (dataPoint.blockStateWrapper != null && !dataPoint.blockStateWrapper.isAir()) if (dataPoint.blockStateWrapper != null && !dataPoint.blockStateWrapper.isAir())
{ {
// does this LOD contain the given Y position? // does this LOD contain the given Y position?
Vec3i dataPointPos = new Vec3i(columnPos.x, dataPoint.bottomYBlockPos, columnPos.z); DhVec3i dataPointPos = new DhVec3i(columnPos.x, dataPoint.bottomYBlockPos, columnPos.z);
if (exactPos.y >= dataPoint.bottomYBlockPos if (exactPos.y >= dataPoint.bottomYBlockPos
&& exactPos.y <= dataPoint.topYBlockPos) && exactPos.y <= dataPoint.topYBlockPos)
{ {
@@ -459,15 +479,15 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
* *
* Used to make sure the raycast step doesn't accidentally walk over any adjacent data. * Used to make sure the raycast step doesn't accidentally walk over any adjacent data.
*/ */
private static ArrayList<Vec3i> getIntersectingColumnsAtPosition(Vec3i rayEndingPos, Vec3f rayDirection) private static ArrayList<DhVec3i> getIntersectingColumnsAtPosition(DhVec3i rayEndingPos, DhVec3f rayDirection)
{ {
ArrayList<Vec3i> returnList = new ArrayList<>(9); ArrayList<DhVec3i> returnList = new ArrayList<>(9);
for (int x = -1; x <= 1; x++) for (int x = -1; x <= 1; x++)
{ {
for (int z = -1; z <= 1; z++) for (int z = -1; z <= 1; z++)
{ {
Vec3i pos = new Vec3i(rayEndingPos.x + x, rayEndingPos.y, rayEndingPos.z + z); DhVec3i pos = new DhVec3i(rayEndingPos.x + x, rayEndingPos.y, rayEndingPos.z + z);
// check if this column is intersected by the ray // check if this column is intersected by the ray
if (RayCastUtil.rayIntersectsSquare(rayEndingPos.x, rayEndingPos.z, rayDirection.x, rayDirection.z, pos.x, pos.z, 1)) if (RayCastUtil.rayIntersectsSquare(rayEndingPos.x, rayEndingPos.z, rayDirection.x, rayDirection.z, pos.x, pos.z, 1))
@@ -480,11 +500,14 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
return returnList; return returnList;
} }
//endregion
//================// //================//
// setter methods // // setter methods //
//================// //================//
//region
@Override @Override
public DhApiResult<Void> overwriteChunkDataAsync(IDhApiLevelWrapper levelWrapper, Object[] chunkObjectArray) throws ClassCastException public DhApiResult<Void> overwriteChunkDataAsync(IDhApiLevelWrapper levelWrapper, Object[] chunkObjectArray) throws ClassCastException
@@ -508,26 +531,32 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
// this will throw a cast exception if the chunk object array isn't correct // this will throw a cast exception if the chunk object array isn't correct
IChunkWrapper chunk = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createChunkWrapper(chunkObjectArray); IChunkWrapper chunk = SingletonInjector.INSTANCE.get(IWrapperFactory.class).createChunkWrapper(chunkObjectArray);
SharedApi.INSTANCE.applyChunkUpdate(chunk, dhLevel.getLevelWrapper(), true, true); SharedApi.INSTANCE.applyChunkUpdate(chunk, dhLevel.getLevelWrapper());
return DhApiResult.createSuccess(); return DhApiResult.createSuccess();
} }
//endregion
//=============// //=============//
// API helpers // // API helpers //
//=============// //=============//
//region
@Override @Override
public IDhApiTerrainDataCache createSoftCache() { return new DhApiTerrainDataCache(); } public IDhApiTerrainDataCache createSoftCache() { return new DhApiTerrainDataCache(); }
//endregion
//===============// //===============//
// debug methods // // debug methods //
//===============// //===============//
//region
/** /**
* This method is here for debugging the repo and isn't intended for normal use. * This method is here for debugging the repo and isn't intended for normal use.
@@ -541,10 +570,10 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
Thread thread = new Thread(() -> { Thread thread = new Thread(() -> {
try try
{ {
DhApiResult<DhApiTerrainDataPoint> single = getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, debugDataCache); DhApiResult<DhApiTerrainDataPoint> single = getTerrainDataAtBlockYPos(levelWrapper, DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, debugDataCache);
DhApiResult<DhApiTerrainDataPoint[]> column = getTerrainDataColumnArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, debugDataCache); DhApiResult<DhApiTerrainDataPoint[]> column = getTerrainDataColumnArray(levelWrapper, DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, debugDataCache);
DhLodPos chunkPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ).convertToDetailLevel(LodUtil.CHUNK_DETAIL_LEVEL); long chunkPos = DhSectionPos.encodeContaining(LodUtil.CHUNK_DETAIL_LEVEL, new DhChunkPos(blockPosX, blockPosZ));
DhApiResult<DhApiTerrainDataPoint[][][]> area = getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, chunkPos, debugDataCache); DhApiResult<DhApiTerrainDataPoint[][][]> area = getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, chunkPos, debugDataCache);
@@ -580,9 +609,9 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
if (rayCast.success if (rayCast.success
&& rayCast.payload != null) && rayCast.payload != null)
{ {
DebugRenderer.makeParticle( DEBUG_RENDERER.makeParticle(
new DebugRenderer.BoxParticle( new AbstractDebugWireframeRenderer.BoxParticle(
new DebugRenderer.Box( new AbstractDebugWireframeRenderer.Box(
DhSectionPos.encode((byte) 0, rayCast.payload.pos.x, rayCast.payload.pos.z), DhSectionPos.encode((byte) 0, rayCast.payload.pos.x, rayCast.payload.pos.z),
rayCast.payload.dataPoint.bottomYBlockPos, rayCast.payload.dataPoint.bottomYBlockPos,
rayCast.payload.dataPoint.topYBlockPos, rayCast.payload.dataPoint.topYBlockPos,
@@ -608,5 +637,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
} }
} }
//endregion
} }
@@ -24,18 +24,28 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*; import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState; import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure; import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry; import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy; import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.render.RenderParams;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.render.renderer.*; import com.seibel.distanthorizons.core.render.renderer.*;
import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.math.DhVec3d;
import com.seibel.distanthorizons.core.util.objects.Pair; import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IImmersivePortalsAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhMetaRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhVanillaFadeRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTestTriangleRenderer;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
@@ -44,8 +54,6 @@ import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering; import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.world.AbstractDhWorld; import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld; import com.seibel.distanthorizons.core.world.DhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
@@ -59,6 +67,7 @@ import org.lwjgl.glfw.GLFW;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
@@ -71,15 +80,19 @@ import java.util.concurrent.ThreadPoolExecutor;
public class ClientApi public class ClientApi
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final DhLogger RATE_LIMITED_LOGGER = new DhLoggerBuilder().maxCountPerSecond(1).build();
public static boolean prefLoggerEnabled = false;
public static final ClientApi INSTANCE = new ClientApi(); public static final ClientApi INSTANCE = new ClientApi();
public static final TestRenderer TEST_RENDERER = new TestRenderer();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
/** Delayed accessing is necessary since this object will be created before the mod accessors are bound. */
private static class DelayedAccessors
{
public static final IImmersivePortalsAccessor IMMERSIVE_PORTALS = ModAccessorInjector.INSTANCE.get(IImmersivePortalsAccessor.class);
}
/** this includes the is dev build message and low allocated memory warning */ /** this includes the is dev build message and low allocated memory warning */
private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000; private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000;
@@ -92,6 +105,18 @@ public class ClientApi
* Only downside is making sure each variable is populated before rendering. * Only downside is making sure each variable is populated before rendering.
*/ */
public static final DhRenderState RENDER_STATE = new DhRenderState(); public static final DhRenderState RENDER_STATE = new DhRenderState();
/**
* static variable so we don't have to re-create it each frame,
* reducing GC pressure.
*/
private static final RenderParams RENDER_PARAMS = new RenderParams();
/**
* 50ms = 20 FPS
* @link https://fpstoms.com/
* @see ClientApi#cameraSpeedRollingAverage
*/
private static final long MIN_MS_BETWEEN_SPEED_CHECKS = 50;
private boolean isDevBuildMessagePrinted = false; private boolean isDevBuildMessagePrinted = false;
@@ -105,21 +130,40 @@ public class ClientApi
public boolean rendererDisabledBecauseOfExceptions = false; public boolean rendererDisabledBecauseOfExceptions = false;
private final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi(this::clientLevelLoadEvent, this::clientLevelUnloadEvent);
/** Delay loading the first level to give the server some time to respond with level to actually load */
private Timer firstLevelLoadTimer;
private static final long FIRST_LEVEL_LOAD_DELAY_IN_MS = 1_000;
/** Holds any levels that were loaded before the {@link ClientApi#onClientOnlyConnected} was fired. */ /** Holds any levels that were loaded before the {@link ClientApi#onClientOnlyConnected} was fired. */
public final HashSet<IClientLevelWrapper> waitingClientLevels = new HashSet<>(); public final HashSet<IClientLevelWrapper> waitingClientLevels = new HashSet<>();
/** Holds any chunks that were loaded before the {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} was fired. */ /** Holds any chunks that were found before the client levels are loaded. */
public final HashMap<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new HashMap<>(); public final Map<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new ConcurrentHashMap<>();
/** publicly available so {@link F3Screen} can display the error */
@Nullable @Nullable
public String lastRenderParamValidationMessage = null; public String lastRenderParamValidationMessage = null;
/**
* measured in blocks/second <br>
*
* The number of points tracked here is related
* to the rate at which we check for speed.
* So if the ms_between is changed the number of points
* tracked should also be to keep the ratio roughly the same.
* @see ClientApi#MIN_MS_BETWEEN_SPEED_CHECKS
*/
private final RollingAverage cameraSpeedRollingAverage = new RollingAverage(40);
private DhVec3d lastCameraPosForSpeedCheck = new DhVec3d();
private long msSinceLastSpeedCheck = 0L;
public double getAvgCameraSpeed() { return cameraSpeedRollingAverage.getAverage(); }
/**
* keeping track of this is necessary to fix
* out-of-date LODs from rendering when the shading
* is changed by Iris, causing LODs to often
* lack the side shading, which looks pretty bad
* when shaders are disabled.
*/
private boolean irisShadersEnabledLastFrame = false;
//==============// //==============//
// constructors // // constructors //
@@ -132,10 +176,11 @@ public class ClientApi
//==============// //==============//
// world events // // world events //
//==============// //==============//
//region world events
/** /**
* May be fired slightly before or after the associated * May be fired slightly before or after the associated
* {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} event * level is loaded
* depending on how the host mod loader functions. <br><br> * depending on how the host mod loader functions. <br><br>
* *
* Synchronized shouldn't be necessary, but is present to match {@see onClientOnlyDisconnected} and prevent any unforeseen issues. * Synchronized shouldn't be necessary, but is present to match {@see onClientOnlyDisconnected} and prevent any unforeseen issues.
@@ -161,39 +206,20 @@ public class ClientApi
MC_CLIENT.sendChatMessage("DH may behave strangely or have missing functionality."); MC_CLIENT.sendChatMessage("DH may behave strangely or have missing functionality.");
MC_CLIENT.sendChatMessage("In order to use pre-generated LODs, put your DH database(s) in:"); MC_CLIENT.sendChatMessage("In order to use pre-generated LODs, put your DH database(s) in:");
MC_CLIENT.sendChatMessage(MinecraftTextFormat.GRAY +".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+ MinecraftTextFormat.CLEAR_FORMATTING); MC_CLIENT.sendChatMessage(MinecraftTextFormat.GRAY +".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+ MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage("This can be disabled in DH's config under Advanced -> Logging."); MC_CLIENT.sendChatMessage("This message can be disabled in DH's config under Advanced -> Logging.");
MC_CLIENT.sendChatMessage(""); MC_CLIENT.sendChatMessage("");
} }
} }
// firing after clientLevelLoadEvent
// TODO if level has prepped to load it should fire level load event
DhClientWorld world = new DhClientWorld(); DhClientWorld world = new DhClientWorld();
SharedApi.setDhWorld(world); SharedApi.setDhWorld(world);
this.pluginChannelApi.onJoinServer(world.networkState.getSession());
world.networkState.sendConfigMessage();
LOGGER.info("Loading [" + this.waitingClientLevels.size() + "] waiting client level wrappers.");
for (IClientLevelWrapper level : this.waitingClientLevels)
{
this.clientLevelLoadEvent(level);
}
this.waitingClientLevels.clear();
} }
} }
/** Synchronized to prevent a rare issue where multiple disconnect events are triggered on top of each other. */ /** Synchronized to prevent a rare issue where multiple disconnect events are triggered on top of each other. */
public synchronized void onClientOnlyDisconnected() public synchronized void onClientOnlyDisconnected()
{ {
// clear the first time timer
if (this.firstLevelLoadTimer != null)
{
this.firstLevelLoadTimer.cancel();
this.firstLevelLoadTimer = null;
}
AbstractDhWorld world = SharedApi.getAbstractDhWorld(); AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null) if (world != null)
{ {
@@ -203,102 +229,20 @@ public class ClientApi
SharedApi.setDhWorld(null); SharedApi.setDhWorld(null);
} }
this.pluginChannelApi.reset();
// remove any waiting items // remove any waiting items
this.waitingChunkByClientLevelAndPos.clear(); this.waitingChunkByClientLevelAndPos.clear();
this.waitingClientLevels.clear();
} }
//endregion
//==============// //==============//
// level events // // level events //
//==============// //==============//
//region
public void clientLevelUnloadEvent(IClientLevelWrapper level) public void loadWaitingChunksForLevel(IClientLevelWrapper level)
{
try
{
LOGGER.info("Unloading client level [" + level.getClass().getSimpleName() + "]-[" + level.getDhIdentifier() + "].");
if (level instanceof IServerKeyedClientLevel)
{
this.pluginChannelApi.onClientLevelUnload();
}
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
{
world.unloadLevel(level);
SharedApi.INSTANCE.clearQueuedChunkUpdates();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
}
else
{
this.waitingClientLevels.remove(level);
}
}
catch (Exception e)
{
// handle errors here to prevent blowing up a mixin or API up stream
LOGGER.error("Unexpected error in ClientApi.clientLevelUnloadEvent(), error: "+e.getMessage(), e);
}
}
public void clientLevelLoadEvent(@Nullable IClientLevelWrapper levelWrapper)
{
// wait a moment before loading the level to give the server a chance to handle the client's login request
if (MC_CLIENT.clientConnectedToDedicatedServer())
{
if (this.firstLevelLoadTimer == null)
{
this.firstLevelLoadTimer = TimerUtil.CreateTimer("FirstLevelLoadTimer");
this.firstLevelLoadTimer.schedule(new TimerTask()
{
@Override
public void run() { ClientApi.this.clientLevelLoadEvent(levelWrapper); }
}, FIRST_LEVEL_LOAD_DELAY_IN_MS);
return;
}
this.firstLevelLoadTimer.cancel();
}
try
{
LOGGER.info("Loading client level [" + levelWrapper + "]-[" + levelWrapper.getDhIdentifier() + "].");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
{
if (!this.pluginChannelApi.allowLevelLoading(levelWrapper))
{
LOGGER.info("Levels in this connection are managed by the server, skipping auto-load.");
// Instead of attempting to load themselves, send the config and wait for a server provided level key.
((DhClientWorld) world).networkState.sendConfigMessage();
return;
}
world.getOrLoadLevel(levelWrapper);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
this.loadWaitingChunksForLevel(levelWrapper);
}
else
{
this.waitingClientLevels.add(levelWrapper);
}
}
catch (Exception e)
{
// handle errors here to prevent blowing up a mixin or API up stream
LOGGER.error("Unexpected error in ClientApi.clientLevelLoadEvent(), error: "+e.getMessage(), e);
}
}
private void loadWaitingChunksForLevel(IClientLevelWrapper level)
{ {
HashSet<Pair<IClientLevelWrapper, DhChunkPos>> keysToRemove = new HashSet<>(); HashSet<Pair<IClientLevelWrapper, DhChunkPos>> keysToRemove = new HashSet<>();
for (Pair<IClientLevelWrapper, DhChunkPos> levelChunkPair : this.waitingChunkByClientLevelAndPos.keySet()) for (Pair<IClientLevelWrapper, DhChunkPos> levelChunkPair : this.waitingChunkByClientLevelAndPos.keySet())
@@ -308,7 +252,7 @@ public class ClientApi
if (levelWrapper.equals(level)) if (levelWrapper.equals(level))
{ {
IChunkWrapper chunkWrapper = this.waitingChunkByClientLevelAndPos.get(levelChunkPair); IChunkWrapper chunkWrapper = this.waitingChunkByClientLevelAndPos.get(levelChunkPair);
SharedApi.INSTANCE.chunkLoadEvent(chunkWrapper, levelWrapper); SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, levelWrapper);
keysToRemove.add(levelChunkPair); keysToRemove.add(levelChunkPair);
} }
} }
@@ -320,11 +264,14 @@ public class ClientApi
} }
} }
//endregion
//============// //============//
// networking // // networking //
//============// //============//
//region networking
/** /**
* Forwards a decoded message into the registered handlers. * Forwards a decoded message into the registered handlers.
@@ -344,7 +291,8 @@ public class ClientApi
{ {
executor.execute(() -> executor.execute(() ->
{ {
NetworkSession networkSession = this.pluginChannelApi.networkSession; DhClientWorld world = (DhClientWorld) Objects.requireNonNull(SharedApi.tryGetDhClientWorld());
NetworkSession networkSession = world.pluginChannelApi.networkSession;
if (networkSession != null) if (networkSession != null)
{ {
networkSession.tryHandleMessage(message); networkSession.tryHandleMessage(message);
@@ -357,11 +305,14 @@ public class ClientApi
} }
} }
//endregion
//===============// //===============//
// LOD rendering // // LOD rendering //
//===============// //===============//
//region lod rendering
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */ /** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
public void renderLods() { this.renderLodLayer(false); } public void renderLods() { this.renderLodLayer(false); }
@@ -374,48 +325,128 @@ public class ClientApi
private void renderLodLayer(boolean renderingDeferredLayer) private void renderLodLayer(boolean renderingDeferredLayer)
{ {
//=========//
// logging //
//=========//
this.sendQueuedChatMessages();
IProfilerWrapper profiler = MC_CLIENT.getProfiler(); IProfilerWrapper profiler = MC_CLIENT.getProfiler();
profiler.pop(); // get out of "terrain" try (IProfilerWrapper.IProfileBlock dhRender_profile = profiler.push("DH-RenderLevel"))
profiler.push("DH-RenderLevel"); {
//===========//
// debugging //
//===========//
//region
// only run these tasks once per frame
if (!renderingDeferredLayer)
{
//DhApiTerrainDataRepo.asyncDebugMethod(
// RENDER_STATE.clientLevelWrapper,
// MC_CLIENT.getPlayerBlockPos().getX(),
// MC_CLIENT.getPlayerBlockPos().getY(),
// MC_CLIENT.getPlayerBlockPos().getZ()
//);
}
//endregion
//=====================// //=====================//
// render thread tasks // // render thread tasks //
//=====================// //=====================//
//region
// only run these tasks once per frame // only run these tasks once per frame
if (!renderingDeferredLayer) if (!renderingDeferredLayer)
{ {
profiler.push("DH render thread tasks"); try (IProfilerWrapper.IProfileBlock renderTask_profile = profiler.push("DH render thread tasks"))
{
//===============//
// chat messages //
//===============//
this.sendQueuedChatMessages();
//======================//
// GL Proxy queued jobs //
//======================//
//region
try try
{ {
// make sure the GLProxy is created for future use
GLProxy.getInstance();
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks // these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
GLProxy.runRenderThreadTasks(); RenderThreadTaskHandler.INSTANCE.runRenderThreadTasks();
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.error("Unexpected issue running render thread tasks, error: [" + e.getMessage() + "].", e); LOGGER.error("Unexpected issue running render thread tasks, error: [" + e.getMessage() + "].", e);
} }
profiler.pop(); //endregion
//==============//
// camera speed //
//==============//
//region
long nowMs = System.currentTimeMillis();
if (this.msSinceLastSpeedCheck + MIN_MS_BETWEEN_SPEED_CHECKS < nowMs
// don't track camera speed for dimensions the player isn't in
&& (DelayedAccessors.IMMERSIVE_PORTALS == null
|| !DelayedAccessors.IMMERSIVE_PORTALS.isRenderingPortal()))
{
// calc time since last check
double secSinceLastCheck = (nowMs - this.msSinceLastSpeedCheck) / 1_000.0;
this.msSinceLastSpeedCheck = nowMs;
// get the distance traveled since last frame
DhVec3d camPos = MC_RENDER.getCameraExactPosition();
double distanceInBlocks = camPos.getDistance(this.lastCameraPosForSpeedCheck);
double speed = distanceInBlocks / secSinceLastCheck;
// record new values for next check
this.cameraSpeedRollingAverage.add(speed);
this.lastCameraPosForSpeedCheck = camPos;
} }
//endregion
//====================//
// Iris data re-build //
//====================//
//region
// delayed getter since ClientApi is created before this accessor is bound
IIrisAccessor irisAccessor = ModAccessorInjector.INSTANCE.get(IIrisAccessor.class);
if (irisAccessor != null)
{
boolean shadersActive = irisAccessor.isShaderPackInUse();
if (this.irisShadersEnabledLastFrame != shadersActive)
{
this.irisShadersEnabledLastFrame = shadersActive;
DhApi.Delayed.renderProxy.clearRenderDataCache();
}
}
//endregion
}
}
//endregion
//=================// //=================//
// parameter setup // // parameter setup //
//=================// //=================//
//region
EDhApiRenderPass renderPass; EDhApiRenderPass renderPass;
if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering()) if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
@@ -438,24 +469,21 @@ public class ClientApi
// render prep and actual rendering into different threads/methods // render prep and actual rendering into different threads/methods
// this is annoying since it's possible to start a render with only // this is annoying since it's possible to start a render with only
// partially complete info, but there isn't a better option at the moment // partially complete info, but there isn't a better option at the moment
RenderParams renderParams = RENDER_PARAMS.update(renderPass, RENDER_STATE);
new RenderParams(
renderPass, //endregion
RENDER_STATE.frameTime,
RENDER_STATE.mcProjectionMatrix, RENDER_STATE.mcModelViewMatrix,
RENDER_STATE.clientLevelWrapper
);
//============// //============//
// validation // // validation //
//============// //============//
//region
// TODO write this message to the F3 menu so people can see when a different mod screws with the lightmap String validationMessage = RENDER_PARAMS.getValidationErrorMessage();
String validationMessage = renderParams.getValidationErrorMessage();
if (validationMessage != null) if (validationMessage != null)
{ {
// store the error message so it can be seen on the F3 screen
this.lastRenderParamValidationMessage = validationMessage; this.lastRenderParamValidationMessage = validationMessage;
return; return;
} }
@@ -477,24 +505,31 @@ public class ClientApi
return; return;
} }
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DISABLED)
{
return;
}
//endregion
//===========// //===========//
// rendering // // rendering //
//===========// //===========//
//region
try try
{ {
// render pass // // render pass //
if (!renderingDeferredLayer)
{
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT) if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
{ {
boolean renderingCancelledForThisFrame = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, renderParams); if (!renderingDeferredLayer)
if (!renderingCancelledForThisFrame)
{ {
LodRenderer.INSTANCE.render(renderParams, profiler); boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, RENDER_PARAMS);
if (!renderingCancelled)
{
LodRenderer.INSTANCE.render(RENDER_PARAMS, profiler);
} }
if (!DhApi.Delayed.renderProxy.getDeferTransparentRendering()) if (!DhApi.Delayed.renderProxy.getDeferTransparentRendering())
@@ -502,19 +537,12 @@ public class ClientApi
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null); ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
} }
} }
else if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEBUG)
{
profiler.push("Render Debug");
ClientApi.TEST_RENDERER.render();
profiler.pop();
}
}
else else
{ {
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, renderParams); boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, RENDER_PARAMS);
if (!renderingCancelled) if (!renderingCancelled)
{ {
LodRenderer.INSTANCE.renderDeferred(renderParams, profiler); LodRenderer.INSTANCE.renderDeferred(RENDER_PARAMS, profiler);
} }
@@ -524,6 +552,30 @@ public class ClientApi
} }
} }
} }
else
{
if (!renderingDeferredLayer)
{
IDhMetaRenderer metaRenderer = SingletonInjector.INSTANCE.get(IDhMetaRenderer.class);
IDhTestTriangleRenderer testRenderer = SingletonInjector.INSTANCE.get(IDhTestTriangleRenderer.class);
if (testRenderer != null
&& metaRenderer != null)
{
// meta renderer needed for render state/texture
// for setup on some APIs (IE openGL)
metaRenderer.runRenderPassSetup(RENDER_PARAMS);
testRenderer.render(RENDER_PARAMS);
metaRenderer.runRenderPassCleanup(RENDER_PARAMS);
}
else
{
RATE_LIMITED_LOGGER.warn("Unable to find singleton [" + IDhTestTriangleRenderer.class.getSimpleName() + "]");
}
}
}
}
catch (Exception e) catch (Exception e)
{ {
this.rendererDisabledBecauseOfExceptions = true; this.rendererDisabledBecauseOfExceptions = true;
@@ -535,17 +587,18 @@ public class ClientApi
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Error: " + MinecraftTextFormat.CLEAR_FORMATTING + e); MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Error: " + MinecraftTextFormat.CLEAR_FORMATTING + e);
} }
//endregion
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
} }
}
//endregion
//================// //================//
// fade rendering // // fade rendering //
//================// //================//
//region fade rendering
/** /**
* The first fade pass. * The first fade pass.
@@ -553,8 +606,14 @@ public class ClientApi
*/ */
public void renderFadeOpaque() public void renderFadeOpaque()
{ {
IDhVanillaFadeRenderer fadeRenderer = SingletonInjector.INSTANCE.get(IDhVanillaFadeRenderer.class);
if (fadeRenderer == null)
{
return;
}
// only fade when DH is rendering // only fade when DH is rendering
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT if (Config.Client.Advanced.Debugging.rendererMode.get() != EDhApiRendererMode.DISABLED
&& &&
( (
// only fade when requested // only fade when requested
@@ -562,10 +621,10 @@ public class ClientApi
// or if LOD-only mode is enabled (fading is used to remove the MC render pass) // or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|| Config.Client.Advanced.Debugging.lodOnlyMode.get() || Config.Client.Advanced.Debugging.lodOnlyMode.get()
) )
// don't fade when Iris shaders are active, otherwise the rendering can get weird && shouldRenderFade())
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{ {
VanillaFadeRenderer.INSTANCE.render(RENDER_STATE.mcModelViewMatrix, RENDER_STATE.mcProjectionMatrix, RENDER_STATE.frameTime, RENDER_STATE.clientLevelWrapper); RENDER_PARAMS.update(EDhApiRenderPass.OPAQUE, RENDER_STATE);
fadeRenderer.render(RENDER_PARAMS);
} }
} }
/** /**
@@ -575,8 +634,14 @@ public class ClientApi
*/ */
public void renderFadeTransparent() public void renderFadeTransparent()
{ {
IDhVanillaFadeRenderer fadeRenderer = SingletonInjector.INSTANCE.get(IDhVanillaFadeRenderer.class);
if (fadeRenderer == null)
{
return;
}
// only fade when DH is rendering // only fade when DH is rendering
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT) if (Config.Client.Advanced.Debugging.rendererMode.get() != EDhApiRendererMode.DISABLED)
{ {
boolean renderFade = boolean renderFade =
( (
@@ -585,20 +650,42 @@ public class ClientApi
// or if LOD-only mode is enabled (fading is used to remove the MC render pass) // or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|| Config.Client.Advanced.Debugging.lodOnlyMode.get() || Config.Client.Advanced.Debugging.lodOnlyMode.get()
) )
// don't fade when Iris shaders are active, otherwise the rendering can get weird && shouldRenderFade();
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering();
if (renderFade) if (renderFade)
{ {
VanillaFadeRenderer.INSTANCE.render(RENDER_STATE.mcModelViewMatrix, RENDER_STATE.mcProjectionMatrix, RENDER_STATE.frameTime, RENDER_STATE.clientLevelWrapper); RENDER_PARAMS.update(EDhApiRenderPass.TRANSPARENT, RENDER_STATE);
fadeRenderer.render(RENDER_PARAMS);
} }
} }
} }
private static boolean shouldRenderFade()
{
// don't fade when Iris shaders are active, otherwise the rendering can get weird
if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{
return false;
}
// Don't render fade through immersive portals, this causes the fade to apply incorrectly
IImmersivePortalsAccessor immersivePortals = ModAccessorInjector.INSTANCE.get(IImmersivePortalsAccessor.class);
if (immersivePortals != null
&& immersivePortals.isRenderingPortal())
{
return false;
}
return true;
}
//endregion
//=================//
// DEBUG USE // //==========//
//=================// // keyboard //
//==========//
//region keyboard
/** Trigger once on key press, with CLIENT PLAYER. */ /** Trigger once on key press, with CLIENT PLAYER. */
public void keyPressedEvent(int glfwKey) public void keyPressedEvent(int glfwKey)
@@ -610,23 +697,32 @@ public class ClientApi
} }
if (glfwKey == GLFW.GLFW_KEY_F8) if (glfwKey == GLFW.GLFW_KEY_F6)
{
Config.Client.Advanced.Debugging.debugRendering.set(EDhApiDebugRendering.next(Config.Client.Advanced.Debugging.debugRendering.get()));
MC_CLIENT.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRendering.get());
}
else if (glfwKey == GLFW.GLFW_KEY_F6)
{ {
Config.Client.Advanced.Debugging.rendererMode.set(EDhApiRendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get())); Config.Client.Advanced.Debugging.rendererMode.set(EDhApiRendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get()));
MC_CLIENT.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererMode.get()); MC_CLIENT.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererMode.get());
} }
else if (glfwKey == GLFW.GLFW_KEY_P) else if (glfwKey == GLFW.GLFW_KEY_F7)
{ {
prefLoggerEnabled = !prefLoggerEnabled; Config.Client.Advanced.Debugging.lodOnlyMode.set(!Config.Client.Advanced.Debugging.lodOnlyMode.get());
MC_CLIENT.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled")); MC_CLIENT.sendChatMessage("F7: Set LOD only mode to " + Config.Client.Advanced.Debugging.lodOnlyMode.get());
}
else if (glfwKey == GLFW.GLFW_KEY_F8)
{
Config.Client.Advanced.Debugging.debugRenderingColors.set(EDhApiDebugRendering.next(Config.Client.Advanced.Debugging.debugRenderingColors.get()));
MC_CLIENT.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRenderingColors.get());
} }
} }
//endregion
//======//
// chat //
//======//
//region chat
private void sendQueuedChatMessages() private void sendQueuedChatMessages()
{ {
// this includes if the current build is a dev build // this includes if the current build is a dev build
@@ -759,6 +855,8 @@ public class ClientApi
*/ */
public void showOverlayMessageNextFrame(String message) { this.overlayMessageQueueForNextFrame.add(message); } public void showOverlayMessageNextFrame(String message) { this.overlayMessageQueueForNextFrame.add(message); }
//endregion
} }
@@ -9,14 +9,14 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage; import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession; import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer;
/** /**
* This class is used to manage the level keys. * This class is used to manage the level keys.
@@ -30,9 +30,6 @@ public class ClientPluginChannelApi
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.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 @Nullable
public NetworkSession networkSession; public NetworkSession networkSession;
@@ -42,10 +39,8 @@ public class ClientPluginChannelApi
// constructor // // constructor //
//=============// //=============//
public ClientPluginChannelApi(Consumer<IServerKeyedClientLevel> levelLoadHandler, Consumer<IClientLevelWrapper> levelUnloadHandler) public ClientPluginChannelApi()
{ {
this.levelLoadHandler = levelLoadHandler;
this.levelUnloadHandler = levelUnloadHandler;
} }
@@ -88,38 +83,27 @@ public class ClientPluginChannelApi
throw new IllegalArgumentException("Server sent invalid level key."); throw new IllegalArgumentException("Server sent invalid level key.");
} }
LOGGER.info("Server level key received: [" + msg.levelKey + "]."); LOGGER.info("Level init received for [" + msg.dimensionResourceLocation + "]: server key [" + msg.serverKey + "], level key [" + msg.levelKey + "]");
GLProxy.queueRunningOnRenderThread(() -> RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("ClientPluginChannelApi onLevelInitMessage", () ->
{ {
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true); IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(); IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(clientLevel);
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.getDhIdentifier() + "].");
this.levelUnloadHandler.accept(clientLevel);
}
if (existingKeyedClientLevel == null if (existingKeyedClientLevel == null
|| !existingKeyedClientLevel.getServerKey().equals(msg.serverKey) || !existingKeyedClientLevel.getServerKey().equals(msg.serverKey)
|| !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey)) || !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
{ {
LOGGER.info("Loading level with key: [" + msg.levelKey + "]."); LOGGER.info("Loading level with key: [" + msg.levelKey + "].");
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.serverKey, msg.levelKey);
this.levelLoadHandler.accept(keyedLevel); IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.dimensionResourceLocation, msg.serverKey, msg.levelKey);
if (keyedLevel != null) {
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null) {
world.getOrLoadLevel(keyedLevel);
}
}
} }
}); });
} }
@@ -19,13 +19,10 @@
package com.seibel.distanthorizons.core.api.internal; 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.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry; import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
import com.seibel.distanthorizons.core.world.*; import com.seibel.distanthorizons.core.world.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
@@ -77,7 +74,6 @@ public class ServerApi
} }
//==============// //==============//
// level events // // level events //
//==============// //==============//
@@ -90,7 +86,6 @@ public class ServerApi
if (serverWorld != null) if (serverWorld != null)
{ {
serverWorld.getOrLoadLevel(levelWrapper); serverWorld.getOrLoadLevel(levelWrapper);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
} }
} }
public void serverLevelUnloadEvent(IServerLevelWrapper level) public void serverLevelUnloadEvent(IServerLevelWrapper level)
@@ -101,19 +96,16 @@ public class ServerApi
if (serverWorld != null) if (serverWorld != null)
{ {
serverWorld.unloadLevel(level); serverWorld.unloadLevel(level);
SharedApi.INSTANCE.clearQueuedChunkUpdates();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
} }
} }
//=======================// //=======================//
// chunk modified events // // chunk modified events //
//=======================// //=======================//
public void serverChunkLoadEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level, false, false); } public void serverChunkLoadEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level); }
public void serverChunkSaveEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level, true, false); } public void serverChunkSaveEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level); }
@@ -123,7 +115,7 @@ public class ServerApi
public void serverPlayerJoinEvent(IServerPlayerWrapper player) public void serverPlayerJoinEvent(IServerPlayerWrapper player)
{ {
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly()) if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
{ {
return; return;
} }
@@ -137,7 +129,7 @@ public class ServerApi
} }
public void serverPlayerDisconnectEvent(IServerPlayerWrapper player) public void serverPlayerDisconnectEvent(IServerPlayerWrapper player)
{ {
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly()) if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
{ {
return; return;
} }
@@ -151,7 +143,7 @@ public class ServerApi
} }
public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel) public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
{ {
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly()) if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
{ {
return; return;
} }
@@ -171,7 +163,7 @@ public class ServerApi
*/ */
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message) public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message)
{ {
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly()) if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
{ {
return; return;
} }
@@ -24,27 +24,22 @@ import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldUn
import com.seibel.distanthorizons.core.Initializer; import com.seibel.distanthorizons.core.Initializer;
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateData; import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateData;
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateQueueManager; import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateQueueManager;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.api.internal.chunkUpdating.WorldChunkUpdateManager;
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler; import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.DhClientLevel; import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo; import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.Pair; import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.*; import com.seibel.distanthorizons.core.world.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; 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.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
@@ -63,44 +58,37 @@ public class SharedApi
/** will be null on the server-side */ /** will be null on the server-side */
@Nullable @Nullable
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
/** will be null on the server-side */
@Nullable
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
public static final ChunkUpdateQueueManager CHUNK_UPDATE_QUEUE_MANAGER = new ChunkUpdateQueueManager(); public static final WorldChunkUpdateManager WORLD_CHUNK_UPDATE_MANAGER = WorldChunkUpdateManager.INSTANCE; // local fariable for quick access
/**
* how many chunks can be queued for updating per thread + player (in multiplayer),
* used to prevent updates from infinitely pilling up if the user flies around extremely fast
*/
public static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER = 1_000;
/** how many milliseconds must pass before an overloaded message can be sent in chat or the log */
public static final int MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE = 30_000;
@Nullable @Nullable
private static AbstractDhWorld currentWorld; private static AbstractDhWorld currentWorld;
private static int lastWorldGenTickDelta = 0; private static final Object worldLockObject = new Object();
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
private SharedApi() { } private SharedApi() { }
public static void init() { Initializer.init(); }
//endregion
//===============// //===============//
// world methods // // world methods //
//===============// //===============//
//region
public static EWorldEnvironment getEnvironment() { return (currentWorld == null) ? null : currentWorld.environment; } public static EWorldEnvironment getEnvironment() { return (currentWorld == null) ? null : currentWorld.environment; }
public static void setDhWorld(AbstractDhWorld newWorld) public static void setDhWorld(AbstractDhWorld newWorld)
{
synchronized (worldLockObject)
{ {
AbstractDhWorld oldWorld = currentWorld; AbstractDhWorld oldWorld = currentWorld;
if (oldWorld != null) if (oldWorld != null)
@@ -120,7 +108,10 @@ public class SharedApi
else else
{ {
ThreadPoolUtil.shutdownThreadPools(); ThreadPoolUtil.shutdownThreadPools();
DebugRenderer.clearRenderables();
// delayed get because SharedApi will be created before the singleton has been bound
AbstractDebugWireframeRenderer debugWireframeRenderer = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
debugWireframeRenderer.clearRenderables();
if (MC_RENDER != null) if (MC_RENDER != null)
{ {
@@ -130,7 +121,9 @@ public class SharedApi
// shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed // shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed
AbstractDhRepo.closeAllConnections(); AbstractDhRepo.closeAllConnections();
// needs to be closed on world shutdown to clear out un-processed chunks // needs to be closed on world shutdown to clear out un-processed chunks
CHUNK_UPDATE_QUEUE_MANAGER.clear(); WORLD_CHUNK_UPDATE_MANAGER.clear();
RenderThreadTaskHandler.INSTANCE.clearDebugStats();
// recommend that the garbage collector cleans up any objects from the old world and thread pools // recommend that the garbage collector cleans up any objects from the old world and thread pools
System.gc(); System.gc();
@@ -141,6 +134,7 @@ public class SharedApi
DhApiWorldProxy.INSTANCE.setReadOnly(false, false); DhApiWorldProxy.INSTANCE.setReadOnly(false, false);
} }
} }
}
@Nullable @Nullable
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; } public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; }
@@ -153,39 +147,44 @@ public class SharedApi
@Nullable @Nullable
public static IDhServerWorld tryGetDhServerWorld() { return (currentWorld instanceof IDhServerWorld) ? (IDhServerWorld) currentWorld : null; } public static IDhServerWorld tryGetDhServerWorld() { return (currentWorld instanceof IDhServerWorld) ? (IDhServerWorld) currentWorld : null; }
//endregion
//==============// //==============//
// chunk update // // chunk update //
//==============// //==============//
//region
/** /**
* Used to prevent getting a full chunk from MC if it isn't necessary. <br> * Used to prevent getting a full chunk from MC if it isn't necessary. <br>
* This is important since asking MC for a chunk is slow and may block the render thread. * This is important since asking MC for a chunk is slow and may block the render thread.
*/ */
public static boolean isChunkAtBlockPosAlreadyUpdating(int blockPosX, int blockPosZ) public static boolean isChunkAtBlockPosAlreadyUpdating(ILevelWrapper levelWrapper, int blockPosX, int blockPosZ)
{ return CHUNK_UPDATE_QUEUE_MANAGER.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ))); } {
ChunkUpdateQueueManager manager = WORLD_CHUNK_UPDATE_MANAGER.getByLevelWrapper(levelWrapper);
if (manager == null)
{
return true;
}
public static boolean isChunkAtChunkPosAlreadyUpdating(int chunkPosX, int chunkPosZ) return manager.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ)));
{ return CHUNK_UPDATE_QUEUE_MANAGER.contains(new DhChunkPos(chunkPosX, chunkPosZ)); } }
/** public static boolean isChunkAtChunkPosAlreadyUpdating(ILevelWrapper levelWrapper, int chunkPosX, int chunkPosZ)
* This is often fired when unloading a level. {
* This is done to prevent overloading the system when ChunkUpdateQueueManager manager = WORLD_CHUNK_UPDATE_MANAGER.getByLevelWrapper(levelWrapper);
* rapidly changing dimensions. if (manager == null)
* (IE prevent DH from infinitely allocating memory {
*/ return true;
public void clearQueuedChunkUpdates() { CHUNK_UPDATE_QUEUE_MANAGER.clear(); } }
public int getQueuedChunkUpdateCount() { return CHUNK_UPDATE_QUEUE_MANAGER.getQueuedCount(); } return manager.contains(new DhChunkPos(chunkPosX, chunkPosZ));
}
/** handles both block place and break events */ public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper levelWrapper)
public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, false); }
public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, true); }
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks, boolean newlyLoaded)
{ {
//===================// //===================//
// validation checks // // validation checks //
@@ -200,32 +199,32 @@ public class SharedApi
AbstractDhWorld dhWorld = SharedApi.getAbstractDhWorld(); AbstractDhWorld dhWorld = SharedApi.getAbstractDhWorld();
if (dhWorld == null) if (dhWorld == null)
{ {
if (level instanceof IClientLevelWrapper) if (levelWrapper instanceof IClientLevelWrapper)
{ {
// If the client world isn't loaded yet, keep track of which chunks were loaded so we can use them later. // If the client world isn't loaded yet, keep track of which chunks were loaded so we can use them later.
// This may happen if the client world and client level load events happen out of order // This may happen if the client world and client level load events happen out of order
IClientLevelWrapper clientLevel = (IClientLevelWrapper) level; IClientLevelWrapper clientLevel = (IClientLevelWrapper) levelWrapper;
ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.replace(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper); ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.put(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper);
} }
return; return;
} }
// ignore updates if the world is read-only // ignore updates if the world is read-only
if (DhApiWorldProxy.INSTANCE.getReadOnly()) if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
{ {
return; return;
} }
// only continue if the level is loaded // only continue if the level is loaded
IDhLevel dhLevel = dhWorld.getLevel(level); IDhLevel dhLevel = dhWorld.getLevel(levelWrapper);
if (dhLevel == null) if (dhLevel == null)
{ {
if (level instanceof IClientLevelWrapper) if (levelWrapper instanceof IClientLevelWrapper)
{ {
// the client level isn't loaded yet // the client level isn't loaded yet
IClientLevelWrapper clientLevel = (IClientLevelWrapper) level; IClientLevelWrapper clientLevel = (IClientLevelWrapper) levelWrapper;
ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.replace(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper); ClientApi.INSTANCE.waitingChunkByClientLevelAndPos.put(new Pair<>(clientLevel, chunkWrapper.getChunkPos()), chunkWrapper);
} }
return; return;
@@ -247,21 +246,23 @@ public class SharedApi
return; return;
} }
// shouldn't normally happen, but just in case ChunkUpdateQueueManager chunkManager = WORLD_CHUNK_UPDATE_MANAGER.getByLevelWrapper(levelWrapper);
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos())) // ignore the wrong level wrapper type or
// if the chunk is already queued for handling
if (chunkManager == null
|| chunkManager.contains(chunkWrapper.getChunkPos()))
{ {
// TODO this will prevent some LODs from updating across dimensions if multiple levels are loaded
return; return;
} }
queueChunkUpdate(chunkWrapper, dhLevel); queueChunkUpdate(chunkManager, chunkWrapper, dhLevel);
} }
private static void queueChunkUpdate(IChunkWrapper chunkWrapper, IDhLevel dhLevel) private static void queueChunkUpdate(ChunkUpdateQueueManager chunkManager, IChunkWrapper chunkWrapper, IDhLevel dhLevel)
{ {
// return if the chunk is already queued // return if the chunk is already queued
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos())) if (chunkManager.contains(chunkWrapper.getChunkPos()))
{ {
return; return;
} }
@@ -269,188 +270,36 @@ public class SharedApi
// add chunk update data to preUpdate queue // add chunk update data to preUpdate queue
ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, dhLevel); ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, dhLevel);
CHUNK_UPDATE_QUEUE_MANAGER.addItemToPreUpdateQueue(chunkWrapper.getChunkPos(), updateData); chunkManager.addItemToPreUpdateQueue(chunkWrapper.getChunkPos(), updateData);
// queue updates up to the number of CPU cores allocated for the job
// (this prevents doing extra work queuing tasks that may not be necessary)
// and makes sure the chunks closest to the player are updated first
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor != null
&& executor.getQueueSize() < executor.getPoolSize())
{
try
{
executor.execute(SharedApi::processQueue);
}
catch (RejectedExecutionException ignore)
{
// the executor was shut down, it should be back up shortly and able to accept new jobs
}
}
}
private static void processQueue()
{
// update the center & max size of the queue manager
int maxUpdateSizeMultiplier;
if (MC_CLIENT != null && MC_CLIENT.playerExists())
{
// Local worlds & multiplayer
CHUNK_UPDATE_QUEUE_MANAGER.setCenter(MC_CLIENT.getPlayerChunkPos());
maxUpdateSizeMultiplier = MC_CLIENT.clientConnectedToDedicatedServer() ? 1 : MC_SHARED.getPlayerCount();
}
else
{
// Dedicated servers
// Also includes spawn chunks since they're likely to be intentionally utilized with updates
maxUpdateSizeMultiplier = 1 + MC_SHARED.getPlayerCount();
}
CHUNK_UPDATE_QUEUE_MANAGER.maxSize = MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER
* Config.Common.MultiThreading.numberOfThreads.get()
* maxUpdateSizeMultiplier;
//===============================//
// update the necessary chunk(s) //
//===============================//
processQueuedChunkPreUpdate();
processQueuedChunkUpdate();
// queue the next position if there are still positions to process // queue the next position if there are still positions to process
AbstractExecutorService executor = ThreadPoolUtil.getChunkToLodBuilderExecutor(); AbstractExecutorService executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor != null && !CHUNK_UPDATE_QUEUE_MANAGER.isEmpty()) if (executor != null)
{ {
try try
{ {
executor.execute(SharedApi::processQueue); executor.execute(WORLD_CHUNK_UPDATE_MANAGER::processEachQueue);
} }
catch (RejectedExecutionException ignore) catch (RejectedExecutionException ignore)
{ {
// the executor was shut down, it should be back up shortly and able to accept new jobs // the executor was shut down, it should be back up shortly and able to accept new jobs
} }
} }
} }
private static void processQueuedChunkPreUpdate() //endregion
{
ChunkUpdateData preUpdateData = CHUNK_UPDATE_QUEUE_MANAGER.preUpdateQueue.popClosest();
if (preUpdateData == null)
{
return;
}
IDhLevel dhLevel = preUpdateData.dhLevel;
IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper;
chunkWrapper.createDhHeightMaps();
try
{
// check if this chunk has been converted into an LOD already
boolean checkChunkHash = !Config.Common.LodBuilding.disableUnchangedChunkCheck.get();
if (checkChunkHash)
{
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();
boolean hasNewChunkHash = (oldChunkHash != newChunkHash);
if (!hasNewChunkHash)
{
// do not update the chunk if the hash is the same
return;
}
}
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(chunkWrapper.getChunkPos(), preUpdateData);
}
catch (Exception e)
{
LOGGER.error("Unexpected error when pre-updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
}
}
private static void processQueuedChunkUpdate()
{
ChunkUpdateData updateData = CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.popClosest();
if (updateData == null)
{
return;
}
IChunkWrapper chunkWrapper = updateData.chunkWrapper;
IDhLevel dhLevel = updateData.dhLevel;
ILevelWrapper levelWrapper = dhLevel.getLevelWrapper();
// having a list of the nearby chunks is needed for lighting and beacon generation
ArrayList<IChunkWrapper> nearbyChunkList = tryGetNeighborChunkListForChunk(chunkWrapper);
try
{
// sky lighting is populated later at the data source level
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, levelWrapper.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList);
int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
dhLevel.updateChunkAsync(chunkWrapper, newChunkHash);
}
catch (Exception e)
{
LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
}
CHUNK_UPDATE_QUEUE_MANAGER.queuedChunkWrapperByChunkPos.remove(updateData.chunkWrapper.getChunkPos());
}
private static ArrayList<IChunkWrapper> tryGetNeighborChunkListForChunk(IChunkWrapper chunkWrapper)
{
// get the neighboring chunk list
ArrayList<IChunkWrapper> neighborChunkList = new ArrayList<>(9);
for (int xOffset = -1; xOffset <= 1; xOffset++)
{
for (int zOffset = -1; zOffset <= 1; zOffset++)
{
if (xOffset == 0 && zOffset == 0)
{
// center chunk
neighborChunkList.add(chunkWrapper);
}
else
{
// neighboring chunk
DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
IChunkWrapper neighborChunk = CHUNK_UPDATE_QUEUE_MANAGER.tryGetChunk(neighborPos);
if (neighborChunk != null)
{
neighborChunkList.add(neighborChunk);
}
}
}
}
return neighborChunkList;
}
//=========// //=========//
// F3 Menu // // F3 Menu //
//=========// //=========//
//region
public String getDebugMenuString() public ArrayList<String> getDebugMenuString() { return WORLD_CHUNK_UPDATE_MANAGER.getDebugMenuString(); }
{
String preUpdatingCountStr = F3Screen.NUMBER_FORMAT.format(CHUNK_UPDATE_QUEUE_MANAGER.preUpdateQueue.getQueuedCount());
String updatingCountStr = F3Screen.NUMBER_FORMAT.format(CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.getQueuedCount());
String queuedCountStr = F3Screen.NUMBER_FORMAT.format(CHUNK_UPDATE_QUEUE_MANAGER.getQueuedCount());
String maxUpdateCountStr = F3Screen.NUMBER_FORMAT.format(CHUNK_UPDATE_QUEUE_MANAGER.maxSize); //endregion
return "Queued chunk updates: "+"( "+preUpdatingCountStr+" + "+updatingCountStr+" ) [ "+queuedCountStr+" / "+maxUpdateCountStr+" ]";
}
@@ -4,28 +4,58 @@ import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi; import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.EWorldEnvironment; import com.seibel.distanthorizons.core.world.EWorldEnvironment;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.*;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
/**
* @see WorldChunkUpdateManager
*/
public class ChunkUpdateQueueManager public class ChunkUpdateQueueManager
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
/**
* how many chunks can be queued for updating per thread + player (in multiplayer),
* used to prevent updates from infinitely pilling up if the user flies around extremely fast
*/
public static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER = 1_000;
/** how many milliseconds must pass before an overloaded message can be sent in chat or the log */
public static final int MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE = 30_000;
private final Set<DhChunkPos> ignoredChunkPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
private static long lastOverloadedLogMessageMsTime = 0;
public final ChunkPosQueue updateQueue; public final ChunkPosQueue updateQueue;
public final ChunkPosQueue preUpdateQueue; public final ChunkPosQueue preUpdateQueue;
public final Set<DhChunkPos> ignoredChunkPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
public final ConcurrentMap<DhChunkPos, IChunkWrapper> queuedChunkWrapperByChunkPos = CacheBuilder.newBuilder() public final ConcurrentMap<DhChunkPos, IChunkWrapper> queuedChunkWrapperByChunkPos = CacheBuilder.newBuilder()
.expireAfterWrite(20, TimeUnit.SECONDS) .expireAfterWrite(20, TimeUnit.SECONDS)
@@ -35,13 +65,15 @@ public class ChunkUpdateQueueManager
/** dynamically changes based on the number of threads currently available */ /** dynamically changes based on the number of threads currently available */
public int maxSize = 500; public int maxSize = 500;
private static long lastOverloadedLogMessageMsTime = 0; /** used to prevent flickering */
public long lastMsTimeShownActiveInF3Screen = System.currentTimeMillis();
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
public ChunkUpdateQueueManager() public ChunkUpdateQueueManager()
{ {
@@ -49,11 +81,14 @@ public class ChunkUpdateQueueManager
this.preUpdateQueue = new ChunkPosQueue(); this.preUpdateQueue = new ChunkPosQueue();
} }
//endregion
//==================// //==================//
// list/set methods // // list/set methods //
//==================// //==================//
//region
public boolean contains(DhChunkPos pos) public boolean contains(DhChunkPos pos)
{ {
@@ -69,7 +104,8 @@ public class ChunkUpdateQueueManager
this.ignoredChunkPosSet.clear(); this.ignoredChunkPosSet.clear();
} }
public int getQueuedCount() { return this.updateQueue.getQueuedCount() + this.preUpdateQueue.getQueuedCount(); } public int getQueuedCount() { return this.updateQueue.getQueuedCount() + this.preUpdateQueue.getQueuedCount(); }
public boolean isEmpty()
public boolean updateQueuesEmpty()
{ {
return this.updateQueue.isEmpty() return this.updateQueue.isEmpty()
&& this.preUpdateQueue.isEmpty(); && this.preUpdateQueue.isEmpty();
@@ -114,14 +150,14 @@ public class ChunkUpdateQueueManager
{ {
// limit how often an overloaded message can be sent // limit how often an overloaded message can be sent
long msBetweenLastLog = System.currentTimeMillis() - lastOverloadedLogMessageMsTime; long msBetweenLastLog = System.currentTimeMillis() - lastOverloadedLogMessageMsTime;
if (msBetweenLastLog >= SharedApi.MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE) if (msBetweenLastLog >= MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE)
{ {
lastOverloadedLogMessageMsTime = System.currentTimeMillis(); lastOverloadedLogMessageMsTime = System.currentTimeMillis();
String message = MinecraftTextFormat.ORANGE + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + MinecraftTextFormat.CLEAR_FORMATTING + String message = MinecraftTextFormat.ORANGE + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + MinecraftTextFormat.CLEAR_FORMATTING +
"\nThis may result in holes in your LODs. " + "\nThis may result in holes in your LODs. " +
"\nFix: move through the world slower, decrease your vanilla render distance, slow down your world pre-generator (IE Chunky), or increase the Distant Horizons' CPU thread counts. " + "\nFix: move through the world slower, decrease your vanilla render distance, slow down your world pre-generator (IE Chunky), or increase the Distant Horizons' CPU thread counts. " +
"\nMax queue count [" + SharedApi.CHUNK_UPDATE_QUEUE_MANAGER.maxSize + "] ([" + SharedApi.MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER + "] per thread+players)."; "\nMax queue count [" + this.maxSize + "] ([" + MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER + "] per thread+players).";
boolean showWarningInChat = Config.Common.Logging.Warning.showUpdateQueueOverloadedChatWarning.get(); boolean showWarningInChat = Config.Common.Logging.Warning.showUpdateQueueOverloadedChatWarning.get();
if (showWarningInChat) if (showWarningInChat)
@@ -159,20 +195,180 @@ public class ChunkUpdateQueueManager
return existingWrapper.copy(); return existingWrapper.copy();
} }
//endregion
//=========// //=========//
// ignores // // ignores //
//=========// //=========//
//region
public void addPosToIgnore(DhChunkPos chunkPos) { this.ignoredChunkPosSet.add(chunkPos); } public void addPosToIgnore(DhChunkPos chunkPos) { this.ignoredChunkPosSet.add(chunkPos); }
public void removePosToIgnore(DhChunkPos chunkPos) { this.ignoredChunkPosSet.remove(chunkPos); } public void removePosToIgnore(DhChunkPos chunkPos) { this.ignoredChunkPosSet.remove(chunkPos); }
//endregion
//===================//
// update processing //
//===================//
//region
public void processQueue()
{
// update the center & max size of the queue manager
int maxUpdateSizeMultiplier;
if (MC_CLIENT != null && MC_CLIENT.playerExists())
{
// Local worlds & multiplayer
this.setCenter(MC_CLIENT.getPlayerChunkPos());
maxUpdateSizeMultiplier = MC_CLIENT.clientConnectedToDedicatedServer() ? 1 : MC_SHARED.getPlayerCount();
}
else
{
// Dedicated servers
// Also includes spawn chunks since they're likely to be intentionally utilized with updates
maxUpdateSizeMultiplier = 1 + MC_SHARED.getPlayerCount();
}
this.maxSize = MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER
* Config.Common.MultiThreading.numberOfThreads.get()
* maxUpdateSizeMultiplier;
//===============================//
// update the necessary chunk(s) //
//===============================//
this.processQueuedChunkPreUpdate();
this.processQueuedChunkUpdate();
// queue the next position if there are still positions to process
AbstractExecutorService executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor != null && !this.updateQueuesEmpty())
{
try
{
executor.execute(this::processQueue);
}
catch (RejectedExecutionException ignore)
{
// the executor was shut down, it should be back up shortly and able to accept new jobs
}
}
}
private void processQueuedChunkPreUpdate()
{
ChunkUpdateData preUpdateData = this.preUpdateQueue.popClosest();
if (preUpdateData == null)
{
return;
}
IDhLevel dhLevel = preUpdateData.dhLevel;
IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper;
chunkWrapper.createDhHeightMaps();
try
{
// check if this chunk has been converted into an LOD already
boolean checkChunkHash = !Config.Common.LodBuilding.disableUnchangedChunkCheck.get();
if (checkChunkHash)
{
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();
boolean hasNewChunkHash = (oldChunkHash != newChunkHash);
if (!hasNewChunkHash)
{
// do not update the chunk if the hash is the same
return;
}
}
this.addItemToUpdateQueue(chunkWrapper.getChunkPos(), preUpdateData);
}
catch (Exception e)
{
LOGGER.error("Unexpected error when pre-updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
}
}
private void processQueuedChunkUpdate()
{
ChunkUpdateData updateData = this.updateQueue.popClosest();
if (updateData == null)
{
return;
}
IChunkWrapper chunkWrapper = updateData.chunkWrapper;
IDhLevel dhLevel = updateData.dhLevel;
ILevelWrapper levelWrapper = dhLevel.getLevelWrapper();
// having a list of the nearby chunks is needed for lighting and beacon generation
ArrayList<IChunkWrapper> nearbyChunkList = this.tryGetNeighborChunkListForChunk(chunkWrapper);
try
{
// sky lighting is populated later at the data source level
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, levelWrapper.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList);
int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
dhLevel.updateChunkAsync(chunkWrapper, newChunkHash);
}
catch (Exception e)
{
LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
}
this.queuedChunkWrapperByChunkPos.remove(updateData.chunkWrapper.getChunkPos());
}
private ArrayList<IChunkWrapper> tryGetNeighborChunkListForChunk(IChunkWrapper chunkWrapper)
{
// get the neighboring chunk list
ArrayList<IChunkWrapper> neighborChunkList = new ArrayList<>(9);
for (int xOffset = -1; xOffset <= 1; xOffset++)
{
for (int zOffset = -1; zOffset <= 1; zOffset++)
{
if (xOffset == 0 && zOffset == 0)
{
// center chunk
neighborChunkList.add(chunkWrapper);
}
else
{
// neighboring chunk
DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
IChunkWrapper neighborChunk = this.tryGetChunk(neighborPos);
if (neighborChunk != null)
{
neighborChunkList.add(neighborChunk);
}
}
}
}
return neighborChunkList;
}
//endregion
//==================// //==================//
// position methods // // position methods //
//==================// //==================//
//region
public void setCenter(DhChunkPos newCenter) public void setCenter(DhChunkPos newCenter)
{ {
@@ -180,5 +376,33 @@ public class ChunkUpdateQueueManager
this.preUpdateQueue.setCenter(newCenter); this.preUpdateQueue.setCenter(newCenter);
} }
//endregion
//=========//
// F3 Menu //
//=========//
//region
public String getDebugMenuString()
{
String y = MinecraftTextFormat.YELLOW;
String o = MinecraftTextFormat.ORANGE;
String cf = MinecraftTextFormat.CLEAR_FORMATTING;
String preUpdatingCountStr = F3Screen.NUMBER_FORMAT.format(this.preUpdateQueue.getQueuedCount());
String updatingCountStr = F3Screen.NUMBER_FORMAT.format(this.updateQueue.getQueuedCount());
String queuedCountStr = F3Screen.NUMBER_FORMAT.format(this.getQueuedCount());
String maxUpdateCountStr = F3Screen.NUMBER_FORMAT.format(this.maxSize);
return "Queued chunk updates: "+"("+y+preUpdatingCountStr+cf+" + "+o+updatingCountStr+cf+") ["+queuedCountStr+"/"+maxUpdateCountStr+"]";
}
//endregion
} }
@@ -0,0 +1,190 @@
package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Holds all the {@link ChunkUpdateQueueManager} for a loaded world.
* Different queues are needed for each level to prevent
* chunks from bleeding between levels (IE a nether chunk applied to the overworld).
*
* @see ChunkUpdateQueueManager
*/
public class WorldChunkUpdateManager
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** singleton since we only expect to have one world loaded at a time */
public static final WorldChunkUpdateManager INSTANCE = new WorldChunkUpdateManager();
public static final Set<String> LOGGED_GET_ERROR_MESSAGES = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
/**
* Queues are only removed during world shutdown.
* The assumption is that there will be a limited number of {@link ILevelWrapper}'s
* for a given world.
*/
private final ConcurrentHashMap<ILevelWrapper, ChunkUpdateQueueManager> updateQueueByLevelWrapper = new ConcurrentHashMap<>();
//=============//
// constructor //
//=============//
//region
private WorldChunkUpdateManager() { }
//endregion
//=================//
// manager methods //
//=================//
//region
/**
* @return null if the world is unloaded or the given level wrapper is the wrong type
*/
@Nullable
public ChunkUpdateQueueManager getByLevelWrapper(ILevelWrapper levelWrapper)
{
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world == null)
{
// world isn't loaded, no warnings need to be logged
return null;
}
// we only want to load chunks for certain level wrappers
// this is done specifically on a local-server to prevent
// loading both the server and client level wrappers
if (world.environment == EWorldEnvironment.CLIENT_ONLY
// when connected to a server we should only ever load client wrappers anyway
// but this check confirms it
&& !(levelWrapper instanceof IClientLevelWrapper))
{
// how did we get a server level wrapper on the client?
// this shouldn't happen, but just in case
return null;
}
else if (
(world.environment == EWorldEnvironment.SERVER_ONLY
|| world.environment == EWorldEnvironment.CLIENT_SERVER)
// when hosting a server we only care about the server wrappers
&& !(levelWrapper instanceof IServerLevelWrapper))
{
// ignore client updates on the server
return null;
}
ChunkUpdateQueueManager queueManager = this.updateQueueByLevelWrapper.get(levelWrapper);
if (queueManager != null)
{
return queueManager;
}
return this.updateQueueByLevelWrapper.compute(levelWrapper,
(ILevelWrapper newLevelWrapper, ChunkUpdateQueueManager oldQueueManager) ->
{
if (oldQueueManager != null)
{
return oldQueueManager;
}
oldQueueManager = new ChunkUpdateQueueManager();
return oldQueueManager;
});
}
public void processEachQueue()
{
this.updateQueueByLevelWrapper.forEach(
(ILevelWrapper levelWrapper, ChunkUpdateQueueManager updateManager) ->
{
updateManager.processQueue();
});
}
public int getTotalQueuedCount()
{
AtomicInteger queueCountRef = new AtomicInteger(0);
this.updateQueueByLevelWrapper.forEach(
(ILevelWrapper levelWrapper, ChunkUpdateQueueManager updateManager) ->
{
queueCountRef.addAndGet(updateManager.getQueuedCount());
});
return queueCountRef.get();
}
public void clear() { this.updateQueueByLevelWrapper.clear(); }
//endregion
//=========//
// F3 Menu //
//=========//
//region
public ArrayList<String> getDebugMenuString()
{
ArrayList<String> stringList = new ArrayList<>();
stringList.add("");// placeholder for the total count
// add each queue to the list
AtomicInteger totalQueueCountRef = new AtomicInteger(0);
AtomicInteger activeQueueCountRef = new AtomicInteger(0);
this.updateQueueByLevelWrapper.forEach(
(ILevelWrapper levelWrapper, ChunkUpdateQueueManager updateManager) ->
{
// is this queue active?
if (!updateManager.updateQueuesEmpty())
{
updateManager.lastMsTimeShownActiveInF3Screen = System.currentTimeMillis();
activeQueueCountRef.incrementAndGet();
}
// show this queue if it hasn't been empty long enough
// (done to prevent flickering on the F3 screen when the queue rapidly fills/empties)
long timeSinceQueueLastShownActiveMs = System.currentTimeMillis() - updateManager.lastMsTimeShownActiveInF3Screen;
if (timeSinceQueueLastShownActiveMs < 4_000)
{
stringList.add(levelWrapper.getDimensionName() + ": " + updateManager.getDebugMenuString());
}
totalQueueCountRef.incrementAndGet();
});
// replace the first line with the number of total/active queues
// (helpful if we need to diagnose a leak due to a massive number of queue level wrappers)
stringList.set(0, "Chunk Update Queues: "+activeQueueCountRef.get()+"/"+totalQueueCountRef.get());
return stringList;
}
//endregion
}
@@ -1,7 +1,8 @@
package com.seibel.distanthorizons.core.api.internal.rendering; package com.seibel.distanthorizons.core.api.internal.rendering;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.DhMat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
/** /**
@@ -11,11 +12,34 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
*/ */
public class DhRenderState public class DhRenderState
{ {
public Mat4f mcModelViewMatrix = null; public DhMat4f mcModelViewMatrix = null;
public Mat4f mcProjectionMatrix = null; public DhMat4f mcProjectionMatrix = null;
public float frameTime = -1; /**
* percentage of time into the current client tick. <br><br>
*
* Can be converted to a millisecond frametime
* (IE time between frames in milliseconds) using the formula: <br>
* <code>
* (partialTickTime/20*1000)
* </code> <br>
* IE 60 FPS = 16.6 MS <br>
*
* @link https://fpstoms.com/
* @see IMinecraftRenderWrapper#getPartialTickTime()
*/
public float partialTickTime = -1;
public IClientLevelWrapper clientLevelWrapper = null; public IClientLevelWrapper clientLevelWrapper = null;
/**
* This will generally be true if the player is: <br>
* - blinded <br>
* - under lava/water <br>
* <br>
* In those cases some rendering logic may need to be changed
* to look correct.
*/
public boolean vanillaFogEnabled = false;
//========// //========//
@@ -38,7 +62,7 @@ public class DhRenderState
errorReasons += "no Projection Matrix, "; errorReasons += "no Projection Matrix, ";
} }
if (this.frameTime == -1) if (this.partialTickTime == -1)
{ {
errorReasons += "no Frame Time, "; errorReasons += "no Frame Time, ";
} }
@@ -51,13 +75,6 @@ public class DhRenderState
return errorReasons; return errorReasons;
} }
public boolean canRender()
{
// separated variable to allow for easy checking with the debugger
String errorReasons = this.unableToRenderBecause();
return errorReasons.isEmpty();
}
public void canRenderOrThrow() throws IllegalStateException public void canRenderOrThrow() throws IllegalStateException
{ {
String errorReasons = this.unableToRenderBecause(); String errorReasons = this.unableToRenderBecause();
@@ -19,7 +19,6 @@
package com.seibel.distanthorizons.core.config; package com.seibel.distanthorizons.core.config;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.*; import com.seibel.distanthorizons.api.enums.config.*;
import com.seibel.distanthorizons.api.enums.config.quickOptions.*; import com.seibel.distanthorizons.api.enums.config.quickOptions.*;
import com.seibel.distanthorizons.api.enums.rendering.*; import com.seibel.distanthorizons.api.enums.rendering.*;
@@ -27,14 +26,12 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGenerat
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorProgressDisplayLocation; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorProgressDisplayLocation;
import com.seibel.distanthorizons.core.config.eventHandlers.*; import com.seibel.distanthorizons.core.config.eventHandlers.*;
import com.seibel.distanthorizons.core.config.eventHandlers.presets.*; import com.seibel.distanthorizons.core.config.eventHandlers.presets.*;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.types.*; import com.seibel.distanthorizons.core.config.types.*;
import com.seibel.distanthorizons.core.config.types.enums.*; import com.seibel.distanthorizons.core.config.types.enums.*;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.NativeDialogUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
@@ -47,7 +44,6 @@ import java.util.List;
* This handles any configuration the user has access to. * This handles any configuration the user has access to.
* *
* @author coolGi * @author coolGi
*
* @see ConfigHandler * @see ConfigHandler
*/ */
@SuppressWarnings("ConcatenationWithEmptyString") @SuppressWarnings("ConcatenationWithEmptyString")
@@ -63,9 +59,6 @@ public class Config
{ {
public static ConfigEntry<Boolean> quickEnableRendering = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> quickEnableRendering = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment(""
+ "If true, Distant Horizons will render LODs beyond the vanilla render distance."
+ "")
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.build(); .build();
@@ -73,12 +66,6 @@ public class Config
public static ConfigEntry<EDhApiQualityPreset> qualityPresetSetting = new ConfigEntry.Builder<EDhApiQualityPreset>() public static ConfigEntry<EDhApiQualityPreset> qualityPresetSetting = new ConfigEntry.Builder<EDhApiQualityPreset>()
.set(EDhApiQualityPreset.MEDIUM) // the default value is set via the listener when accessed .set(EDhApiQualityPreset.MEDIUM) // the default value is set via the listener when accessed
.comment(""
+ "Changing this setting will modify a number of different settings that will change the \n"
+ "visual fidelity of the rendered LODs.\n"
+ "\n"
+ "Higher settings will improve the graphical quality while increasing GPU and memory use.\n"
+ "")
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(RenderQualityPresetConfigEventHandler.INSTANCE) .addListener(RenderQualityPresetConfigEventHandler.INSTANCE)
.build(); .build();
@@ -86,16 +73,6 @@ public class Config
public static ConfigEntry<EDhApiThreadPreset> threadPresetSetting = new ConfigEntry.Builder<EDhApiThreadPreset>() public static ConfigEntry<EDhApiThreadPreset> threadPresetSetting = new ConfigEntry.Builder<EDhApiThreadPreset>()
.setChatCommandName("common.threadPreset") .setChatCommandName("common.threadPreset")
.set(EDhApiThreadPreset.BALANCED) // the default value is set via the listener when accessed .set(EDhApiThreadPreset.BALANCED) // the default value is set via the listener when accessed
.comment(""
+ "Changing this setting will modify a number of different settings that will change \n"
+ "the load that Distant Horizons is allowed to put on your CPU. \n"
+ "\n"
+ "Higher options will improve LOD generation and loading speed, \n"
+ "but will increase CPU load and may introduce stuttering.\n"
+ "\n"
+ "Note: This is a CPU relative setting. \n"
+ "It should put an equal amount of strain on a 2 core CPU as a 64 core CPU.\n"
+ "")
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(ThreadPresetConfigEventHandler.INSTANCE) .addListener(ThreadPresetConfigEventHandler.INSTANCE)
.build(); .build();
@@ -146,8 +123,11 @@ public class Config
public static ConfigCategory quality = new ConfigCategory.Builder().set(Quality.class).build(); public static ConfigCategory quality = new ConfigCategory.Builder().set(Quality.class).build();
public static ConfigUISpacer qualitySpacer = new ConfigUISpacer.Builder().build(); public static ConfigUISpacer qualitySpacer = new ConfigUISpacer.Builder().build();
public static ConfigUiLinkedEntry quickEnableSsao = new ConfigUiLinkedEntry(Ssao.enableSsao);
public static ConfigCategory ssao = new ConfigCategory.Builder().set(Ssao.class).build(); public static ConfigEntry<Boolean> enableSsao = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment("Enable Screen Space Ambient Occlusion")
.build();
public static ConfigUISpacer ssaoSpacer = new ConfigUISpacer.Builder().build(); public static ConfigUISpacer ssaoSpacer = new ConfigUISpacer.Builder().build();
@@ -194,14 +174,21 @@ public class Config
.setMinDefaultMax(32, 256, 4096) .setMinDefaultMax(32, 256, 4096)
.comment("" + .comment("" +
"The radius of the mod's render distance. (measured in chunks)\n" + "The radius of the mod's render distance. (measured in chunks)\n" +
"\n" +
"Note: this is a best effort number. \n" +
"The actual render distance may be above or below this number \n" +
"depending on your other graphic settings. \n" +
"") "")
.build(); .build();
public static ConfigUISpacer qualityDropoffSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<EDhApiHorizontalQuality> horizontalQuality = new ConfigEntry.Builder<EDhApiHorizontalQuality>() public static ConfigEntry<EDhApiHorizontalQuality> horizontalQuality = new ConfigEntry.Builder<EDhApiHorizontalQuality>()
.set(EDhApiHorizontalQuality.MEDIUM) .set(EDhApiHorizontalQuality.MEDIUM)
.comment("" .comment(""
+ "This indicates how quickly LODs decrease in quality the further away they are. \n" + "This indicates how far apart drops in LOD quality are. \n"
+ "Higher settings will render higher quality fake chunks farther away, \n" + "\n"
+ "Higher settings will increase the distance between drops \n"
+ "but will increase memory and GPU usage.") + "but will increase memory and GPU usage.")
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
@@ -209,7 +196,7 @@ public class Config
public static ConfigEntry<EDhApiMaxHorizontalResolution> maxHorizontalResolution = new ConfigEntry.Builder<EDhApiMaxHorizontalResolution>() public static ConfigEntry<EDhApiMaxHorizontalResolution> maxHorizontalResolution = new ConfigEntry.Builder<EDhApiMaxHorizontalResolution>()
.set(EDhApiMaxHorizontalResolution.BLOCK) .set(EDhApiMaxHorizontalResolution.BLOCK)
.comment("" .comment(""
+ "What is the maximum detail LODs should be drawn at? \n" + "What is the maximum detail LODs can render at? \n"
+ "Higher settings will increase memory and GPU usage. \n" + "Higher settings will increase memory and GPU usage. \n"
+ "\n" + "\n"
+ EDhApiMaxHorizontalResolution.CHUNK + ": render 1 LOD for each Chunk. \n" + EDhApiMaxHorizontalResolution.CHUNK + ": render 1 LOD for each Chunk. \n"
@@ -218,8 +205,8 @@ public class Config
+ EDhApiMaxHorizontalResolution.TWO_BLOCKS + ": render 64 LODs for each Chunk. \n" + EDhApiMaxHorizontalResolution.TWO_BLOCKS + ": render 64 LODs for each Chunk. \n"
+ EDhApiMaxHorizontalResolution.BLOCK + ": render 256 LODs for each Chunk (width of one block). \n" + EDhApiMaxHorizontalResolution.BLOCK + ": render 256 LODs for each Chunk (width of one block). \n"
+ "\n" + "\n"
+ "Lowest Quality: " + EDhApiMaxHorizontalResolution.CHUNK + "\n" + "Fastest: " + EDhApiMaxHorizontalResolution.CHUNK + "\n"
+ "Highest Quality: " + EDhApiMaxHorizontalResolution.BLOCK) + "Fanciest: " + EDhApiMaxHorizontalResolution.BLOCK)
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
@@ -232,51 +219,35 @@ public class Config
+ "will increase memory and GPU usage. \n" + "will increase memory and GPU usage. \n"
+ "\n" + "\n"
+ "Lowest Quality: " + EDhApiVerticalQuality.HEIGHT_MAP + "\n" + "Lowest Quality: " + EDhApiVerticalQuality.HEIGHT_MAP + "\n"
+ "Highest Quality: " + EDhApiVerticalQuality.EXTREME) + "Highest Quality: " + EDhApiVerticalQuality.PIXEL_ART)
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<Boolean> useCameraPositionForQualityDropOff = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If true DH will try to use the camera position when \n"
+ "determining LOD quality drop-off. \n"
+ "If false DH will use the player's position. \n"
+ " \n"
+ "Enabling helps free-cam mods render correctly. \n"
+ "Disabling helps multi-camera mods render correctly (ie Immersive Portals or camera mods). \n"
+ "")
.build();
public static ConfigUISpacer qualitySpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<EDhApiTransparency> transparency = new ConfigEntry.Builder<EDhApiTransparency>() public static ConfigEntry<EDhApiTransparency> transparency = new ConfigEntry.Builder<EDhApiTransparency>()
.set(EDhApiTransparency.COMPLETE) .set(EDhApiTransparency.COMPLETE)
.comment("" .comment(""
+ "How should LOD transparency be handled. \n" + "How should LOD transparency be handled. \n"
+ "\n" + "\n"
+ EDhApiTransparency.COMPLETE + ": LODs will render transparent. \n" + EDhApiTransparency.COMPLETE + ": LODs will render transparent. \n"
+ EDhApiTransparency.FAKE + ": LODs will be opaque, but shaded to match the blocks underneath. \n"
+ EDhApiTransparency.DISABLED + ": LODs will be opaque. \n" + EDhApiTransparency.DISABLED + ": LODs will be opaque. \n"
+ "") + "")
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<EDhApiBlocksToAvoid> blocksToIgnore = new ConfigEntry.Builder<EDhApiBlocksToAvoid>()
.set(EDhApiBlocksToAvoid.NON_COLLIDING)
.comment(""
+ "What blocks shouldn't be rendered as LODs? \n"
+ "\n"
+ EDhApiBlocksToAvoid.NONE + ": Represent all blocks in the LODs \n"
+ EDhApiBlocksToAvoid.NON_COLLIDING + ": Only represent solid blocks in the LODs (tall grass, torches, etc. won't count for a LOD's height) \n"
+ "")
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build();
public static ConfigEntry<Boolean> tintWithAvoidedBlocks = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "Should the blocks underneath avoided blocks gain the color of the avoided block? \n"
+ "\n"
+ "True: a red flower will tint the grass below it red. \n"
+ "False: skipped blocks will not change color of surface below them. "
+ "")
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build();
public static ConfigEntry<Double> lodBias = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0d, 0d, null)
.comment(""
+ "What value should vanilla Minecraft's texture LodBias be? \n"
+ "If set to 0 the mod wont overwrite vanilla's default (which so happens to also be 0)")
.build();
public static ConfigEntry<EDhApiLodShading> lodShading = new ConfigEntry.Builder<EDhApiLodShading>() public static ConfigEntry<EDhApiLodShading> lodShading = new ConfigEntry.Builder<EDhApiLodShading>()
.set(EDhApiLodShading.AUTO) .set(EDhApiLodShading.AUTO)
.comment("" .comment(""
@@ -302,6 +273,8 @@ public class Config
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigUISpacer fadeSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<Boolean> ditherDhFade = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> ditherDhFade = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("" .comment(""
@@ -330,8 +303,22 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Double> brightnessMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats) public static ConfigEntry<Integer> lodBiomeBlending = new ConfigEntry.Builder<Integer>()
.set(1.0) .setMinDefaultMax(0, 3, 3) // going higher than 3 causes banding issues for blending across LOD borders and an exponential increase in load times
.comment(""
+ "This is the same as vanilla Biome Blending settings for Lod area. \n"
+ " Note: anything above '0' will slow down LOD loading time. \n"
+ "\n"
+ " '0' equals to Vanilla Biome Blending of '1x1' or 'OFF', \n"
+ " '1' equals to Vanilla Biome Blending of '3x3', \n"
+ " '2' equals to Vanilla Biome Blending of '5x5'...")
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build();
public static ConfigUISpacer multiplierSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<Float> brightnessMultiplier = new ConfigEntry.Builder<Float>()
.set(1.0f)
.comment("" .comment(""
+ "How bright LOD colors are. \n" + "How bright LOD colors are. \n"
+ "\n" + "\n"
@@ -341,8 +328,8 @@ public class Config
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<Double> saturationMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats) public static ConfigEntry<Float> saturationMultiplier = new ConfigEntry.Builder<Float>()
.set(1.0) .set(1.0f)
.comment("" .comment(""
+ "How saturated LOD colors are. \n" + "How saturated LOD colors are. \n"
+ "\n" + "\n"
@@ -352,82 +339,6 @@ public class Config
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigEntry<Integer> lodBiomeBlending = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0,3,3) // going higher than 3 causes banding issues for blending across LOD borders and an exponential increase in load times
.comment(""
+ "This is the same as vanilla Biome Blending settings for Lod area. \n"
+ " Note that anything other than '0' will greatly effect Lod building time. \n"
+ "\n"
+ " '0' equals to Vanilla Biome Blending of '1x1' or 'OFF', \n"
+ " '1' equals to Vanilla Biome Blending of '3x3', \n"
+ " '2' equals to Vanilla Biome Blending of '5x5'...")
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build();
}
public static class Ssao
{
public static ConfigUIComment ssaoHeader = new ConfigUIComment.Builder().setParentConfigClass(Ssao.class).build();
public static ConfigEntry<Boolean> enableSsao = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment("Enable Screen Space Ambient Occlusion")
.build();
public static ConfigEntry<Integer> sampleCount = new ConfigEntry.Builder<Integer>()
.set(6)
.comment("" +
"Determines how many points in space are sampled for the occlusion test. \n" +
"Higher numbers will improve quality and reduce banding, but will increase GPU load." +
"")
.build();
public static ConfigEntry<Double> radius = new ConfigEntry.Builder<Double>()
.set(4.0)
.comment("" +
"Determines the radius Screen Space Ambient Occlusion is applied, measured in blocks." +
"")
.build();
public static ConfigEntry<Double> strength = new ConfigEntry.Builder<Double>()
.set(0.2)
.comment("" +
"Determines how dark the Screen Space Ambient Occlusion effect will be." +
"")
.build();
public static ConfigEntry<Double> bias = new ConfigEntry.Builder<Double>()
.set(0.02)
.comment("" +
"Increasing the value can reduce banding at the cost of reducing the strength of the effect." +
"")
.build();
public static ConfigEntry<Double> minLight = new ConfigEntry.Builder<Double>()
.set(0.25)
.comment("" +
"Determines how dark the occlusion shadows can be. \n" +
"0 = totally black at the corners \n" +
"1 = no shadow" +
"")
.build();
public static ConfigEntry<Integer> blurRadius = new ConfigEntry.Builder<Integer>()
.set(2)
.comment("" +
"The radius, measured in pixels, that blurring is calculated for the SSAO. \n" +
"Higher numbers will reduce banding at the cost of GPU performance." +
"")
.build();
public static ConfigEntry<Integer> fadeDistanceInBlocks = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0, 1_600, 30_000_000)
.comment("" +
"The distance in blocks from the camera where the SSAO will fade out to. \n"+
"This is done to prevent banding and noise at extreme distances. \n"+
"")
.build();
} }
public static class GenericRendering public static class GenericRendering
@@ -437,8 +348,8 @@ public class Config
public static ConfigEntry<Boolean> enableGenericRendering = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableGenericRendering = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("" .comment(""
+ "If true non terrain objects will be rendered in DH's terrain. \n" + "If true non terrain objects will be rendered by DH. \n"
+ "This includes beacon beams and clouds. \n" + "i.e. beacon beams and clouds. \n"
+ "") + "")
.build(); .build();
@@ -452,9 +363,9 @@ public class Config
public static ConfigEntry<Integer> beaconRenderHeight = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> beaconRenderHeight = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(1, 6000, 6_000_000) .setMinDefaultMax(1, 6000, 6_000_000)
.comment("" .comment(""
+ "Sets the maximum height at which beacons will render." + "Sets the maximum height beacons will render up to. \n"
+ "This will only affect new beacons coming into LOD render distance." + "\n"
+ "Beacons currently visible in LOD chunks will not be affected." + "Requires a world re-load to take affect. \n"
+ "") + "")
.build(); .build();
@@ -474,19 +385,32 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Boolean> enableInstancedRendering = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableMultiLayerClouds = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("" .comment(""
+ "Can be disabled to use much slower but more compatible direct rendering. \n" + "False = DH will render a single layer of clouds, like vanilla Minecraft. \n"
+ "Disabling this can be used to fix some crashes on Mac. \n" + "True = DH will render 3 layers of clouds at different heights. \n"
+ "") + "")
.build(); .build();
public static ConfigEntry<String> dimensionEnabledCloudRenderingCsv = new ConfigEntry.Builder<String>()
.set("minecraft:overworld")
.setAppearance(EConfigEntryAppearance.ALL)
.comment(""
+ "A comma separated separated list of dimension resource locations where DH clouds will render.\n"
+ "\n"
+ "Example: \"minecraft:overworld,minecraft:the_end\"\n"
+ "\n"
+ "Changes require a world re-load.\n"
+ "")
.build();
} }
public static class Fog public static class Fog
{ {
private static final Double FOG_RANGE_MIN = 0.0; private static final Float FOG_RANGE_MIN = 0.0f;
private static final Double FOG_RANGE_MAX = Math.sqrt(2.0); private static final Float FOG_RANGE_MAX = (float) Math.sqrt(2.0);
@@ -518,8 +442,8 @@ public class Config
public static ConfigEntry<Double> farFogStart = new ConfigEntry.Builder<Double>() public static ConfigEntry<Float> farFogStart = new ConfigEntry.Builder<Float>()
.setMinDefaultMax(FOG_RANGE_MIN, 0.4, FOG_RANGE_MAX) .setMinDefaultMax(FOG_RANGE_MIN, 0.4f, FOG_RANGE_MAX)
.comment("" .comment(""
+ "At what distance should the far fog start? \n" + "At what distance should the far fog start? \n"
+ "\n" + "\n"
@@ -528,8 +452,8 @@ public class Config
+ "1.414: Fog starts at the corner of the vanilla render distance.") + "1.414: Fog starts at the corner of the vanilla render distance.")
.build(); .build();
public static ConfigEntry<Double> farFogEnd = new ConfigEntry.Builder<Double>() public static ConfigEntry<Float> farFogEnd = new ConfigEntry.Builder<Float>()
.setMinDefaultMax(FOG_RANGE_MIN, 1.0, FOG_RANGE_MAX) .setMinDefaultMax(FOG_RANGE_MIN, 1.0f, FOG_RANGE_MAX)
.comment("" .comment(""
+ "Where should the far fog end? \n" + "Where should the far fog end? \n"
+ "\n" + "\n"
@@ -538,8 +462,8 @@ public class Config
+ "1.414: Fog ends at the corner of the vanilla render distance.") + "1.414: Fog ends at the corner of the vanilla render distance.")
.build(); .build();
public static ConfigEntry<Double> farFogMin = new ConfigEntry.Builder<Double>() public static ConfigEntry<Float> farFogMin = new ConfigEntry.Builder<Float>()
.setMinDefaultMax(-5.0, 0.0, FOG_RANGE_MAX) .setMinDefaultMax(-5.0f, 0.0f, FOG_RANGE_MAX)
.comment("" .comment(""
+ "What is the minimum fog thickness? \n" + "What is the minimum fog thickness? \n"
+ "\n" + "\n"
@@ -547,8 +471,8 @@ public class Config
+ "1.0: Fully opaque fog.") + "1.0: Fully opaque fog.")
.build(); .build();
public static ConfigEntry<Double> farFogMax = new ConfigEntry.Builder<Double>() public static ConfigEntry<Float> farFogMax = new ConfigEntry.Builder<Float>()
.setMinDefaultMax(FOG_RANGE_MIN, 1.0, 5.0) .setMinDefaultMax(FOG_RANGE_MIN, 1.0f, 5.0f)
.comment("" .comment(""
+ "What is the maximum fog thickness? \n" + "What is the maximum fog thickness? \n"
+ "\n" + "\n"
@@ -566,8 +490,8 @@ public class Config
+ EDhApiFogFalloff.EXPONENTIAL_SQUARED + ": 1/(e^((distance*density)^2)") + EDhApiFogFalloff.EXPONENTIAL_SQUARED + ": 1/(e^((distance*density)^2)")
.build(); .build();
public static ConfigEntry<Double> farFogDensity = new ConfigEntry.Builder<Double>() public static ConfigEntry<Float> farFogDensity = new ConfigEntry.Builder<Float>()
.setMinDefaultMax(0.01, 2.5, 50.0) .setMinDefaultMax(0.01f, 2.5f, 50.0f)
.comment("" .comment(""
+ "Used in conjunction with the Fog Falloff.") + "Used in conjunction with the Fog Falloff.")
.build(); .build();
@@ -613,13 +537,13 @@ public class Config
+ EDhApiHeightFogDirection.ABOVE_AND_BELOW_SET_HEIGHT + ": Height fog starts from a set height and goes towards both the sky and void") + EDhApiHeightFogDirection.ABOVE_AND_BELOW_SET_HEIGHT + ": Height fog starts from a set height and goes towards both the sky and void")
.build(); .build();
public static ConfigEntry<Double> heightFogBaseHeight = new ConfigEntry.Builder<Double>() public static ConfigEntry<Float> heightFogBaseHeight = new ConfigEntry.Builder<Float>()
.setMinDefaultMax(-4096.0, 80.0, 4096.0) .setMinDefaultMax(-3_000_000.0f, 80.0f, 3_000_000.0f)
.comment("If the height fog is calculated around a set height, what is that height position?") .comment("If the height fog is calculated around a set height, what is that height position?")
.build(); .build();
public static ConfigEntry<Double> heightFogStart = new ConfigEntry.Builder<Double>() public static ConfigEntry<Float> heightFogStart = new ConfigEntry.Builder<Float>()
.setMinDefaultMax(FOG_RANGE_MIN, 0.0, FOG_RANGE_MAX) .setMinDefaultMax(FOG_RANGE_MIN, 0.0f, FOG_RANGE_MAX)
.comment("" .comment(""
+ "Should the start of the height fog be offset? \n" + "Should the start of the height fog be offset? \n"
+ "\n" + "\n"
@@ -627,8 +551,8 @@ public class Config
+ "1.0: Fog start with offset of the entire world's height. (Includes depth)") + "1.0: Fog start with offset of the entire world's height. (Includes depth)")
.build(); .build();
public static ConfigEntry<Double> heightFogEnd = new ConfigEntry.Builder<Double>() public static ConfigEntry<Float> heightFogEnd = new ConfigEntry.Builder<Float>()
.setMinDefaultMax(FOG_RANGE_MIN, 0.6, FOG_RANGE_MAX) .setMinDefaultMax(FOG_RANGE_MIN, 0.6f, FOG_RANGE_MAX)
.comment("" .comment(""
+ "Should the end of the height fog be offset? \n" + "Should the end of the height fog be offset? \n"
+ "\n" + "\n"
@@ -636,8 +560,8 @@ public class Config
+ "1.0: Fog end with offset of the entire world's height. (Include depth)") + "1.0: Fog end with offset of the entire world's height. (Include depth)")
.build(); .build();
public static ConfigEntry<Double> heightFogMin = new ConfigEntry.Builder<Double>() public static ConfigEntry<Float> heightFogMin = new ConfigEntry.Builder<Float>()
.setMinDefaultMax(0.0, 0.0, FOG_RANGE_MAX) .setMinDefaultMax(0.0f, 0.0f, FOG_RANGE_MAX)
.comment("" .comment(""
+ "What is the minimum fog thickness? \n" + "What is the minimum fog thickness? \n"
+ "\n" + "\n"
@@ -645,8 +569,8 @@ public class Config
+ "1.0: Fully opaque fog.") + "1.0: Fully opaque fog.")
.build(); .build();
public static ConfigEntry<Double> heightFogMax = new ConfigEntry.Builder<Double>() public static ConfigEntry<Float> heightFogMax = new ConfigEntry.Builder<Float>()
.setMinDefaultMax(FOG_RANGE_MIN, 1.0, 5.0) .setMinDefaultMax(FOG_RANGE_MIN, 1.0f, 5.0f)
.comment("" .comment(""
+ "What is the maximum fog thickness? \n" + "What is the maximum fog thickness? \n"
+ "\n" + "\n"
@@ -664,8 +588,8 @@ public class Config
+ EDhApiFogFalloff.EXPONENTIAL_SQUARED + ": 1/(e^((height*density)^2)") + EDhApiFogFalloff.EXPONENTIAL_SQUARED + ": 1/(e^((height*density)^2)")
.build(); .build();
public static ConfigEntry<Double> heightFogDensity = new ConfigEntry.Builder<Double>() public static ConfigEntry<Float> heightFogDensity = new ConfigEntry.Builder<Float>()
.setMinDefaultMax(0.01, 20.0, 50.0) .setMinDefaultMax(0.01f, 20.0f, 50.0f)
.comment("What is the height fog's density?") .comment("What is the height fog's density?")
.build(); .build();
@@ -693,13 +617,13 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Double> noiseIntensity = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesn't support floats) public static ConfigEntry<Float> noiseIntensity = new ConfigEntry.Builder<Float>()
.setMinDefaultMax(0d, 5d, 100d) // TODO: Once this becomes a float make it 0-1 instead of 0-100 (I did this cus doubles only allow 2 decimal places) .setMinDefaultMax(0f, 0.05f, 1f)
.comment("" .comment(""
+ "How intense should the noise should be?") + "How intense should the noise should be?")
.build(); .build();
public static ConfigEntry<Integer> noiseDropoff = new ConfigEntry.Builder<Integer>() // TODO: Make this a float (the ClassicConfigGUI doesn't support floats) public static ConfigEntry<Integer> noiseDropoff = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0, 1024, null) .setMinDefaultMax(0, 1024, null)
.comment("" .comment(""
+ "Defines how far should the noise texture render before it fades away. (in blocks) \n" + "Defines how far should the noise texture render before it fades away. (in blocks) \n"
@@ -713,13 +637,13 @@ public class Config
{ {
public static ConfigUIComment cullingHeader = new ConfigUIComment.Builder().setParentConfigClass(Culling.class).build(); public static ConfigUIComment cullingHeader = new ConfigUIComment.Builder().setParentConfigClass(Culling.class).build();
public static ConfigEntry<Double> overdrawPrevention = new ConfigEntry.Builder<Double>() public static ConfigEntry<Float> overdrawPrevention = new ConfigEntry.Builder<Float>()
.setMinDefaultMax(0.0, 0.0, 1.0) // TODO change -1 to auto .setMinDefaultMax(-1.0f, -1.0f, 1.0f)
.comment("" .comment(""
+ "Determines how far from the camera Distant Horizons will start rendering. \n" + "Determines how far from the camera Distant Horizons will start rendering. \n"
+ "Measured as a percentage of the vanilla render distance.\n" + "Measured as a percentage of the vanilla render distance.\n"
+ "\n" + "\n"
+ "0 = auto, overdraw will change based on the vanilla render distance.\n" + "-1 = auto, overdraw will change based on the vanilla render distance.\n"
+ "\n" + "\n"
+ "Higher values will prevent LODs from rendering behind vanilla blocks at a higher distance,\n" + "Higher values will prevent LODs from rendering behind vanilla blocks at a higher distance,\n"
+ "but may cause holes in the world. \n" + "but may cause holes in the world. \n"
@@ -729,6 +653,19 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Boolean> reduceOverdrawWithFastMovement = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If set to true the overdraw prevention radius will get closer\n"
+ "to the camera when flying/moving quickly.\n"
+ "\n"
+ "This helps reduce issues where Minecraft can't load or\n"
+ "generate chunks fast enough to keep up with DH.\n"
+ "")
.build();
public static ConfigUISpacer speedSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<Boolean> enableCaveCulling = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableCaveCulling = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("" .comment(""
@@ -750,6 +687,8 @@ public class Config
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigUISpacer caveCullingSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<Boolean> disableBeaconDistanceCulling = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> disableBeaconDistanceCulling = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("" .comment(""
@@ -783,59 +722,79 @@ public class Config
+ "Disable this if shadows render incorrectly.") + "Disable this if shadows render incorrectly.")
.build(); .build();
public static ConfigEntry<String> ignoredRenderBlockCsv = new ConfigEntry.Builder<String>() // TODO accept wildcards public static ConfigUISpacer ignoreCsvStartSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<String> ignoredRenderBlockCsv = new ConfigEntry.Builder<String>()
.set("minecraft:barrier,minecraft:structure_void,minecraft:light,minecraft:tripwire,minecraft:brown_mushroom") .set("minecraft:barrier,minecraft:structure_void,minecraft:light,minecraft:tripwire,minecraft:brown_mushroom")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // only shown in file since the UI has a character limit .setAppearance(EConfigEntryAppearance.ALL)
.addListener(RenderBlockCacheCsvHandler.INSTANCE)
.comment("" .comment(""
+ "A comma separated list of block resource locations that won't be rendered by DH. \n" + "A comma separated list of block resource locations that won't be rendered by DH. \n"
+ "Air is always included in this list. \n" + "Air is always included in this list. \n"
+ "Requires a restart to change. \n"
+ "\n" + "\n"
+ "Note:\n" + "Note:\n"
+ "If you see gaps, or holes you may have to change\n" + "If you see gaps, or holes you may have to change\n"
+ "worldCompression to ["+EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS+"] and re-generate the LODs.\n" + "worldCompression to [" + EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS + "] and re-generate the LODs.\n"
+ "Black spots may happen occur to block lighting being zero for covered blocks.\n"
+ "") + "")
.build(); .build();
public static ConfigEntry<String> ignoredRenderCaveBlockCsv = new ConfigEntry.Builder<String>() // TODO accept wildcards public static ConfigEntry<String> ignoredRenderCaveBlockCsv = new ConfigEntry.Builder<String>()
.set("minecraft:glow_lichen,minecraft:rail,minecraft:water,minecraft:lava,minecraft:bubble_column," + .set("")
"minecraft:cave_vines_plant,minecraft:vine,minecraft:cave_vines,minecraft:short_grass,minecraft:tall_grass," + .setAppearance(EConfigEntryAppearance.ALL)
"minecraft:small_dripleaf,minecraft:big_dripleaf,minecraft:big_dripleaf_stem,minecraft:sculk_vein") .addListener(RenderBlockCacheCsvHandler.INSTANCE)
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // only shown in file since the UI has a character limit
.comment("" .comment(""
+ "A comma separated list of block resource locations that shouldn't be rendered \n" + "A comma separated list of block resource locations that shouldn't be rendered \n"
+ "if they are in a 0 sky light underground area. \n" + "if they are in a 0 sky light underground area. \n"
+ "Air is always included in this list. \n" + "Air is always included in this list. \n"
+ "Requires a restart to change. \n" + "\n"
+ "Defaults to an empty list since most cave blocks will be automatically ignored due to being: \n"
+ "transparent, non-solid, or liquids, but new blocks can be added here if needed.\n"
+ "") + "")
.build(); .build();
public static ConfigEntry<String> waterSubSurfaceBlockReplacementCsv = new ConfigEntry.Builder<String>()
.set("minecraft:kelp,minecraft:tall_seagrass,minecraft:seagrass")
.setAppearance(EConfigEntryAppearance.ALL)
.addListener(RenderBlockCacheCsvHandler.INSTANCE)
.comment(""
+ "A comma separated list of block resource locations that will be replaced by water \n"
+ "if they're visible on the water's surface. \n"
+ "")
.build();
static public static ConfigEntry<String> waterSurfaceBlockReplacementCsv = new ConfigEntry.Builder<String>()
{ .set("minecraft:lily_pad")
ignoredRenderBlockCsv.addListener(new ConfigChangeListener<String>(ignoredRenderBlockCsv, .setAppearance(EConfigEntryAppearance.ALL)
(blockCsv) -> .addListener(RenderBlockCacheCsvHandler.INSTANCE)
{ .comment(""
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class); + "A comma separated list of block resource locations that will be removed \n"
if (wrapperFactory != null) + "when on top of water. \n"
{ + "")
wrapperFactory.resetRendererIgnoredBlocksSet(); .build();
DhApi.Delayed.renderProxy.clearRenderDataCache();
} public static ConfigUISpacer blockSpacer = new ConfigUISpacer.Builder().build();
}));
public static ConfigEntry<EDhApiBlocksToAvoid> blocksToIgnore = new ConfigEntry.Builder<EDhApiBlocksToAvoid>()
.set(EDhApiBlocksToAvoid.NON_COLLIDING)
.comment(""
+ "Defines what blocks should be rendered as LODs. \n"
+ "\n"
+ EDhApiBlocksToAvoid.NONE + ": Include all blocks in the LODs \n"
+ EDhApiBlocksToAvoid.NON_COLLIDING + ": Only render solid blocks in the LODs (tall grass, torches, etc. will be ignored) \n"
+ "")
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build();
public static ConfigEntry<Boolean> tintWithAvoidedBlocks = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "Should the blocks underneath avoided blocks gain the color of the avoided block? \n"
+ "\n"
+ "True: a red flower will tint the grass below it red. \n"
+ "False: skipped blocks will not change color of surface below them. "
+ "")
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build();
ignoredRenderCaveBlockCsv.addListener(new ConfigChangeListener<String>(ignoredRenderCaveBlockCsv,
(blockCsv) ->
{
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
if (wrapperFactory != null)
{
wrapperFactory.resetRendererIgnoredCaveBlocks();
DhApi.Delayed.renderProxy.clearRenderDataCache();
}
}));
}
} }
public static class Experimental public static class Experimental
@@ -853,7 +812,7 @@ public class Config
+ "100 = 1 to 100 (63,710 blocks) \n" + "100 = 1 to 100 (63,710 blocks) \n"
+ "10000 = 1 to 10000 (637.1 blocks) \n" + "10000 = 1 to 10000 (637.1 blocks) \n"
+ "\n" + "\n"
+ "Note: Due to current limitations, the min value is ["+WorldCurvatureConfigEventHandler.MIN_VALID_CURVE_VALUE+"] \n" + "Note: Due to current limitations, the min value is [" + WorldCurvatureConfigEventHandler.MIN_VALID_CURVE_VALUE + "] \n"
+ "and the max value is 5000. Any values outside this range \n" + "and the max value is 5000. Any values outside this range \n"
+ "will be set to 0 (disabled).") + "will be set to 0 (disabled).")
.addListener(WorldCurvatureConfigEventHandler.INSTANCE) .addListener(WorldCurvatureConfigEventHandler.INSTANCE)
@@ -873,6 +832,19 @@ public class Config
.addListener(IgnoredDimensionCsvHandler.INSTANCE) .addListener(IgnoredDimensionCsvHandler.INSTANCE)
.build(); .build();
public static ConfigEntry<EDhApiRenderingEngine> renderingEngine = new ConfigEntry.Builder<EDhApiRenderingEngine>()
.set(EDhApiRenderingEngine.AUTO)
.comment(""
+ "Requires a restart to change. \n"
+ " \n"
+ "Options: \n"
+ EDhApiRenderingEngine.AUTO + " - changes based on the most likely API for that MC version \n"
+ EDhApiRenderingEngine.OPEN_GL + " - The Default for MC 1.21.11 and older (supports Iris shaders) \n"
+ EDhApiRenderingEngine.BLAZE_3D + " - The Default for MC 26.1.2 and newer (supports Vulkan) \n"
+ "")
.build();
} }
} }
@@ -900,10 +872,11 @@ public class Config
.set(EDhApiUpdateBranch.AUTO) .set(EDhApiUpdateBranch.AUTO)
.comment("" .comment(""
+ "If DH should use the nightly (provided by Gitlab), or stable (provided by Modrinth) build. \n" + "If DH should use the nightly (provided by Gitlab), or stable (provided by Modrinth) build. \n"
+ "If ["+EDhApiUpdateBranch.AUTO+"] is selected DH will update to new stable releases if the current jar is a stable jar \n" + "If [" + EDhApiUpdateBranch.AUTO + "] is selected DH will update to new stable releases if the current jar is a stable jar \n"
+ "and will update to new nightly builds if the current jar is a nightly jar (IE the version number ends in '-dev')." + "and will update to new nightly builds if the current jar is a nightly jar (IE the version number ends in '-dev')."
+ "") + "")
.build(); .build();
} }
public static class Multiplayer public static class Multiplayer
@@ -934,11 +907,11 @@ public class Config
+ "What renderer is active? \n" + "What renderer is active? \n"
+ "\n" + "\n"
+ EDhApiRendererMode.DEFAULT + ": Default lod renderer \n" + EDhApiRendererMode.DEFAULT + ": Default lod renderer \n"
+ EDhApiRendererMode.DEBUG + ": Debug testing renderer \n" + EDhApiRendererMode.DEBUG_TRIANGLE + ": Debug testing renderer \n"
+ EDhApiRendererMode.DISABLED + ": Disable rendering") + EDhApiRendererMode.DISABLED + ": Disable rendering")
.build(); .build();
public static ConfigEntry<EDhApiDebugRendering> debugRendering = new ConfigEntry.Builder<EDhApiDebugRendering>() public static ConfigEntry<EDhApiDebugRendering> debugRenderingColors = new ConfigEntry.Builder<EDhApiDebugRendering>()
.set(EDhApiDebugRendering.OFF) .set(EDhApiDebugRendering.OFF)
.comment("" .comment(""
+ "Should specialized colors/rendering modes be used? \n" + "Should specialized colors/rendering modes be used? \n"
@@ -951,6 +924,15 @@ public class Config
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE) .addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build(); .build();
public static ConfigUISpacer debugRenderingSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<Boolean> enableWhiteWorld = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "Stops vertex colors from being passed. \n"
+ "Useful for debugging shaders")
.build();
public static ConfigEntry<Boolean> lodOnlyMode = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> lodOnlyMode = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
.comment("" .comment(""
@@ -968,21 +950,6 @@ public class Config
+ "") + "")
.build(); .build();
// TODO add LOD-only mode to this
public static ConfigEntry<Boolean> enableDebugKeybindings = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "If true the F8 key can be used to cycle through the different debug modes. \n"
+ "and the F6 key can be used to enable and disable LOD rendering.")
.build();
public static ConfigEntry<Boolean> enableWhiteWorld = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "Stops vertex colors from being passed. \n"
+ "Useful for debugging shaders")
.build();
public static ConfigEntry<Boolean> showOverlappingQuadErrors = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> showOverlappingQuadErrors = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
.comment("" .comment(""
@@ -991,6 +958,18 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigUISpacer miscSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<Boolean> enableDebugKeybindings = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "If true several keys can be used to toggle debug states. \n"
+ "F6 - enable/disable LOD rendering \n"
+ "F7 - enable/disable LOD only rendering \n"
+ "F8 - cycle through the different debug rendering modes \n"
+ "")
.build();
public static ConfigEntry<Boolean> logBufferGarbageCollection = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> logBufferGarbageCollection = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
.comment("" .comment(""
@@ -999,6 +978,8 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigUISpacer unsafeSpacer = new ConfigUISpacer.Builder().build();
// Note: This will reset on game restart, and should have a warning on the tooltip // Note: This will reset on game restart, and should have a warning on the tooltip
public static ConfigEntry<Boolean> allowUnsafeValues = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> allowUnsafeValues = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
@@ -1006,9 +987,12 @@ public class Config
.addListener(UnsafeValuesConfigListener.INSTANCE) .addListener(UnsafeValuesConfigListener.INSTANCE)
.build(); .build();
public static ConfigUISpacer categorySpacer = new ConfigUISpacer.Builder().build();
public static ConfigCategory debugWireframe = new ConfigCategory.Builder().set(DebugWireframe.class).build(); public static ConfigCategory debugWireframe = new ConfigCategory.Builder().set(DebugWireframe.class).build();
public static ConfigCategory openGl = new ConfigCategory.Builder().set(OpenGl.class).build(); public static ConfigCategory openGl = new ConfigCategory.Builder().set(OpenGl.class).build();
public static ConfigCategory columnBuilderDebugging = new ConfigCategory.Builder().set(ColumnBuilderDebugging.class).build(); public static ConfigCategory columnBuilderDebugging = new ConfigCategory.Builder().set(ColumnBuilderDebugging.class).build();
public static ConfigCategory positionFinderDebugging = new ConfigCategory.Builder().set(PositionFinder.class).build();
public static ConfigCategory f3Screen = new ConfigCategory.Builder().set(F3Screen.class).build(); public static ConfigCategory f3Screen = new ConfigCategory.Builder().set(F3Screen.class).build();
public static ConfigCategory exampleConfigScreen = new ConfigCategory.Builder().set(ExampleConfigScreen.class).build(); public static ConfigCategory exampleConfigScreen = new ConfigCategory.Builder().set(ExampleConfigScreen.class).build();
@@ -1029,6 +1013,8 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigUISpacer wireframeOptionSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<Boolean> showWorldGenQueue = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> showWorldGenQueue = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
.comment("Render queued world gen tasks?") .comment("Render queued world gen tasks?")
@@ -1043,15 +1029,6 @@ public class Config
.set(false) .set(false)
.comment("Render LOD section status?") .comment("Render LOD section status?")
.build(); .build();
public static ConfigEntry<Boolean> showRenderSectionToggling = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("" +
"A white box will be drawn when an LOD starts rendering \n" +
"and a purple box when an LOD stops rendering. \n" +
"\n" +
"This can be used to debug Quad Tree holes.\n" +
"")
.build();
public static ConfigEntry<Boolean> showQuadTreeRenderStatus = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> showQuadTreeRenderStatus = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
@@ -1104,13 +1081,6 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<EDhApiGpuUploadMethod> glUploadMode = new ConfigEntry.Builder<EDhApiGpuUploadMethod>()
.set(EDhApiGpuUploadMethod.AUTO)
.comment(""
+ "\n"
+ "")
.build();
} }
public static class ColumnBuilderDebugging public static class ColumnBuilderDebugging
@@ -1138,6 +1108,8 @@ public class Config
.addListener(ReloadLodsConfigEventHandler.INSTANT_INSTANCE) .addListener(ReloadLodsConfigEventHandler.INSTANT_INSTANCE)
.build(); .build();
public static ConfigUISpacer subLodSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<Integer> columnBuilderDebugXRow = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> columnBuilderDebugXRow = new ConfigEntry.Builder<Integer>()
.set(-1) .set(-1)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
@@ -1156,6 +1128,38 @@ public class Config
} }
public static class PositionFinder
{
//public static ConfigUIComment positionFinderHeader = new ConfigUIComment.Builder().setParentConfigClass(ColumnBuilderDebugging.class).build();
public static ConfigEntry<Boolean> positionFinderEnable = new ConfigEntry.Builder<Boolean>()
.set(false)
.build();
public static ConfigEntry<Integer> positionFinderDetailLevel = new ConfigEntry.Builder<Integer>()
.set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
.build();
public static ConfigEntry<Integer> positionFinderXPos = new ConfigEntry.Builder<Integer>()
.set(0)
.build();
public static ConfigEntry<Integer> positionFinderZPos = new ConfigEntry.Builder<Integer>()
.set(0)
.build();
public static ConfigUISpacer positionFinderBlockPosSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<Integer> positionFinderMinBlockY = new ConfigEntry.Builder<Integer>()
.set(-64)
.build();
public static ConfigEntry<Integer> positionFinderMaxBlockY = new ConfigEntry.Builder<Integer>()
.set(125)
.build();
public static ConfigEntry<Float> positionFinderMarginPercent = new ConfigEntry.Builder<Float>()
.set(0.0f)
.build();
}
public static class F3Screen public static class F3Screen
{ {
public static ConfigUIComment f3ScreenHeader = new ConfigUIComment.Builder().setParentConfigClass(F3Screen.class).build(); public static ConfigUIComment f3ScreenHeader = new ConfigUIComment.Builder().setParentConfigClass(F3Screen.class).build();
@@ -1177,6 +1181,11 @@ public class Config
.comment("Shows info about each thread pool.") .comment("Shows info about each thread pool.")
.build(); .build();
public static ConfigEntry<Boolean> showRenderThreadTasks = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("Shows info about the render thread tasks.")
.build();
public static ConfigEntry<Boolean> showCombinedObjectPools = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> showCombinedObjectPools = new ConfigEntry.Builder<Boolean>()
.set(false) .set(false)
.comment("Shows the combined memory use and array counts for all DH pooled objects.") .comment("Shows the combined memory use and array counts for all DH pooled objects.")
@@ -1188,7 +1197,7 @@ public class Config
public static ConfigEntry<Boolean> showQueuedChunkUpdateCount = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> showQueuedChunkUpdateCount = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("Shows how many chunks are queud for processing and the max count that can be queued.") .comment("Shows how many chunks are queued for processing and the max count that can be queued.")
.build(); .build();
public static ConfigEntry<Boolean> showLevelStatus = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> showLevelStatus = new ConfigEntry.Builder<Boolean>()
@@ -1196,6 +1205,11 @@ public class Config
.comment("Shows what levels are loaded and world gen/rendering info about those levels.") .comment("Shows what levels are loaded and world gen/rendering info about those levels.")
.build(); .build();
public static ConfigEntry<Boolean> onlyShowRenderingLevels = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment("Only show levels that DH is actively rendering.")
.build();
} }
/** This class is used to debug the different features of the config GUI */ /** This class is used to debug the different features of the config GUI */
@@ -1261,9 +1275,11 @@ public class Config
}); });
public static void onButtonPressed() public static void onButtonPressed()
{ {
IMinecraftClientWrapper mcClient = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
LOGGER.info("Attempting to show tinyfd message box..."); LOGGER.info("Attempting to show tinyfd message box...");
boolean buttonPress = NativeDialogUtil.showDialog("Button pressed!", "UITester dialog", "ok", "info"); mcClient.showDialog("Button pressed!", "UITester dialog", "ok", "info");
LOGGER.info("dialog returned with ["+(buttonPress ? "TRUE" : "FALSE")+"]"); LOGGER.info("dialog closed");
} }
public static ConfigCategory categoryTest = new ConfigCategory.Builder().set(CategoryTest.class).build(); public static ConfigCategory categoryTest = new ConfigCategory.Builder().set(CategoryTest.class).build();
@@ -1279,10 +1295,13 @@ public class Config
public static ConfigUiLinkedEntry linkableTest = new ConfigUiLinkedEntry(ExampleConfigScreen.linkableTest); public static ConfigUiLinkedEntry linkableTest = new ConfigUiLinkedEntry(ExampleConfigScreen.linkableTest);
} }
} }
} }
} }
} }
public static class Common public static class Common
@@ -1343,12 +1362,14 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigUISpacer genProgressSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<EDhApiDistantGeneratorProgressDisplayLocation> showGenerationProgress = new ConfigEntry.Builder<EDhApiDistantGeneratorProgressDisplayLocation>() public static ConfigEntry<EDhApiDistantGeneratorProgressDisplayLocation> showGenerationProgress = new ConfigEntry.Builder<EDhApiDistantGeneratorProgressDisplayLocation>()
.set(EDhApiDistantGeneratorProgressDisplayLocation.DISABLED) .set(EDhApiDistantGeneratorProgressDisplayLocation.DISABLED)
.comment("" .comment(""
+ "How should distant generator progress be displayed? \n" + "How should distant generator progress be displayed? \n"
+ "\n" + "\n"
+ EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY + ": may be the same as "+EDhApiDistantGeneratorProgressDisplayLocation.CHAT+" for some Minecraft versions \n" + EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY + ": may be the same as " + EDhApiDistantGeneratorProgressDisplayLocation.CHAT + " for some Minecraft versions \n"
+ EDhApiDistantGeneratorProgressDisplayLocation.CHAT + " \n" + EDhApiDistantGeneratorProgressDisplayLocation.CHAT + " \n"
+ EDhApiDistantGeneratorProgressDisplayLocation.LOG + " \n" + EDhApiDistantGeneratorProgressDisplayLocation.LOG + " \n"
+ EDhApiDistantGeneratorProgressDisplayLocation.DISABLED + " \n" + EDhApiDistantGeneratorProgressDisplayLocation.DISABLED + " \n"
@@ -1438,7 +1459,8 @@ public class Config
public static ConfigEntry<EDhApiDataCompressionMode> dataCompression = new ConfigEntry.Builder<EDhApiDataCompressionMode>() public static ConfigEntry<EDhApiDataCompressionMode> dataCompression = new ConfigEntry.Builder<EDhApiDataCompressionMode>()
.set(EDhApiDataCompressionMode.Z_STD_BLOCK) .set(EDhApiDataCompressionMode.Z_STD_BLOCK)
// only visible via the API since there is no reason to use any compressor except ZStandard as of 2025-11-24 // only visible via the API since there is no reason to use any compressor except ZStandard as of 2025-11-24
.setAppearance(EConfigEntryAppearance.ONLY_IN_API) // Note: this may need to be re-visited since some people (android) have issues with ZStd
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.build(); .build();
public static ConfigEntry<EDhApiWorldCompressionMode> worldCompression = new ConfigEntry.Builder<EDhApiWorldCompressionMode>() public static ConfigEntry<EDhApiWorldCompressionMode> worldCompression = new ConfigEntry.Builder<EDhApiWorldCompressionMode>()
@@ -1485,6 +1507,7 @@ public class Config
+ "\n" + "\n"
+ "") + "")
.build(); .build();
} }
} }
@@ -1610,6 +1633,8 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigUISpacer warningSpacer = new ConfigUISpacer.Builder().build();
public static ConfigCategory warning = new ConfigCategory.Builder().set(Warning.class).build(); public static ConfigCategory warning = new ConfigCategory.Builder().set(Warning.class).build();
@@ -1679,11 +1704,21 @@ public class Config
.comment("" .comment(""
+ "If enabled, a message will be logged if the garbage \n" + "If enabled, a message will be logged if the garbage \n"
+ "collector Java is currently using is known \n" + "collector Java is currently using is known \n"
+ "to cause stutters and/or issues. \n" + "to cause frame stuttering and/or other issues. \n"
+ "")
.build();
public static ConfigEntry<Boolean> showGarbageCollectorWarning = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If enabled, a chat message will be displayed if the garbage \n"
+ "collector Java is currently using is known \n"
+ "to cause frame stuttering and/or other issues. \n"
+ "") + "")
.build(); .build();
} }
} }
} }
@@ -1850,6 +1885,7 @@ public class Config
+ "For better performance when switching LOD detail levels, enabling [upsampleLowerDetailLodsToFillHoles] is recommended.\n" + "For better performance when switching LOD detail levels, enabling [upsampleLowerDetailLodsToFillHoles] is recommended.\n"
+ "") + "")
.build(); .build();
} }
} }
@@ -62,7 +62,7 @@ public class ConfigHandler
* <br> {@link String} * <br> {@link String}
* <br> * <br>
* <br> // Below, "T" should be a value from above * <br> // Below, "T" should be a value from above
* <br> // Note: This is not checked, so we trust that you are doing the right thing (TODO: Check it) * <br> // Note: This is not checked, so we trust that you are doing the right thing
* <br> List<T> * <br> List<T>
* <br> ArrayList<T> * <br> ArrayList<T>
* <br> Map<String, T> * <br> Map<String, T>
@@ -261,7 +261,6 @@ public class ConfigHandler
if (ConfigUIComment.class.isAssignableFrom(entry.getClass()) if (ConfigUIComment.class.isAssignableFrom(entry.getClass())
&& ((ConfigUIComment)entry).parentConfigPath != null) && ((ConfigUIComment)entry).parentConfigPath != null)
{ {
// TODO this could potentially add the same item multiple times
entryPrefix = "distanthorizons.config." + ((ConfigUIComment)entry).parentConfigPath; entryPrefix = "distanthorizons.config." + ((ConfigUIComment)entry).parentConfigPath;
} }
@@ -46,6 +46,12 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
private final IConverter<coreType, apiType> configConverter; private final IConverter<coreType, apiType> configConverter;
//==============//
// constructors //
//==============//
//region
/** /**
* This constructor should only be called internally. <br> * This constructor should only be called internally. <br>
* There is no reason for API users to create this object. <br><br> * There is no reason for API users to create this object. <br><br>
@@ -69,11 +75,29 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
this.configConverter = newConverter; this.configConverter = newConverter;
} }
//endregion
public apiType getValue() { return this.configConverter.convertToApiType(this.configBase.get()); }
public apiType getTrueValue() { return this.configConverter.convertToApiType(this.configBase.getTrueValue()); }
public apiType getApiValue() { return this.configConverter.convertToApiType(this.configBase.getApiValue()); }
//===========//
// overrides //
//===========//
//region
@Override public apiType getValue() { return this.configConverter.convertToApiType(this.configBase.get()); }
@Override public apiType getTrueValue() { return this.configConverter.convertToApiType(this.configBase.getTrueValue()); }
@Override public apiType getApiValue()
{
// if no API value is set, this should return null
if (this.configBase.getApiValue() == null)
{
return null;
}
return this.configConverter.convertToApiType(this.configBase.getApiValue());
}
@Override
public boolean setValue(apiType newValue) public boolean setValue(apiType newValue)
{ {
if (this.configBase.getAllowApiOverride()) if (this.configBase.getAllowApiOverride())
@@ -87,12 +111,12 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
} }
} }
@Override
public boolean clearValue() public boolean clearValue()
{ {
if (this.configBase.getAllowApiOverride()) if (this.configBase.getAllowApiOverride())
{ {
// no converter should be used here since null objects may need to be handled differently // no converter should be used here since null objects may need to be handled differently
// TODO the API should just have a bool to keep track of whether the API value is in use instead of using NULL
this.configBase.setApiValue(null); this.configBase.setApiValue(null);
return true; return true;
} }
@@ -102,13 +126,15 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
} }
} }
@Override
public boolean getCanBeOverrodeByApi() { return this.configBase.getAllowApiOverride(); } public boolean getCanBeOverrodeByApi() { return this.configBase.getAllowApiOverride(); }
public apiType getDefaultValue() { return this.configConverter.convertToApiType(this.configBase.getDefaultValue()); } @Override public apiType getDefaultValue() { return this.configConverter.convertToApiType(this.configBase.getDefaultValue()); }
public apiType getMaxValue() { return this.configConverter.convertToApiType(this.configBase.getMax()); } @Override public apiType getMaxValue() { return this.configConverter.convertToApiType(this.configBase.getMax()); }
public apiType getMinValue() { return this.configConverter.convertToApiType(this.configBase.getMin()); } @Override public apiType getMinValue() { return this.configConverter.convertToApiType(this.configBase.getMin()); }
@Override
public void addChangeListener(Consumer<apiType> onValueChangeFunc) public void addChangeListener(Consumer<apiType> onValueChangeFunc)
{ {
this.configBase.addValueChangeListener((coreValue) -> this.configBase.addValueChangeListener((coreValue) ->
@@ -118,4 +144,6 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
}); });
} }
//endregion
} }
@@ -0,0 +1,84 @@
package com.seibel.distanthorizons.core.config.eventHandlers;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.util.TimerUtil;
import java.util.Timer;
import java.util.TimerTask;
public abstract class AbstractDelayedConfigEventHandler implements IConfigListener
{
public static final long DEFAULT_TIMEOUT_IN_MS = 2_000L;
/** how long to wait in milliseconds before applying the config changes */
private final long timeoutInMs;
private Timer timer;
//=============//
// constructor //
//=============//
//region
public AbstractDelayedConfigEventHandler(long timeoutInMs) { this.timeoutInMs = timeoutInMs; }
//endregion
//==================//
// abstract methods //
//==================//
//region
public abstract void onConfigTimeout();
//endregion
//========//
// events //
//========//
//region
@Override
public void onConfigValueSet()
{
if (this.timeoutInMs > 0)
{
this.refreshRenderDataAfterTimeout();
}
else
{
this.onConfigTimeout();
}
}
/** Calling this method multiple times will reset the timer */
private synchronized void refreshRenderDataAfterTimeout() // synchronized to prevent potential threading issues when adding/removing the timer
{
// stop the previous timer if one exists
if (this.timer != null)
{
this.timer.cancel();
}
// create a new timer task
TimerTask timerTask = new TimerTask()
{
public void run()
{
AbstractDelayedConfigEventHandler.this.onConfigTimeout();
}
};
this.timer = TimerUtil.CreateTimer("AbstractDelayedConfigTimer");
this.timer.schedule(timerTask, this.timeoutInMs);
}
//endregion
}
@@ -27,72 +27,36 @@ import com.seibel.distanthorizons.core.util.TimerUtil;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
public class ReloadLodsConfigEventHandler implements IConfigListener public class ReloadLodsConfigEventHandler extends AbstractDelayedConfigEventHandler
{ {
/** /**
* should be used for user facing UI options * should be used for user facing UI options
* this allows the user a second to click through options before they're applied * this allows the user a second to click through options before they're applied
*/ */
public static ReloadLodsConfigEventHandler DELAYED_INSTANCE = new ReloadLodsConfigEventHandler(2_000L); public static ReloadLodsConfigEventHandler DELAYED_INSTANCE = new ReloadLodsConfigEventHandler(AbstractDelayedConfigEventHandler.DEFAULT_TIMEOUT_IN_MS);
/** should be used for debug options so their change can be seen instantly */ /** should be used for debug options so their change can be seen instantly */
public static ReloadLodsConfigEventHandler INSTANT_INSTANCE = new ReloadLodsConfigEventHandler(0); public static ReloadLodsConfigEventHandler INSTANT_INSTANCE = new ReloadLodsConfigEventHandler(0);
/** how long to wait in milliseconds before applying the config changes */
private final long timeoutInMs;
private Timer cacheClearingTimer;
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
public ReloadLodsConfigEventHandler(long timeoutInMs) public ReloadLodsConfigEventHandler(long timeoutInMs) { super(timeoutInMs); }
{
this.timeoutInMs = timeoutInMs; //endregion
}
//========// //========//
// events // // events //
//========// //========//
//region
@Override @Override
public void onConfigValueSet() public void onConfigTimeout()
{
if (this.timeoutInMs > 0)
{
this.refreshRenderDataAfterTimeout();
}
else
{
clearRenderDataCache();
}
}
/** Calling this method multiple times will reset the timer */
private synchronized void refreshRenderDataAfterTimeout() // synchronized to prevent potential threading issues when adding/removing the timer
{
// stop the previous timer if one exists
if (this.cacheClearingTimer != null)
{
this.cacheClearingTimer.cancel();
}
// create a new timer task
TimerTask timerTask = new TimerTask()
{
public void run()
{
clearRenderDataCache();
}
};
this.cacheClearingTimer = TimerUtil.CreateTimer("RenderCacheClearConfigTimer");
this.cacheClearingTimer.schedule(timerTask, this.timeoutInMs);
}
private static void clearRenderDataCache()
{ {
IDhApiRenderProxy renderProxy = DhApi.Delayed.renderProxy; IDhApiRenderProxy renderProxy = DhApi.Delayed.renderProxy;
if (renderProxy != null) if (renderProxy != null)
@@ -101,5 +65,8 @@ public class ReloadLodsConfigEventHandler implements IConfigListener
} }
} }
//endregion
} }
@@ -0,0 +1,77 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.config.eventHandlers;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import java.util.Timer;
public class RenderBlockCacheCsvHandler extends AbstractDelayedConfigEventHandler
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static RenderBlockCacheCsvHandler INSTANCE = new RenderBlockCacheCsvHandler();
//=============//
// constructor //
//=============//
//region
/** private since we only ever need one handler at a time */
private RenderBlockCacheCsvHandler() { super(AbstractDelayedConfigEventHandler.DEFAULT_TIMEOUT_IN_MS); }
//endregion
//=================//
// config handling //
//=================//
//region
@Override
public void onConfigTimeout()
{
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
if (wrapperFactory != null)
{
wrapperFactory.resetCachedIgnoredBlocksSets();
DhApi.Delayed.renderProxy.clearRenderDataCache();
}
}
//endregion
}
@@ -78,7 +78,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, EDhApiTransparency.COMPLETE); this.put(EDhApiQualityPreset.HIGH, EDhApiTransparency.COMPLETE);
this.put(EDhApiQualityPreset.EXTREME, EDhApiTransparency.COMPLETE); this.put(EDhApiQualityPreset.EXTREME, EDhApiTransparency.COMPLETE);
}}); }});
private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> ssaoEnabled = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Ssao.enableSsao, private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> ssaoEnabled = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.enableSsao,
new HashMap<EDhApiQualityPreset, Boolean>() new HashMap<EDhApiQualityPreset, Boolean>()
{{ {{
this.put(EDhApiQualityPreset.MINIMUM, false); this.put(EDhApiQualityPreset.MINIMUM, false);
@@ -241,8 +241,8 @@ public class ConfigFileHandler
} }
else if (entry.getTrueValue() == null) else if (entry.getTrueValue() == null)
{ {
// TODO when can this happen? // shouldn't happen, but just in case
throw new IllegalArgumentException("BlockBiomeWrapperPair [" + entry.getNameAndCategory() + "] is null, this may be a problem with [" + ModInfo.NAME + "]. Please contact the authors."); throw new IllegalArgumentException("ConfigEntry [" + entry.getNameAndCategory() + "] is null, how did this happen?");
} }
workConfig.set(entry.getNameAndCategory(), ConfigTypeConverters.attemptToConvertToString(entry.getType(), entry.getTrueValue())); workConfig.set(entry.getNameAndCategory(), ConfigTypeConverters.attemptToConvertToString(entry.getType(), entry.getTrueValue()));
@@ -360,7 +360,8 @@ public class ConfigFileHandler
{ {
LOGGER.error("File creation failed at ["+this.configPath+"], error: ["+e.getMessage()+"].", e); LOGGER.error("File creation failed at ["+this.configPath+"], error: ["+e.getMessage()+"].", e);
// TODO is there a reason this is lazily gotten? // delayed MC getter since this object may be created before
// the singleton has been bound
IMinecraftClientWrapper mc = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); IMinecraftClientWrapper mc = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
mc.crashMinecraft("Loading file and resetting config file failed at path [" + this.configPath + "]. Please check the file is ok and you have the permissions", e); mc.crashMinecraft("Loading file and resetting config file failed at path [" + this.configPath + "]. Please check the file is ok and you have the permissions", e);
} }
@@ -1,196 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.config.gui;
import com.seibel.distanthorizons.core.jar.EPlatform;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.system.jawt.JAWT;
import org.lwjgl.system.macosx.*;
import java.awt.*;
import java.lang.reflect.*;
import java.util.regex.*;
import static org.lwjgl.glfw.GLFWNativeCocoa.*;
import static org.lwjgl.glfw.GLFWNativeWin32.*;
import static org.lwjgl.glfw.GLFWNativeX11.*;
import static org.lwjgl.system.JNI.*;
import static org.lwjgl.system.jawt.JAWTFunctions.*;
import static org.lwjgl.system.macosx.ObjCRuntime.*;
// Some of the code is from https://github.com/LWJGL/lwjgl3/blob/master/modules/samples/src/test/java/org/lwjgl/demo/system/jawt/EmbeddedFrameUtil.java
// which is licensed under https://www.lwjgl.org/license
/**
* Some utils for embedding awt and swing items into lwjgl windows
*
* @author Ran
* @author coolGi
*/
public final class EmbeddedFrameUtil
{
private static final int JAVA_VERSION;
private static final JAWT awt;
static
{
Pattern p = Pattern.compile("^(?:1[.])?([1-9][0-9]*)[.-]");
Matcher m = p.matcher(System.getProperty("java.version"));
if (!m.find())
{
throw new IllegalStateException("Failed to parse java.version");
}
JAVA_VERSION = Integer.parseInt(m.group(1));
awt = JAWT.calloc();
awt.version(JAVA_VERSION < 9 ? JAWT_VERSION_1_4 : JAWT_VERSION_9);
if (!JAWT_GetAWT(awt))
{
throw new RuntimeException("GetAWT failed");
}
}
private static String getEmbeddedFrameImpl()
{
switch (EPlatform.get())
{
case LINUX:
return "sun.awt.X11.XEmbeddedFrame";
case WINDOWS:
return "sun.awt.windows.WEmbeddedFrame";
case MACOS:
return "sun.lwawt.macosx.CViewEmbeddedFrame";
default:
throw new IllegalStateException();
}
}
private static long getEmbeddedFrameHandle(long window)
{
switch (EPlatform.get())
{
case LINUX:
return glfwGetX11Window(window);
case WINDOWS:
return glfwGetWin32Window(window);
case MACOS:
long objc_msgSend = ObjCRuntime.getLibrary().getFunctionAddress("objc_msgSend");
return invokePPP(glfwGetCocoaWindow(window), sel_getUid("contentView"), objc_msgSend);
default:
throw new IllegalStateException();
}
}
public static Frame embeddedFrameCreate(long window)
{
if (JAVA_VERSION < 9)
{
try
{
@SuppressWarnings("unchecked")
Class<? extends Frame> EmdeddedFrame = (Class<? extends Frame>) Class.forName(getEmbeddedFrameImpl());
Constructor<? extends Frame> c = EmdeddedFrame.getConstructor(long.class);
return c.newInstance(getEmbeddedFrameHandle(window));
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
else
{
return nJAWT_CreateEmbeddedFrame(getEmbeddedFrameHandle(window), awt.CreateEmbeddedFrame());
}
}
static void embeddedFrameSynthesizeWindowActivation(Frame embeddedFrame, boolean doActivate)
{
if (JAVA_VERSION < 9)
{
try
{
embeddedFrame
.getClass()
.getMethod("synthesizeWindowActivation", boolean.class)
.invoke(embeddedFrame, doActivate);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
else
{
JAWT_SynthesizeWindowActivation(embeddedFrame, doActivate, awt.SynthesizeWindowActivation());
}
}
public static void embeddedFrameSetBounds(Frame embeddedFrame, int x, int y, int width, int height)
{
if (JAVA_VERSION < 9)
{
try
{
Method setLocationPrivate = embeddedFrame
.getClass()
.getSuperclass()
.getDeclaredMethod("setBoundsPrivate", int.class, int.class, int.class, int.class);
setLocationPrivate.setAccessible(true);
setLocationPrivate.invoke(embeddedFrame, x, y, width, height);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
else
{
JAWT_SetBounds(embeddedFrame, x, y, width, height, awt.SetBounds());
}
}
public static void hideFrame(@NotNull Frame embeddedFrame)
{
embeddedFrame.setVisible(false);
embeddedFrameSynthesizeWindowActivation(embeddedFrame, false);
}
public static void showFrame(@NotNull Frame embeddedFrame)
{
embeddedFrameSynthesizeWindowActivation(embeddedFrame, true);
embeddedFrame.setVisible(true);
}
public static void placeAtCenter(Frame embeddedFrame, int windowWidth, int windowHeight, int frameWidth, int frameHeight, float scale)
{
float scaleFactor = (100.0F - scale) / 100.0F;
float newWidth = frameWidth * scaleFactor;
float newHeight = frameHeight * scaleFactor;
float newX = (windowWidth - newWidth) / 2F;
float newY = (windowHeight - newHeight) / 2F;
embeddedFrameSetBounds(embeddedFrame, Math.round(newX), Math.round(newY), Math.round(newWidth), Math.round(newHeight));
}
}
@@ -1,164 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.config.gui;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/**
*
*/
public class JavaScreenHandlerScreen extends AbstractScreen
{
public static Frame frame;
public static boolean firstRun = true;
public final Component jComponent;
static
{
// Note: this code can cause Mac
// to lock up and refuse the load (there's a bug with Java.awt texture loading)
// Needs to be called before any Swing code is called, otherwise
// Swing will get stuck thinking it's headless
System.setProperty("java.awt.headless", "false");
}
public JavaScreenHandlerScreen(@NotNull Component component)
{
this.jComponent = component;
}
@Override
public void init()
{
if (firstRun)
{
frame = EmbeddedFrameUtil.embeddedFrameCreate(this.minecraftWindow); // Don't call this multiple times
}
frame.add(this.jComponent);
frame.setBackground(new Color(0, 125, 155));
JavaScreenHandlerScreen thiss = this;
frame.addKeyListener(new KeyListener()
{
@Override
public void keyPressed(KeyEvent keyEvent)
{
System.out.println("Key pressed code=" + keyEvent.getKeyCode() + ", char=" + keyEvent.getKeyChar());
if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE)
{
thiss.close = true;
}
}
@Override
public void keyTyped(KeyEvent keyEvent) { }
@Override
public void keyReleased(KeyEvent keyEvent) { }
});
if (firstRun)
{
EmbeddedFrameUtil.embeddedFrameSetBounds(frame, 0, 0, this.width, this.height);
firstRun = false;
}
EmbeddedFrameUtil.showFrame(frame);
}
/** A testing/debug screen */
public static class ExampleScreen extends JComponent
{
public ExampleScreen()
{
this.setLayout(new GridBagLayout());
this.setBackground(new Color(255, 0, 0)); // doesn't appear to be used
GridBagConstraints helloWorldConstraints = new GridBagConstraints();
helloWorldConstraints.weightx = 0.5;
helloWorldConstraints.gridx = 0;
helloWorldConstraints.gridy = 0;
//helloWorldConstraints.fill = GridBagConstraints.BOTH;
this.add(new JLabel("Hello World!"), helloWorldConstraints);
GridBagConstraints buttonConstraints = new GridBagConstraints();
buttonConstraints.weightx = 0.5;
buttonConstraints.gridx = 0;
buttonConstraints.gridy = 1;
//buttonConstraints.fill = GridBagConstraints.BOTH;
JButton button = new JButton();
button.setBackground(Color.GREEN);
button.setFocusable(false); // otherwise we can't use escape to leave
button.setAction(new ExampleButtonEventHandler("Button text"));
this.add(button, buttonConstraints);
}
private class ExampleButtonEventHandler extends AbstractAction
{
public ExampleButtonEventHandler(String text)
{
super(text);
//this.putValue(SHORT_DESCRIPTION, text);
//this.putValue(MNEMONIC_KEY, text);
}
@Override
public void actionPerformed(ActionEvent e)
{
System.out.println("button pressed");
}
}
}
@Override
public void render(float delta)
{
// TODO: Make screen only update on this being called
}
@Override
public void onResize()
{
EmbeddedFrameUtil.embeddedFrameSetBounds(frame, 0, 0, this.width, this.height);
}
@Override
public void onClose()
{
frame.remove(this.jComponent);
EmbeddedFrameUtil.hideFrame(frame);
}
}
@@ -52,11 +52,17 @@ public abstract class AbstractConfigBase<T>
protected AbstractConfigBase(EConfigEntryAppearance appearance, T defaultValue) protected AbstractConfigBase(EConfigEntryAppearance appearance, T defaultValue)
{ {
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
if (this.defaultValue == null)
{
throw new IllegalArgumentException("defaultValue cannot be null");
}
this.value = defaultValue; this.value = defaultValue;
this.appearance = appearance; this.appearance = appearance;
Class<?> defaultValueClass = defaultValue.getClass(); Class<?> defaultValueClass = defaultValue.getClass();
this.isFloatingPointNumber = (defaultValueClass == Double.class || defaultValueClass == Float.class); this.isFloatingPointNumber = (defaultValueClass == Double.class || defaultValueClass == Float.class);
} }
@@ -55,11 +55,22 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
@Nullable @Nullable
private T apiValue; private T apiValue;
/**
* Will be null if un-set. <br> <br>
*
* Some options aren't supported on all Minecraft versions,
* in those cases this value will be set to override the
* config file option.
*/
@Nullable
private T mcVersionOverrideValue;
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
private ConfigEntry( private ConfigEntry(
EConfigEntryAppearance appearance, EConfigEntryAppearance appearance,
@@ -78,11 +89,14 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
this.listenerList = listenerList; this.listenerList = listenerList;
} }
//endregion
//==========================// //==========================//
// property getters/setters // // property getters/setters //
//==========================// //==========================//
//region
/** the string used when entering the config into the command line or chat */ /** the string used when entering the config into the command line or chat */
public String getChatCommandName() { return this.chatCommandName; } public String getChatCommandName() { return this.chatCommandName; }
@@ -100,17 +114,23 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
public T getMax() { return this.max; } public T getMax() { return this.max; }
public void setMax(T newMax) { this.max = newMax; } public void setMax(T newMax) { this.max = newMax; }
//endregion
//===============// //===============//
// value setters // // value setters //
//===============// //===============//
//region
public void setApiValue(T newApiValue) public void setApiValue(T newApiValue)
{ {
this.apiValue = newApiValue; this.apiValue = newApiValue;
synchronized (this.listenerList)
{
this.listenerList.forEach(IConfigListener::onConfigValueSet); this.listenerList.forEach(IConfigListener::onConfigValueSet);
} }
}
public boolean apiIsOverriding() public boolean apiIsOverriding()
{ {
@@ -118,6 +138,13 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
&& this.apiValue != null; && this.apiValue != null;
} }
/** setting to null will allow the config to be used normally */
public void setMcVersionOverrideValue(@Nullable T value)
{ this.mcVersionOverrideValue = value; }
public boolean mcVersionOverridePresent()
{ return this.mcVersionOverrideValue != null; }
/** /**
* Should only be used when loading the config from file. <Br> * Should only be used when loading the config from file. <Br>
* Sets the value without informing the rest of the code (ie, it doesn't call listeners, or saving the value to file). * Sets the value without informing the rest of the code (ie, it doesn't call listeners, or saving the value to file).
@@ -129,8 +156,12 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
public void setWithoutSaving(T newValue) public void setWithoutSaving(T newValue)
{ {
super.set(newValue); super.set(newValue);
synchronized (this.listenerList)
{
this.listenerList.forEach(IConfigListener::onConfigValueSet); this.listenerList.forEach(IConfigListener::onConfigValueSet);
} }
}
@Override @Override
public void set(T newValue) public void set(T newValue)
{ {
@@ -141,23 +172,40 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
public void uiSetWithoutSaving(T newValue) public void uiSetWithoutSaving(T newValue)
{ {
this.setWithoutSaving(newValue); this.setWithoutSaving(newValue);
synchronized (this.listenerList)
{
this.listenerList.forEach(IConfigListener::onUiModify); this.listenerList.forEach(IConfigListener::onUiModify);
} }
}
public void uiSet(T newValue) public void uiSet(T newValue)
{ {
this.set(newValue); this.set(newValue);
synchronized (this.listenerList)
{
this.listenerList.forEach(IConfigListener::onUiModify); this.listenerList.forEach(IConfigListener::onUiModify);
} }
}
//endregion
//===============// //===============//
// value getters // // value getters //
//===============// //===============//
//region
@Override @Override
public T get() public T get()
{ {
// always use the MC version specific option if defined
if (this.mcVersionOverrideValue != null)
{
return this.mcVersionOverrideValue;
}
if (this.allowApiOverride if (this.allowApiOverride
&& this.apiValue != null) && this.apiValue != null)
{ {
@@ -174,11 +222,14 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
@Nullable @Nullable
public T getApiValue() { return this.apiValue; } public T getApiValue() { return this.apiValue; }
//endregion
//===========// //===========//
// listeners // // listeners //
//===========// //===========//
//region
/** Fired whenever the config value changes to a new value. */ /** Fired whenever the config value changes to a new value. */
public void addValueChangeListener(Consumer<T> onValueChangeFunc) public void addValueChangeListener(Consumer<T> onValueChangeFunc)
@@ -187,26 +238,38 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
this.addListener(changeListener); this.addListener(changeListener);
} }
/** Fired whenever the config value is updated, including when the value doesn't change (IE when the UI changes state or the config is reloaded). */ /** Fired whenever the config value is updated, including when the value doesn't change (IE when the UI changes state or the config is reloaded). */
public void addListener(IConfigListener newListener) { this.listenerList.add(newListener); } public void addListener(IConfigListener newListener)
{
synchronized (this.listenerList)
{
this.listenerList.add(newListener);
}
}
//public void removeValueChangeListener(Consumer<T> onValueChangeFunc) { } // not currently implemented public void removeListener(IConfigListener oldListener)
public void removeListener(IConfigListener oldListener) { this.listenerList.remove(oldListener); } {
synchronized (this.listenerList)
{
this.listenerList.remove(oldListener);
}
}
public void clearListeners() { this.listenerList.clear(); } public void clearListeners()
public ArrayList<IConfigListener> getListeners() { return this.listenerList; } {
/** Replaces the listener list */ synchronized (this.listenerList)
public void setListeners(ArrayList<IConfigListener> newListeners)
{ {
this.listenerList.clear(); this.listenerList.clear();
this.listenerList.addAll(newListeners);
} }
public void setListeners(IConfigListener... newListeners) { this.listenerList.addAll(Arrays.asList(newListeners)); } }
//endregion
//====================// //====================//
// min/max validation // // min/max validation //
//====================// //====================//
//region
/** Checks if this config's current value is valid */ /** Checks if this config's current value is valid */
public EConfigValidity getValidity() { return this.getValidity(this.value, this.min, this.max); } public EConfigValidity getValidity() { return this.getValidity(this.value, this.min, this.max); }
@@ -257,22 +320,31 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
} }
} }
//endregion
//===============// //===============//
// file handling // // file handling //
//===============// //===============//
//region
/** This should normally not be called since set() automatically calls this */ /** This should normally not be called since set() automatically calls this */
public void save() { ConfigHandler.INSTANCE.configFileHandler.saveEntry(this); } public void save() { ConfigHandler.INSTANCE.configFileHandler.saveEntry(this); }
/** This should normally not be called except for special circumstances */ /** This should normally not be called except for special circumstances */
public void load() { ConfigHandler.INSTANCE.configFileHandler.loadEntry(this); } public void load() { ConfigHandler.INSTANCE.configFileHandler.loadEntry(this); }
//endregion
//================// //================//
// base overrides // // base overrides //
//================// //================//
//region
@Override
public String toString() { return this.name + ": [" + this.get() + "]"; }
public boolean equals(AbstractConfigBase<?> obj) public boolean equals(AbstractConfigBase<?> obj)
{ {
@@ -294,11 +366,14 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
} }
} }
//endregion
//=========// //=========//
// builder // // builder //
//=========// //=========//
//region
public static class Builder<T> extends AbstractConfigBase.Builder<T, Builder<T>> public static class Builder<T> extends AbstractConfigBase.Builder<T, Builder<T>>
{ {
@@ -397,4 +472,8 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
} }
//endregion
} }
@@ -2,11 +2,9 @@ package com.seibel.distanthorizons.core.dataObjects;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -121,9 +119,9 @@ public class BlockBiomeWrapperPair
//=================// //=============//
// (de)serializing // // serializing //
//=================// //=============//
public String serialize() public String serialize()
{ {
@@ -135,17 +133,6 @@ public class BlockBiomeWrapperPair
return this.serialString; return this.serialString;
} }
public static BlockBiomeWrapperPair deserialize(String str, ILevelWrapper levelWrapper) throws DataCorruptedException
{
int separatorIndex = str.indexOf(FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING);
if (separatorIndex == -1)
{
throw new DataCorruptedException("Failed to deserialize BiomeBlockStateEntry ["+str+"], unable to find separator.");
}
IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapperOrGetDefault(str.substring(0, separatorIndex), levelWrapper);
IBlockStateWrapper blockState = WRAPPER_FACTORY.deserializeBlockStateWrapperOrGetDefault(str.substring(separatorIndex+FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING.length()), levelWrapper);
return BlockBiomeWrapperPair.get(blockState, biome);
}
} }
@@ -20,37 +20,42 @@
package com.seibel.distanthorizons.core.dataObjects.fullData; package com.seibel.distanthorizons.core.dataObjects.fullData;
import com.seibel.distanthorizons.core.dataObjects.BlockBiomeWrapperPair; import com.seibel.distanthorizons.core.dataObjects.BlockBiomeWrapperPair;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.util.objects.pooling.StringPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream; import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream; import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import it.unimi.dsi.fastutil.chars.CharArrayList;
import org.jetbrains.annotations.NotNull;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
* WARNING: This is not THREAD-SAFE! <br><br> * Used to map a numerical IDs to a Biome/BlockState pair. <br>
* * Note: This is not thread safe. <br>
* Used to map a numerical IDs to a Biome/BlockState pair. <br><br>
*
* TODO the serializing of this map might be really big
* since it stringifies every block and biome name, which is quite bulky.
* It might be worth while to have a biome and block ID that then both get mapped
* to the data point ID to reduce file size.
* And/or it would be good to dynamically remove IDs that aren't currently in use.
* *
* @author Leetom * @author Leetom
*/ */
public class FullDataPointIdMap public class FullDataPointIdMap
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("IdMap");
/** /**
* Should only be enabled when debugging. * Should only be enabled when debugging.
* Has the system check if any duplicate Entries were read/written * Has the system check if any duplicate Entries were read/written
@@ -75,14 +80,18 @@ public class FullDataPointIdMap
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
public FullDataPointIdMap(long pos) { this.pos = pos; } public FullDataPointIdMap(long pos) { this.pos = pos; }
//endregion
//=========// //=========//
// getters // // getters //
//=========// //=========//
//region
/** @see FullDataPointIdMap#getEntry(int) */ /** @see FullDataPointIdMap#getEntry(int) */
public IBiomeWrapper getBiomeWrapper(int id) throws IndexOutOfBoundsException { return this.getEntry(id).biome; } public IBiomeWrapper getBiomeWrapper(int id) throws IndexOutOfBoundsException { return this.getEntry(id).biome; }
@@ -113,11 +122,14 @@ public class FullDataPointIdMap
public long getPos() { return this.pos; } public long getPos() { return this.pos; }
//endregion
//=========// //=========//
// setters // // setters //
//=========// //=========//
//region
/** /**
* If an entry with the given values already exists nothing will * If an entry with the given values already exists nothing will
@@ -215,11 +227,14 @@ public class FullDataPointIdMap
this.cachedHashCode = 0; this.cachedHashCode = 0;
} }
//endregion
//=============// //=============//
// serializing // // serializing //
//=============// //=============//
//region
/** Serializes all contained entries into the given stream, formatted in UTF */ /** Serializes all contained entries into the given stream, formatted in UTF */
public void serialize(DhDataOutputStream outputStream) throws IOException public void serialize(DhDataOutputStream outputStream) throws IOException
@@ -249,8 +264,9 @@ public class FullDataPointIdMap
} }
} }
/** Creates a new IdBiomeBlockStateMap from the given UTF formatted stream */ /** Clears and populates the given {@link FullDataPointIdMap} from the given UTF formatted stream */
public static FullDataPointIdMap deserialize(DhDataInputStream inputStream, long pos, ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException public static void deserialize(@NotNull FullDataPointIdMap map, DhDataInputStream inputStream, long pos, ILevelWrapper levelWrapper)
throws IOException, InterruptedException, DataCorruptedException
{ {
int entityCount = inputStream.readInt(); int entityCount = inputStream.readInt();
if (entityCount < 0) if (entityCount < 0)
@@ -258,11 +274,14 @@ public class FullDataPointIdMap
throw new DataCorruptedException("FullDataPointIdMap deserialize entry count should have a number greater than or equal to 0, returned value ["+entityCount+"]."); throw new DataCorruptedException("FullDataPointIdMap deserialize entry count should have a number greater than or equal to 0, returned value ["+entityCount+"].");
} }
// clearing the old values is necessary so we can re-use the same map multiple times
map.clear(pos);
// only used when debugging // only used when debugging
HashMap<String, BlockBiomeWrapperPair> dataPointEntryBySerialization = new HashMap<>(); HashMap<String, BlockBiomeWrapperPair> dataPointEntryBySerialization = new HashMap<>();
FullDataPointIdMap newMap = new FullDataPointIdMap(pos); try(PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutArrays(0, 0, 0, 3))
{
for (int i = 0; i < entityCount; i++) for (int i = 0; i < entityCount; i++)
{ {
// necessary to prevent issues with deserializing objects after the level has been closed // necessary to prevent issues with deserializing objects after the level has been closed
@@ -271,13 +290,34 @@ public class FullDataPointIdMap
throw new InterruptedException("[" + FullDataPointIdMap.class.getSimpleName() + "] deserializing interrupted."); throw new InterruptedException("[" + FullDataPointIdMap.class.getSimpleName() + "] deserializing interrupted.");
} }
int length = inputStream.readUnsignedShort();
CharArrayList fullCharList = checkout.getCharArray(0, length);
CharArrayList biomeCharList = checkout.getCharArray(1, length);
CharArrayList blockCharList = checkout.getCharArray(2, length);
// parse the full UTF string
for (int stringIndex = 0; stringIndex < length; stringIndex++)
{
byte b = inputStream.readByte();
char c = (char) (b & 0xFF);
fullCharList.set(stringIndex, c);
}
splitCharArray(fullCharList, biomeCharList, blockCharList);
String biomeString = StringPool.INSTANCE.getPooledString(biomeCharList);
IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapperOrGetDefault(biomeString, levelWrapper);
String blockStateString = StringPool.INSTANCE.getPooledString(blockCharList);
IBlockStateWrapper blockState = WRAPPER_FACTORY.deserializeBlockStateWrapperOrGetDefault(blockStateString, levelWrapper);
BlockBiomeWrapperPair newPair = BlockBiomeWrapperPair.get(blockState, biome);
map.blockBiomePairList.add(newPair);
String entryString = inputStream.readUTF();
BlockBiomeWrapperPair newPair = BlockBiomeWrapperPair.deserialize(entryString, levelWrapper);
newMap.blockBiomePairList.add(newPair);
if (RUN_SERIALIZATION_DUPLICATE_VALIDATION) if (RUN_SERIALIZATION_DUPLICATE_VALIDATION)
{ {
String entryString = StringPool.INSTANCE.getPooledString(fullCharList);
if (dataPointEntryBySerialization.containsKey(entryString)) if (dataPointEntryBySerialization.containsKey(entryString))
{ {
LOGGER.error("Duplicate deserialized entry found with serial: " + entryString); LOGGER.error("Duplicate deserialized entry found with serial: " + entryString);
@@ -289,21 +329,85 @@ public class FullDataPointIdMap
dataPointEntryBySerialization.put(entryString, newPair); dataPointEntryBySerialization.put(entryString, newPair);
} }
} }
}
if (newMap.size() != entityCount) if (map.size() != entityCount)
{ {
// if the mappings are out of sync then the LODs will render incorrectly due to IDs being wrong // if the mappings are out of sync then the LODs will render incorrectly due to IDs being wrong
LodUtil.assertNotReach("ID maps failed to deserialize for pos: ["+ DhSectionPos.toString(pos)+"], incorrect entity count. Expected count ["+entityCount+"], actual count ["+newMap.size()+"]"); LodUtil.assertNotReach("ID maps failed to deserialize for pos: ["+ DhSectionPos.toString(pos)+"], incorrect entity count. Expected count ["+entityCount+"], actual count ["+map.size()+"]");
}
}
/**
* Splits up the given input {@link CharArrayList} into the
* necessary biome and blockstate lists based on the location of
* {@link FullDataPointIdMap#BLOCK_STATE_SEPARATOR_STRING}
*/
private static void splitCharArray(
CharArrayList input,
CharArrayList biomeString, CharArrayList blockString) throws DataCorruptedException
{
boolean separatorFound = false;
int foundStartIndex = -1;
int separatorIndex = 0;
for (int inputIndex = 0; inputIndex < input.size(); inputIndex++)
{
char ch = input.getChar(inputIndex);
if (ch == FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING.charAt(separatorIndex))
{
if (!separatorFound)
{
foundStartIndex = inputIndex;
}
separatorFound = true;
separatorIndex++;
}
else
{
separatorFound = false;
foundStartIndex = -1;
separatorIndex = 0;
} }
return newMap; if (separatorFound
&& separatorIndex == FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING.length())
{
break;
} }
}
if (foundStartIndex == -1)
{
throw new DataCorruptedException("Failed to deserialize BiomeBlockStateEntry ["+input.toString()+"], unable to find separator.");
}
biomeString.clear();
for (int i = 0; i < foundStartIndex; i++)
{
biomeString.push(input.getChar(i));
}
blockString.clear();
for (int i = foundStartIndex + FullDataPointIdMap.BLOCK_STATE_SEPARATOR_STRING.length();
i < input.size();
i++)
{
blockString.push(input.getChar(i));
}
}
//endregion
//===========// //===========//
// overrides // // overrides //
//===========// //===========//
//region
@Override
public String toString() { return DhSectionPos.toString(this.pos) + " size: " + this.blockBiomePairList.size(); }
@Override @Override
public boolean equals(Object other) public boolean equals(Object other)
@@ -339,6 +443,8 @@ public class FullDataPointIdMap
this.cachedHashCode = result; this.cachedHashCode = result;
} }
//endregion
} }
@@ -166,8 +166,7 @@ public class FullDataSourceV1
this.setDataPoints(dataPoints); this.setDataPoints(dataPoints);
FullDataPointIdMap mapping = this.readIdMappings(inputStream, level.getLevelWrapper()); this.readIdMappings(this.mapping, inputStream, level.getLevelWrapper());
this.setIdMapping(mapping);
} }
@@ -347,7 +346,7 @@ public class FullDataSourceV1
outputStream.writeInt(DATA_GUARD_BYTE); outputStream.writeInt(DATA_GUARD_BYTE);
this.mapping.serialize(outputStream); this.mapping.serialize(outputStream);
} }
public FullDataPointIdMap readIdMappings(DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException public void readIdMappings(FullDataPointIdMap map, DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{ {
int guardByte = inputStream.readInt(); int guardByte = inputStream.readInt();
if (guardByte != DATA_GUARD_BYTE) if (guardByte != DATA_GUARD_BYTE)
@@ -355,9 +354,8 @@ public class FullDataSourceV1
throw new IOException("Invalid data content end guard for ID mapping"); throw new IOException("Invalid data content end guard for ID mapping");
} }
return FullDataPointIdMap.deserialize(inputStream, this.pos, levelWrapper); FullDataPointIdMap.deserialize(map, inputStream, this.pos, levelWrapper);
} }
public void setIdMapping(FullDataPointIdMap mappings) { this.mapping.mergeAndReturnRemappedEntityIds(mappings); }
@@ -31,10 +31,9 @@ import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pooling.AbstractPhantomArrayList; import com.seibel.distanthorizons.core.util.objects.pooling.AbstractPhantomArrayList;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.sql.dto.util.FullDataMinMaxPosUtil; import com.seibel.distanthorizons.core.sql.dto.util.FullDataMinMaxPosUtil;
@@ -44,6 +43,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -78,7 +78,9 @@ public class FullDataSourceV2
/** how many chunks wide this datasource is at detail level 0. */ /** how many chunks wide this datasource is at detail level 0. */
public static final int NUMB_OF_CHUNKS_WIDE = WIDTH / LodUtil.CHUNK_WIDTH; public static final int NUMB_OF_CHUNKS_WIDE = WIDTH / LodUtil.CHUNK_WIDTH;
public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("FullDataV2"); private static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("FullDataV2");
private static final ThreadLocal<FullDataPointIdMap> DATA_MAP_FOR_REMAPPING_REF = ThreadLocal.withInitial(() -> new FullDataPointIdMap(DhSectionPos.encode((byte)0, 0, 0)));
@@ -128,6 +130,7 @@ public class FullDataSourceV2
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
//region
public static FullDataSourceV2 createFromChunk(ILevelWrapper levelWrapper, IChunkWrapper chunkWrapper) { return LodDataBuilder.createFromChunk(levelWrapper, chunkWrapper); } public static FullDataSourceV2 createFromChunk(ILevelWrapper levelWrapper, IChunkWrapper chunkWrapper) { return LodDataBuilder.createFromChunk(levelWrapper, chunkWrapper); }
@@ -222,7 +225,7 @@ public class FullDataSourceV2
byte @Nullable [] columnGenerationSteps, byte @Nullable [] columnWorldCompressionMode, byte @Nullable [] columnGenerationSteps, byte @Nullable [] columnWorldCompressionMode,
boolean empty) boolean empty)
{ {
super(ARRAY_LIST_POOL, 2, 0, WIDTH * WIDTH); super(ARRAY_LIST_POOL, 2, 0, WIDTH * WIDTH, 0);
LodUtil.assertTrue(data == null || data.length == WIDTH * WIDTH); LodUtil.assertTrue(data == null || data.length == WIDTH * WIDTH);
@@ -274,11 +277,14 @@ public class FullDataSourceV2
} }
} }
//endregion
//=========// //=========//
// getters // // getters //
//=========// //=========//
//region
public LongArrayList getColumnAtRelPos(int relX, int relZ) throws IndexOutOfBoundsException public LongArrayList getColumnAtRelPos(int relX, int relZ) throws IndexOutOfBoundsException
{ return this.dataPoints[relativePosToIndex(relX, relZ)]; } { return this.dataPoints[relativePosToIndex(relX, relZ)]; }
@@ -301,16 +307,11 @@ public class FullDataSourceV2
*/ */
public long getDataPointAtBlockPos(int blockPosX, int blockPosY, int blockPosZ, int levelMinY) public long getDataPointAtBlockPos(int blockPosX, int blockPosY, int blockPosZ, int levelMinY)
{ {
DhLodPos requestedPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ); long requestedPos = DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ);
// stop if the requested blockPos is outside this datasource // stop if the requested blockPos is outside this datasource
{ {
// get the detail levels for this request long sectionPos = DhSectionPos.encodeContaining(DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL, new DhBlockPos(blockPosX, blockPosY, blockPosZ));
byte requestedDetailLevel = requestedPos.detailLevel;
byte requestedSectionDetailLevel = (byte) (requestedDetailLevel + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
// get the positions for this request
long sectionPos = requestedPos.getSectionPosWithSectionDetailLevel(requestedSectionDetailLevel);
if (!DhSectionPos.contains(this.pos, sectionPos)) if (!DhSectionPos.contains(this.pos, sectionPos))
{ {
return FullDataPointUtil.EMPTY_DATA_POINT; return FullDataPointUtil.EMPTY_DATA_POINT;
@@ -320,10 +321,10 @@ public class FullDataSourceV2
// get the relative data source position // get the relative data source position
byte requestDetailLevel = (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); byte requestDetailLevel = (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
DhLodPos relativePos = requestedPos.getDhSectionRelativePositionForDetailLevel(requestDetailLevel); long relativePos = DhSectionPos.getDhSectionRelativePositionForDetailLevel(requestedPos, requestDetailLevel);
// get the data column // get the data column
LongArrayList dataColumn = this.getColumnAtRelPos(relativePos.x, relativePos.z); LongArrayList dataColumn = this.getColumnAtRelPos(DhSectionPos.getX(relativePos), DhSectionPos.getZ(relativePos));
if (dataColumn == null) if (dataColumn == null)
{ {
return FullDataPointUtil.EMPTY_DATA_POINT; return FullDataPointUtil.EMPTY_DATA_POINT;
@@ -361,11 +362,14 @@ public class FullDataSourceV2
return FullDataPointUtil.EMPTY_DATA_POINT; return FullDataPointUtil.EMPTY_DATA_POINT;
} }
//endregion
//==========// //==========//
// updating // // updating //
//==========// //==========//
//region
public boolean updateFromDataSource(@NotNull FullDataSourceV2 inputDataSource) public boolean updateFromDataSource(@NotNull FullDataSourceV2 inputDataSource)
{ {
@@ -439,6 +443,8 @@ public class FullDataSourceV2
} }
// needed to prevent infinite mapped ID growth
this.removeUnusedIdsAndRemap();
@@ -676,13 +682,13 @@ public class FullDataSourceV2
return dataChanged; return dataChanged;
} }
/** /**
* The minimum value is used because we don't want to accidentally record that * The minimum value is used because we don't want to accidentally record that
* something was generated when it wasn't. * something was generated when it wasn't.
*/ */
private static byte determineMinWorldGenStepForTwoByTwoColumn(ByteArrayList columnGenerationSteps, int relX, int relZ) private static byte determineMinWorldGenStepForTwoByTwoColumn(ByteArrayList columnGenerationSteps, int relX, int relZ)
{ {
// TODO merge similar logic with determineHighestWorldCompressionForTwoByTwoColumn
byte minWorldGenStepValue = Byte.MAX_VALUE; byte minWorldGenStepValue = Byte.MAX_VALUE;
for (int x = 0; x < 2; x++) for (int x = 0; x < 2; x++)
{ {
@@ -701,7 +707,6 @@ public class FullDataSourceV2
*/ */
private static byte determineHighestWorldCompressionForTwoByTwoColumn(ByteArrayList columnCompressionMode, int relX, int relZ) private static byte determineHighestWorldCompressionForTwoByTwoColumn(ByteArrayList columnCompressionMode, int relX, int relZ)
{ {
// TODO merge similar logic with determineMinWorldGenStepForTwoByTwoColumn
byte minWorldGenStepValue = Byte.MIN_VALUE; byte minWorldGenStepValue = Byte.MIN_VALUE;
for (int x = 0; x < 2; x++) for (int x = 0; x < 2; x++)
{ {
@@ -777,7 +782,7 @@ public class FullDataSourceV2
return newColumnList; return newColumnList;
} }
// sort the transitions from bottom to top // TODO // sort the transitions from bottom to top
yTransitions.sort(null); yTransitions.sort(null);
// create index trackers for each column, // create index trackers for each column,
@@ -1129,11 +1134,76 @@ public class FullDataSourceV2
return dataChanged; return dataChanged;
} }
/**
* Should be run at the end of {@link FullDataSourceV2#updateFromDataSource}
* so the {@link FullDataPointIdMap} will only contain ID's that are actively in use. <br><br>
*
* This prevents the {@link FullDataPointIdMap} from growing infinitely when merged.
*
* @see FullDataPointIdMap#mergeAndReturnRemappedEntityIds(FullDataPointIdMap)
*/
private void removeUnusedIdsAndRemap()
{
Int2IntOpenHashMap newIdByOldId = new Int2IntOpenHashMap();
FullDataPointIdMap newMap = DATA_MAP_FOR_REMAPPING_REF.get();
newMap.clear(this.pos);
// find all the IDs that are currently in use
for (int x = 0; x < WIDTH; x++)
{
for (int z = 0; z < WIDTH; z++)
{
int index = relativePosToIndex(x, z);
LongArrayList dataColumn = this.dataPoints[index];
for (int i = 0; i < dataColumn.size(); i++)
{
long dataPoint = dataColumn.getLong(i);
int oldId = FullDataPointUtil.getId(dataPoint);
int newId = newMap.addIfNotPresentAndGetId(
this.mapping.getBiomeWrapper(oldId),
this.mapping.getBlockStateWrapper(oldId));
newIdByOldId.put(oldId, newId);
}
}
}
// replace the old entries to remove any unneeded ones
this.mapping.clear(this.pos);
this.mapping.addAll(newMap);
// remap the data
for (int x = 0; x < WIDTH; x++)
{
for (int z = 0; z < WIDTH; z++)
{
int index = relativePosToIndex(x, z);
LongArrayList dataColumn = this.dataPoints[index];
for (int i = 0; i < dataColumn.size(); i++)
{
long oldDataPoint = dataColumn.getLong(i);
int oldId = FullDataPointUtil.getId(oldDataPoint);
int newId = newIdByOldId.get(oldId);
long newDataPoint = FullDataPointUtil.setId(oldDataPoint, newId);
dataColumn.set(i, newDataPoint);
}
}
}
}
//endregion
//===================// //===================//
// adjacent clearing // // adjacent clearing //
//===================// //===================//
//region
/** Removes any non-adjacent data from the given direction. */ /** Removes any non-adjacent data from the given direction. */
public void clearAllNonAdjData(EDhDirection direction) public void clearAllNonAdjData(EDhDirection direction)
@@ -1162,10 +1232,13 @@ public class FullDataSourceV2
} }
} }
//endregion
//================// //================//
// helper methods // // helper methods //
//================// //================//
//region
/** /**
* Usually this should just be used internally, but there may be instances * Usually this should just be used internally, but there may be instances
@@ -1257,11 +1330,14 @@ public class FullDataSourceV2
} }
} }
//endregion
//=====================// //=====================//
// setters and getters // // setters and getters //
//=====================// //=====================//
//region
public long getPos() { return this.pos; } public long getPos() { return this.pos; }
@@ -1291,11 +1367,14 @@ public class FullDataSourceV2
} }
} }
//endregion
//=============// //=============//
// API methods // // API methods //
//=============// //=============//
//region
public void setRunApiSetterValidation(boolean runValidation) { this.runApiSetterValidation = runValidation; } public void setRunApiSetterValidation(boolean runValidation) { this.runApiSetterValidation = runValidation; }
@@ -1316,7 +1395,6 @@ public class FullDataSourceV2
LongArrayList packedDataPoints = LodDataBuilder.convertApiDataPointListToPackedLongArray(columnDataPoints, this, 0, true); LongArrayList packedDataPoints = LodDataBuilder.convertApiDataPointListToPackedLongArray(columnDataPoints, this, 0, true);
// TODO there should be an "unknown" compression and generation step, or be defined via the datapoints
this.setSingleColumn(packedDataPoints, relX, relZ, EDhApiWorldGenerationStep.SURFACE, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS); this.setSingleColumn(packedDataPoints, relX, relZ, EDhApiWorldGenerationStep.SURFACE, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
return columnDataPoints; return columnDataPoints;
@@ -1344,20 +1422,26 @@ public class FullDataSourceV2
return apiList; return apiList;
} }
//endregion
//============// //============//
// unit tests // // unit tests //
//============// //============//
//region
public PhantomArrayListCheckout getPhantomArrayCheckoutForUnitTesting() public PhantomArrayListCheckout getPhantomArrayCheckoutForUnitTesting()
{ return this.pooledArraysCheckout; } { return this.pooledArraysCheckout; }
//endregion
//================// //================//
// base overrides // // base overrides //
//================// //================//
//region
@Override @Override
public String toString() { return DhSectionPos.toString(this.pos); } public String toString() { return DhSectionPos.toString(this.pos); }
@@ -1404,6 +1488,8 @@ public class FullDataSourceV2
} }
} }
//endregion
} }
@@ -19,13 +19,12 @@
package com.seibel.distanthorizons.core.dataObjects.render; package com.seibel.distanthorizons.core.dataObjects.render;
import com.seibel.distanthorizons.api.enums.config.EDhApiVerticalQuality;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pooling.AbstractPhantomArrayList; import com.seibel.distanthorizons.core.util.objects.pooling.AbstractPhantomArrayList;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnQuadView;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
@@ -46,15 +45,16 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
/** will be zero if an empty data source was created */ /**
public int verticalDataCount; * will be zero if an empty data source was created
* @see EDhApiVerticalQuality#calculateMaxNumberOfVerticalSlicesAtDetailLevel(byte)
*/
public int maxVerticalSliceCount;
public long pos; public long pos;
public int yOffset; public int yOffset;
public final LongArrayList renderDataContainer; public final LongArrayList renderDataContainer;
public final DebugSourceFlag[] debugSourceFlags;
private boolean isEmpty = true; private boolean isEmpty = true;
@@ -62,64 +62,67 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
//region
public static ColumnRenderSource createEmpty(long pos, int maxVerticalSize, int yOffset) public static ColumnRenderSource createEmpty(long pos, int maxVertSliceCount, int yOffset)
{ return new ColumnRenderSource(pos, maxVerticalSize, yOffset); } { return new ColumnRenderSource(pos, maxVertSliceCount, yOffset); }
/** /**
* Creates an empty ColumnRenderSource. * Creates an empty ColumnRenderSource.
* *
* @param pos the relative position of the container * @param pos the relative position of the container
* @param maxVerticalSize the maximum vertical size of the container * @param maxVertSliceCount the maximum vertical size of the container
*/ */
private ColumnRenderSource(long pos, int maxVerticalSize, int yOffset) private ColumnRenderSource(long pos, int maxVertSliceCount, int yOffset)
{ {
super(ARRAY_LIST_POOL, 0, 0, 1); super(ARRAY_LIST_POOL, 0, 0, 1, 0);
this.pos = pos; this.pos = pos;
this.yOffset = yOffset; this.yOffset = yOffset;
this.verticalDataCount = maxVerticalSize; this.maxVerticalSliceCount = maxVertSliceCount;
this.renderDataContainer = this.pooledArraysCheckout.getLongArray(0, WIDTH * WIDTH * this.verticalDataCount); this.renderDataContainer = this.pooledArraysCheckout.getLongArray(0, WIDTH * WIDTH * this.maxVerticalSliceCount);
this.debugSourceFlags = new DebugSourceFlag[WIDTH * WIDTH];
} }
//endregion
//========================// //========================//
// datapoint manipulation // // datapoint manipulation //
//========================// //========================//
//region
public long getDataPoint(int posX, int posZ, int verticalIndex) { return this.renderDataContainer.getLong(posX * WIDTH * this.verticalDataCount + posZ * this.verticalDataCount + verticalIndex); } public long getDataPoint(int posX, int posZ, int verticalIndex) { return this.renderDataContainer.getLong(posX * WIDTH * this.maxVerticalSliceCount + posZ * this.maxVerticalSliceCount + verticalIndex); }
public ColumnArrayView getVerticalDataPointView(int posX, int posZ) public void populateColumnView(ColumnRenderView view, int posX, int posZ) throws IllegalArgumentException
{ {
int offset = posX * WIDTH * this.verticalDataCount + posZ * this.verticalDataCount; int offset = posX * WIDTH * this.maxVerticalSliceCount + posZ * this.maxVerticalSliceCount;
// don't allow returning views that are outside this render source's bounds // don't allow returning views that are outside this render source's bounds
if (offset >= this.renderDataContainer.size()) if (offset >= this.renderDataContainer.size())
{ {
return null; throw new IllegalArgumentException("Column View offset ["+offset+"] greater than parent render data container ["+DhSectionPos.toString(this.pos)+"] size ["+this.renderDataContainer.size()+"].");
} }
else if (posX < 0 || posX >= WIDTH else if (posX < 0 || posX >= WIDTH
|| posZ < 0 || posZ >= WIDTH) || posZ < 0 || posZ >= WIDTH)
{ {
return null; throw new IllegalArgumentException("Column View pos outside valid range ["+posX+","+posZ+"].");
} }
return new ColumnArrayView(this.renderDataContainer, this.verticalDataCount, view.populate(
offset, this.verticalDataCount); this.renderDataContainer, this.maxVerticalSliceCount,
offset, this.maxVerticalSliceCount);
} }
public ColumnQuadView getFullQuadView() { return this.getQuadViewOverRange(0, 0, WIDTH, WIDTH); } //endregion
public ColumnQuadView getQuadViewOverRange(int quadX, int quadZ, int quadXSize, int quadZSize) { return new ColumnQuadView(this.renderDataContainer, WIDTH, this.verticalDataCount, quadX, quadZ, quadXSize, quadZSize); }
//=====================// //=====================//
// data helper methods // // data helper methods //
//=====================// //=====================//
//region
public Long getPos() { return this.pos; } public Long getPos() { return this.pos; }
public Long getKey() { return this.pos; } public Long getKey() { return this.pos; }
@@ -137,15 +140,16 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
return false; return false;
} }
try (ColumnRenderView columnView = ColumnRenderView.getPooled())
{
for (int x = 0; x < WIDTH; x++) for (int x = 0; x < WIDTH; x++)
{ {
for (int z = 0; z < WIDTH; z++) for (int z = 0; z < WIDTH; z++)
{ {
ColumnArrayView columnArrayView = this.getVerticalDataPointView(x,z); this.populateColumnView(columnView, x, z);
for (int i = 0; i < columnArrayView.size; i++) for (int i = 0; i < columnView.size; i++)
{ {
long dataPoint = columnArrayView.get(i); long dataPoint = columnView.get(i);
if (!RenderDataPointUtil.hasZeroHeight(dataPoint)) if (!RenderDataPointUtil.hasZeroHeight(dataPoint))
{ {
return true; return true;
@@ -153,35 +157,19 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
} }
} }
} }
}
return false; return false;
} }
//endregion
//=======//
// debug //
//=======//
/** Sets the debug flag for the given area */
public void fillDebugFlag(int xStart, int zStart, int xWidth, int zWidth, DebugSourceFlag flag)
{
for (int x = xStart; x < xStart + xWidth; x++)
{
for (int z = zStart; z < zStart + zWidth; z++)
{
this.debugSourceFlags[x * WIDTH + z] = flag;
}
}
}
public DebugSourceFlag debugGetFlag(int ox, int oz) { return this.debugSourceFlags[ox * WIDTH + oz]; }
//==============// //==============//
// base methods // // base methods //
//==============// //==============//
//region
@Override @Override
public String toString() public String toString()
@@ -199,11 +187,11 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
{ {
for (int x = 0; x < size; x++) for (int x = 0; x < size; x++)
{ {
for (int y = 0; y < this.verticalDataCount; y++) for (int y = 0; y < this.maxVerticalSliceCount; y++)
{ {
//Converting the dataToHex //Converting the dataToHex
stringBuilder.append(Long.toHexString(this.getDataPoint(x, z, y))); stringBuilder.append(Long.toHexString(this.getDataPoint(x, z, y)));
if (y != this.verticalDataCount - 1) if (y != this.maxVerticalSliceCount - 1)
stringBuilder.append(SUBDATA_DELIMITER); stringBuilder.append(SUBDATA_DELIMITER);
} }
@@ -217,21 +205,8 @@ public class ColumnRenderSource extends AbstractPhantomArrayList
return stringBuilder.toString(); return stringBuilder.toString();
} }
//endregion
//==============//
// helper enums //
//==============//
public enum DebugSourceFlag
{
FULL(ColorUtil.BLUE),
DIRECT(ColorUtil.WHITE),
FILE(ColorUtil.BROWN);
public final int color;
DebugSourceFlag(int color) { this.color = color; }
}
} }
@@ -41,104 +41,102 @@ public final class BufferQuad
public static final int MAX_QUAD_WIDTH_FOR_EARTH_CURVATURE = LodUtil.CHUNK_WIDTH; public static final int MAX_QUAD_WIDTH_FOR_EARTH_CURVATURE = LodUtil.CHUNK_WIDTH;
public final short x; public short x;
public final short y; public short y;
public final short z; public short z;
public short widthEastWest; public short widthEastWest;
/** This is both North/South and Up/Down since the merging logic is the same either way */ /** This is both North/South and Up/Down since the merging logic is the same either way */
public short widthNorthSouthOrUpDown; public short widthNorthSouthOrHeight;
public final int color; public int color;
/** used by the Iris shader mod to determine how each LOD should be rendered */ /** used by the Iris shader mod to determine how each LOD should be rendered */
public final byte irisBlockMaterialId; public byte irisBlockMaterialId;
public final byte skyLight; public byte skyLight;
public final byte blockLight; public byte blockLight;
public final EDhDirection direction; public EDhDirection direction;
public boolean hasError = false; public boolean hasError = false;
// Pre-computed sort keys to avoid recomputing on every comparison
// Slight increase in memory for reduction in cpu usage
public long sortKeyEastWest;
public long sortKeyNorthSouth;
BufferQuad(
short x, short y, short z, short widthEastWest, short widthNorthSouthOrUpDown, //=============//
// constructor //
//=============//
//region
public BufferQuad() {}
public void set(short x, short y, short z, short widthEastWest, short widthNorthSouthOrHeight,
int color, byte irisBlockMaterialId, byte skylight, byte blockLight, int color, byte irisBlockMaterialId, byte skylight, byte blockLight,
EDhDirection direction) EDhDirection direction)
{ {
if (widthEastWest == 0 || widthNorthSouthOrUpDown == 0) if (widthEastWest == 0 || widthNorthSouthOrHeight == 0)
{
throw new IllegalArgumentException("Size 0 quad!"); throw new IllegalArgumentException("Size 0 quad!");
if (widthEastWest < 0 || widthNorthSouthOrUpDown < 0) }
if (widthEastWest < 0 || widthNorthSouthOrHeight < 0)
{
throw new IllegalArgumentException("Negative sized quad!"); throw new IllegalArgumentException("Negative sized quad!");
}
this.x = x; this.x = x;
this.y = y; this.y = y;
this.z = z; this.z = z;
this.widthEastWest = widthEastWest; this.widthEastWest = widthEastWest;
this.widthNorthSouthOrUpDown = widthNorthSouthOrUpDown; this.widthNorthSouthOrHeight = widthNorthSouthOrHeight;
this.color = color; this.color = color;
this.irisBlockMaterialId = irisBlockMaterialId; this.irisBlockMaterialId = irisBlockMaterialId;
this.skyLight = skylight; this.skyLight = skylight;
this.blockLight = blockLight; this.blockLight = blockLight;
this.direction = direction; this.direction = direction;
this.sortKeyEastWest = computeSortKey(direction, true);
this.sortKeyNorthSouth = computeSortKey(direction, false);
} }
private long computeSortKey(EDhDirection dir, boolean eastWest)
/** a rough but fast calculation */
double calculateDistance(double relativeX, double relativeY, double relativeZ)
{ {
return Math.pow(relativeX - this.x, 2) + Math.pow(relativeY - this.y, 2) + Math.pow(relativeZ - this.z, 2); if (eastWest)
{
switch (dir.axis)
{
case X: return (long) x << 48 | (long) y << 32 | (long) z << 16;
case Y: return (long) y << 48 | (long) z << 32 | (long) x << 16;
case Z: return (long) z << 48 | (long) y << 32 | (long) x << 16;
default: throw new IllegalArgumentException("Invalid Axis enum: [" + dir.axis + "].");
}
}
else
{
switch (dir.axis)
{
case X: return (long) x << 48 | (long) z << 32 | (long) y << 16;
case Y: return (long) y << 48 | (long) x << 32 | (long) z << 16;
case Z: return (long) z << 48 | (long) x << 32 | (long) y << 16;
default: throw new IllegalArgumentException("Invalid Axis enum: [" + dir.axis + "].");
}
}
} }
/** compares this quad's position to the given quad */ //endregion
/** compares this quad's position to the given quad using pre-computed sort keys */
public int compare(BufferQuad quad, BufferMergeDirectionEnum compareDirection) public int compare(BufferQuad quad, BufferMergeDirectionEnum compareDirection)
{ {
if (this.direction != quad.direction) if (this.direction != quad.direction)
throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + this.direction); throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + this.direction);
if (compareDirection == BufferMergeDirectionEnum.EastWest) return compareDirection == BufferMergeDirectionEnum.EastWest
{ ? Long.compare(this.sortKeyEastWest, quad.sortKeyEastWest)
switch (this.direction.axis) : Long.compare(this.sortKeyNorthSouth, quad.sortKeyNorthSouth);
{
case X:
return threeDimensionalCompare(this.x, this.y, this.z, quad.x, quad.y, quad.z);
case Y:
return threeDimensionalCompare(this.y, this.z, this.x, quad.y, quad.z, quad.x);
case Z:
return threeDimensionalCompare(this.z, this.y, this.x, quad.z, quad.y, quad.x);
default:
throw new IllegalArgumentException("Invalid Axis enum: [" + this.direction.axis + "].");
}
}
else
{
switch (this.direction.axis)
{
case X:
return threeDimensionalCompare(this.x, this.z, this.y, quad.x, quad.z, quad.y);
case Y:
return threeDimensionalCompare(this.y, this.x, this.z, quad.y, quad.x, quad.z);
case Z:
return threeDimensionalCompare(this.z, this.x, this.y, quad.z, quad.x, quad.y);
default:
throw new IllegalArgumentException("Invalid Axis enum: [" + this.direction.axis + "].");
}
}
}
/**
* Compares two 3D points A and B. <br>
* The X, Y, and Z coordinates can be passed into parameters 0, 1, and 2 in any order
* provided they are in the same order for both A and B. <br>
* With the 0th parameter being the most significant when comparing.
*/
private static int threeDimensionalCompare(short a0, short a1, short a2, short b0, short b1, short b2)
{
long a = (long) a0 << 48 | (long) a1 << 32 | (long) a2 << 16;
long b = (long) b0 << 48 | (long) b1 << 32 | (long) b2 << 16;
return Long.compare(a, b);
} }
@@ -150,11 +148,15 @@ public final class BufferQuad
public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection) public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection)
{ {
if (quad.hasError || this.hasError) if (quad.hasError || this.hasError)
{
return false; return false;
}
// only merge quads that are in the same direction // only merge quads that are in the same direction
if (this.direction != quad.direction) if (this.direction != quad.direction)
{
return false; return false;
}
// make sure these quads share the same perpendicular axis // make sure these quads share the same perpendicular axis
if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y) if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y)
@@ -171,7 +173,6 @@ public final class BufferQuad
short otherParallelCompareStartPos; short otherParallelCompareStartPos;
switch (this.direction.axis) switch (this.direction.axis)
{ {
default: // shouldn't normally happen, just here to make the compiler happy
case X: case X:
if (mergeDirection == BufferMergeDirectionEnum.EastWest) if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{ {
@@ -228,6 +229,9 @@ public final class BufferQuad
otherParallelCompareStartPos = quad.z; otherParallelCompareStartPos = quad.z;
} }
break; break;
default: // shouldn't normally happen, just here to make the compiler happy
throw new IllegalArgumentException("Unsupported axis: ["+this.direction.axis+"]");
} }
// get the width of this quad in the relevant axis // get the width of this quad in the relevant axis
@@ -238,17 +242,17 @@ public final class BufferQuad
if (mergeDirection == BufferMergeDirectionEnum.EastWest) if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{ {
thisPerpendicularCompareWidth = this.widthEastWest; thisPerpendicularCompareWidth = this.widthEastWest;
thisParallelCompareWidth = this.widthNorthSouthOrUpDown; thisParallelCompareWidth = this.widthNorthSouthOrHeight;
otherPerpendicularCompareWidth = quad.widthEastWest; otherPerpendicularCompareWidth = quad.widthEastWest;
otherParallelCompareWidth = quad.widthNorthSouthOrUpDown; otherParallelCompareWidth = quad.widthNorthSouthOrHeight;
} }
else else
{ {
thisPerpendicularCompareWidth = this.widthNorthSouthOrUpDown; thisPerpendicularCompareWidth = this.widthNorthSouthOrHeight;
thisParallelCompareWidth = this.widthEastWest; thisParallelCompareWidth = this.widthEastWest;
otherPerpendicularCompareWidth = quad.widthNorthSouthOrUpDown; otherPerpendicularCompareWidth = quad.widthNorthSouthOrHeight;
otherParallelCompareWidth = quad.widthEastWest; otherParallelCompareWidth = quad.widthEastWest;
} }
@@ -318,7 +322,7 @@ public final class BufferQuad
// merge the two quads // merge the two quads
if (mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown) if (mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown)
{ {
this.widthNorthSouthOrUpDown += quad.widthNorthSouthOrUpDown; this.widthNorthSouthOrHeight += quad.widthNorthSouthOrHeight;
} }
else // if (mergeDirection == MergeDirection.EastWest) else // if (mergeDirection == MergeDirection.EastWest)
{ {
@@ -329,4 +333,6 @@ public final class BufferQuad
return true; return true;
} }
} }
@@ -19,16 +19,18 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiTransparency;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
@@ -52,23 +54,28 @@ public class ColumnBox
public static void addBoxQuadsToBuilder( public static void addBoxQuadsToBuilder(
LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, IDhClientLevel clientLevel, LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, IDhClientLevel clientLevel,
short width, short yHeight, short blockWidth, short yHeight,
short minX, short minY, short minZ, short minX, short minY, short minZ,
int color, byte irisBlockMaterialId, byte skyLight, byte blockLight, int color, byte irisBlockMaterialId, byte skyLight, byte blockLight,
long topData, long bottomData, ColumnArrayView[] adjData, boolean[] isAdjDataSameDetailLevel) long topData, long bottomData, ColumnRenderView[] adjData, boolean[] isAdjDataSameDetailLevel)
{ {
//================// //================//
// variable setup // // variable setup //
//================// //================//
short maxX = (short) (minX + width); IClientLevelWrapper clientLevelWrapper = clientLevel.getClientLevelWrapper();
if (clientLevelWrapper == null)
{
LodUtil.assertNotReach("addBoxQuadsToBuilder getClientLevelWrapper should always succeed");
}
short maxX = (short) (minX + blockWidth);
short maxY = (short) (minY + yHeight); short maxY = (short) (minY + yHeight);
short maxZ = (short) (minZ + width); short maxZ = (short) (minZ + blockWidth);
byte skyLightTop = skyLight; byte skyLightTop = skyLight;
byte skyLightBot = RenderDataPointUtil.doesDataPointExist(bottomData) ? RenderDataPointUtil.getLightSky(bottomData) : 0; byte skyLightBot = RenderDataPointUtil.doesDataPointExist(bottomData) ? RenderDataPointUtil.getLightSky(bottomData) : 0;
boolean transparencyEnabled = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled; boolean transparencyEnabled = Config.Client.Advanced.Graphics.Quality.transparency.get() == EDhApiTransparency.COMPLETE;
boolean fakeOceanFloor = Config.Client.Advanced.Graphics.Quality.transparency.get().fakeTransparencyEnabled;
boolean isTransparent = ColorUtil.getAlpha(color) < 255 && transparencyEnabled; boolean isTransparent = ColorUtil.getAlpha(color) < 255 && transparencyEnabled;
boolean overVoid = !RenderDataPointUtil.doesDataPointExist(bottomData); boolean overVoid = !RenderDataPointUtil.doesDataPointExist(bottomData);
@@ -93,24 +100,6 @@ public class ColumnBox
} }
// fake ocean transparency
if (transparencyEnabled && fakeOceanFloor)
{
if (!isTransparent && isTopTransparent && RenderDataPointUtil.doesDataPointExist(topData))
{
skyLightTop = (byte) MathUtil.clamp(0, 15 - (RenderDataPointUtil.getYMax(topData) - minY), 15);
yHeight = (short) (RenderDataPointUtil.getYMax(topData) - minY - 1);
}
else if (isTransparent && !isBottomTransparent && RenderDataPointUtil.doesDataPointExist(bottomData))
{
minY = (short) (minY + yHeight - 1);
yHeight = 1;
}
maxY = (short) (minY + yHeight);
}
//==========================// //==========================//
// add top and bottom faces // // add top and bottom faces //
@@ -123,7 +112,7 @@ public class ColumnBox
&& !isTopTransparent; && !isTopTransparent;
if (!skipTop) if (!skipTop)
{ {
builder.addQuadUp(minX, maxY, minZ, width, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight); builder.addQuadUp(minX, maxY, minZ, blockWidth, ColorUtil.applyShade(color, clientLevelWrapper.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight);
} }
} }
@@ -134,7 +123,7 @@ public class ColumnBox
&& !isBottomTransparent; && !isBottomTransparent;
if (!skipBottom) if (!skipBottom)
{ {
builder.addQuadDown(minX, minY, minZ, width, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight); builder.addQuadDown(minX, minY, minZ, blockWidth, ColorUtil.applyShade(color, clientLevelWrapper.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight);
} }
} }
@@ -146,7 +135,7 @@ public class ColumnBox
// NORTH face // NORTH face
{ {
ColumnArrayView adjCol = adjData[EDhDirection.NORTH.compassIndex]; ColumnRenderView adjCol = adjData[EDhDirection.NORTH.compassIndex];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.NORTH.compassIndex]; boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.NORTH.compassIndex];
// if the adjacent column is null that generally means the adjacent area hasn't been generated yet // if the adjacent column is null that generally means the adjacent area hasn't been generated yet
if (adjCol == null) if (adjCol == null)
@@ -157,23 +146,23 @@ public class ColumnBox
builder.addQuadAdj( builder.addQuadAdj(
EDhDirection.NORTH, EDhDirection.NORTH,
minX, minY, minZ, minX, minY, minZ,
width, yHeight, blockWidth, yHeight,
color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
} }
else else
{ {
makeAdjVerticalQuad( makeAdjVerticalQuad(
builder, phantomArrayCheckout, builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH,
minX, minY, minZ, width, yHeight, minX, minY, minZ, blockWidth, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
} }
} }
// SOUTH face // SOUTH face
{ {
ColumnArrayView adjCol = adjData[EDhDirection.SOUTH.compassIndex]; ColumnRenderView adjCol = adjData[EDhDirection.SOUTH.compassIndex];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.SOUTH.compassIndex]; boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.SOUTH.compassIndex];
if (adjCol == null) if (adjCol == null)
{ {
@@ -182,23 +171,23 @@ public class ColumnBox
builder.addQuadAdj( builder.addQuadAdj(
EDhDirection.SOUTH, EDhDirection.SOUTH,
minX, minY, maxZ, minX, minY, maxZ,
width, yHeight, blockWidth, yHeight,
color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
} }
else else
{ {
makeAdjVerticalQuad( makeAdjVerticalQuad(
builder, phantomArrayCheckout, builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH,
minX, minY, maxZ, width, yHeight, minX, minY, maxZ, blockWidth, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
} }
} }
// WEST face // WEST face
{ {
ColumnArrayView adjCol = adjData[EDhDirection.WEST.compassIndex]; ColumnRenderView adjCol = adjData[EDhDirection.WEST.compassIndex];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.WEST.compassIndex]; boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.WEST.compassIndex];
if (adjCol == null) if (adjCol == null)
{ {
@@ -207,23 +196,23 @@ public class ColumnBox
builder.addQuadAdj( builder.addQuadAdj(
EDhDirection.WEST, EDhDirection.WEST,
minX, minY, minZ, minX, minY, minZ,
width, yHeight, blockWidth, yHeight,
color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
} }
else else
{ {
makeAdjVerticalQuad( makeAdjVerticalQuad(
builder, phantomArrayCheckout, builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST,
minX, minY, minZ, width, yHeight, minX, minY, minZ, blockWidth, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
} }
} }
// EAST face // EAST face
{ {
ColumnArrayView adjCol = adjData[EDhDirection.EAST.compassIndex]; ColumnRenderView adjCol = adjData[EDhDirection.EAST.compassIndex];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.EAST.compassIndex]; boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.EAST.compassIndex];
if (adjCol == null) if (adjCol == null)
{ {
@@ -232,25 +221,25 @@ public class ColumnBox
builder.addQuadAdj( builder.addQuadAdj(
EDhDirection.EAST, EDhDirection.EAST,
maxX, minY, minZ, maxX, minY, minZ,
width, yHeight, blockWidth, yHeight,
color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
} }
else else
{ {
makeAdjVerticalQuad( makeAdjVerticalQuad(
builder, phantomArrayCheckout, builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST,
maxX, minY, minZ, width, yHeight, maxX, minY, minZ, blockWidth, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
} }
} }
} }
private static void makeAdjVerticalQuad( private static void makeAdjVerticalQuad(
LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, IClientLevelWrapper clientLevelWrapper,
@NotNull ColumnArrayView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction, @NotNull ColumnRenderView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction,
short x, short yMin, short z, short horizontalWidth, short ySize, short x, short yMin, short z, short horizontalBlockWidth, short ySize,
int color, byte irisBlockMaterialId, byte blockLight) int color, byte irisBlockMaterialId, byte blockLight)
{ {
// pooled arrays // pooled arrays
@@ -264,12 +253,12 @@ public class ColumnBox
// no adjacent data // // no adjacent data //
//==================// //==================//
color = ColorUtil.applyShade(color, MC_RENDER.getShade(direction)); color = ColorUtil.applyShade(color, clientLevelWrapper.getShade(direction));
if (adjColumnView.size == 0 if (adjColumnView.size == 0
|| RenderDataPointUtil.hasZeroHeight(adjColumnView.get(0))) || RenderDataPointUtil.hasZeroHeight(adjColumnView.get(0)))
{ {
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); builder.addQuadAdj(direction, x, yMin, z, horizontalBlockWidth, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
return; return;
} }
@@ -279,12 +268,12 @@ public class ColumnBox
// determine face visibility/light // // determine face visibility/light //
//=================================// //=================================//
boolean transparencyEnabled = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled; boolean transparencyEnabled = Config.Client.Advanced.Graphics.Quality.transparency.get() == EDhApiTransparency.COMPLETE;
boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && transparencyEnabled; boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && transparencyEnabled;
short yMax = (short) (yMin + ySize); short yMax = (short) (yMin + ySize);
int adjCount = adjColumnView.size(); int adjCount = adjColumnView.size;
// Start with the entire range at max light // Start with the entire range at max light
segments.add(YSegmentUtil.encode(yMin, yMax, LodUtil.MAX_MC_LIGHT)); segments.add(YSegmentUtil.encode(yMin, yMax, LodUtil.MAX_MC_LIGHT));
@@ -321,18 +310,34 @@ public class ColumnBox
if (!adjTransparent) if (!adjTransparent)
{ {
// Adjacent is opaque // Adjacent is opaque
boolean adjacentCoversThis =
!adjacentIsSameDetailLevel
&& RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY
&&
(
(x == 0 && direction == EDhDirection.WEST)
|| (z == 0 && direction == EDhDirection.NORTH)
|| (x == 256 && direction == EDhDirection.EAST)
|| (z == 256 && direction == EDhDirection.SOUTH)
);
lightToApply = adjacentCoversThis ? adjSkyLight : SKYLIGHT_COVERED; // The following logic is done to provide a little bit of overdraw to
// prevent holes when low detail LODs are replaced by higher-detail ones
// when moving.
// If not done higher quality LODs can cause holes due to not
// covering the whole face like the lower detail LODs they replaced,
// while still culling most LODs that are covered by other blocks.
boolean onBorder =
(direction == EDhDirection.WEST && x == 0)
|| (direction == EDhDirection.NORTH && z == 0)
|| (direction == EDhDirection.EAST && x == ((horizontalBlockWidth) * (ColumnRenderSource.WIDTH)))
|| (direction == EDhDirection.SOUTH && z == ((horizontalBlockWidth) * (ColumnRenderSource.WIDTH)));
boolean isLit =
RenderDataPointUtil.getLightSky(adjPoint) != LodUtil.MIN_MC_LIGHT
|| RenderDataPointUtil.getLightBlock(adjPoint) != LodUtil.MIN_MC_LIGHT;
// render the face if...
boolean useAdjLighting =
// we're on the border... (holes can only happen on LOD borders since faces inside an LOD will always be the same detail level)
onBorder
// ...this face has some sort of lighting... (0 light generally means the face is covered by other blocks)
&& isLit
// ...and is above the culling height
&& RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY;
lightToApply = useAdjLighting ? adjSkyLight : SKYLIGHT_COVERED;
} }
else else
{ {
@@ -342,13 +347,24 @@ public class ColumnBox
// Apply light to the range [adjMinY, adjMaxY) // Apply light to the range [adjMinY, adjMaxY)
applyLightToRange(segments, newSegments, adjMinY, adjMaxY, lightToApply); applyLightToRangeAndPopulateNewSgements(segments, newSegments, adjMinY, adjMaxY, lightToApply);
{
// swap references so we can use the newly populated segments
LongArrayList temp = segments;
segments = newSegments;
newSegments = temp;
}
// Fill overhang area [adjMaxY, adjAboveMinY) with adjSkyLight // Fill overhang area [adjMaxY, adjAboveMinY) with adjSkyLight
short adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint); short adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint);
if (adjMaxY < adjAboveMinY) if (adjMaxY < adjAboveMinY)
{ {
applyLightToRange(segments, newSegments, adjMaxY, adjAboveMinY, adjSkyLight); applyLightToRangeAndPopulateNewSgements(segments, newSegments, adjMaxY, adjAboveMinY, adjSkyLight);
{
LongArrayList temp = segments;
segments = newSegments;
newSegments = temp;
}
} }
} }
@@ -364,7 +380,7 @@ public class ColumnBox
long segment = segments.getLong(i); long segment = segments.getLong(i);
tryAddVerticalFaceWithSkyLightToBuilder( tryAddVerticalFaceWithSkyLightToBuilder(
builder, direction, builder, direction,
x, z, horizontalWidth, x, z, horizontalBlockWidth,
color, irisBlockMaterialId, blockLight, color, irisBlockMaterialId, blockLight,
YSegmentUtil.getSkyLight(segment), inputTransparent, YSegmentUtil.getEndY(segment), YSegmentUtil.getStartY(segment) YSegmentUtil.getSkyLight(segment), inputTransparent, YSegmentUtil.getEndY(segment), YSegmentUtil.getStartY(segment)
); );
@@ -374,10 +390,11 @@ public class ColumnBox
/** /**
* Apply the new light value over the given y range, * Apply the new light value over the given y range,
* splitting segments as needed * splitting segments as needed
* and putting the new segments into "newSegments"
* <p> * <p>
* source: claude.ai * source: claude.ai
*/ */
private static void applyLightToRange( private static void applyLightToRangeAndPopulateNewSgements(
LongArrayList segments, LongArrayList newSegments, LongArrayList segments, LongArrayList newSegments,
short rangeStart, short rangeEnd, short rangeStart, short rangeEnd,
byte newLight) byte newLight)
@@ -420,9 +437,6 @@ public class ColumnBox
newSegments.add(YSegmentUtil.encode(rangeEnd, endY, skyLight)); newSegments.add(YSegmentUtil.encode(rangeEnd, endY, skyLight));
} }
} }
segments.clear();
segments.addAll(newSegments);
} }
private static void tryAddVerticalFaceWithSkyLightToBuilder( private static void tryAddVerticalFaceWithSkyLightToBuilder(
@@ -27,15 +27,14 @@ import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.coreapi.util.ColorUtil;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -56,26 +55,6 @@ public class ColumnRenderBufferBuilder
// vbo building // // vbo building //
//==============// //==============//
/** @link adjData should be null for adjacent sections that cross detail level boundaries */
public static CompletableFuture<LodBufferContainer> uploadBuffersAsync(
IDhClientLevel clientLevel,
long pos,
LodQuadBuilder quadBuilder
)
{
DhBlockPos minBlockPos = new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getLevelWrapper().getMinHeight(), DhSectionPos.getMinCornerBlockZ(pos));
LodBufferContainer bufferContainer = new LodBufferContainer(pos, minBlockPos);
CompletableFuture<LodBufferContainer> uploadFuture = bufferContainer.makeAndUploadBuffersAsync(quadBuilder);
uploadFuture.whenComplete((uploadedBuffer, exception) ->
{
// clean up if not uploaded
if (uploadedBuffer != null && !uploadedBuffer.buffersUploaded)
{
uploadedBuffer.close();
}
});
return uploadFuture;
}
public static void makeLodRenderData( public static void makeLodRenderData(
LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel, LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel,
ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel) ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel)
@@ -108,16 +87,29 @@ public class ColumnRenderBufferBuilder
//===================// //===================//
// pooled arrays for ColumnBox use // pooled arrays for ColumnBox use
try (PhantomArrayListCheckout phantomArrayCheckout = ARRAY_LIST_POOL.checkoutArrays(0, 0, 2)) try (PhantomArrayListCheckout phantomArrayCheckout = ARRAY_LIST_POOL.checkoutLongArrays(2);
ColumnRenderView columnRenderData = ColumnRenderView.getPooled();
ColumnRenderView northAdjView = ColumnRenderView.getPooled();
ColumnRenderView southAdjView = ColumnRenderView.getPooled();
ColumnRenderView eastAdjView = ColumnRenderView.getPooled();
ColumnRenderView westAdjView = ColumnRenderView.getPooled())
{ {
ColumnRenderView[] adjColumnViews = new ColumnRenderView[EDhDirection.CARDINAL_COMPASS.length];
adjColumnViews[EDhDirection.NORTH.compassIndex] = northAdjView;
adjColumnViews[EDhDirection.SOUTH.compassIndex] = southAdjView;
adjColumnViews[EDhDirection.EAST.compassIndex] = eastAdjView;
adjColumnViews[EDhDirection.WEST.compassIndex] = westAdjView;
byte thisDetailLevel = renderSource.getDataDetailLevel(); byte thisDetailLevel = renderSource.getDataDetailLevel();
for (int relX = 0; relX < ColumnRenderSource.WIDTH; relX++) for (int relX = 0; relX < ColumnRenderSource.WIDTH; relX++)
{ {
for (int relZ = 0; relZ < ColumnRenderSource.WIDTH; relZ++) for (int relZ = 0; relZ < ColumnRenderSource.WIDTH; relZ++)
{ {
// ignore empty/null columns renderSource.populateColumnView(columnRenderData, relX, relZ);
ColumnArrayView columnRenderData = renderSource.getVerticalDataPointView(relX, relZ);
if (columnRenderData.size() == 0 // ignore empty columns
if (columnRenderData.size == 0
|| !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0)) || !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0))
|| RenderDataPointUtil.hasZeroHeight(columnRenderData.get(0))) || RenderDataPointUtil.hasZeroHeight(columnRenderData.get(0)))
{ {
@@ -125,7 +117,6 @@ public class ColumnRenderBufferBuilder
} }
//=============// //=============//
// debug limit // // debug limit //
//=============// //=============//
@@ -152,7 +143,12 @@ public class ColumnRenderBufferBuilder
// get adjacent render data columns // // get adjacent render data columns //
//==================================// //==================================//
ColumnArrayView[] adjColumnViews = new ColumnArrayView[EDhDirection.CARDINAL_COMPASS.length]; // clear the old data so we can handle if one of the adjacent columns is missing/empty
adjColumnViews[EDhDirection.NORTH.compassIndex].clear();
adjColumnViews[EDhDirection.SOUTH.compassIndex].clear();
adjColumnViews[EDhDirection.EAST.compassIndex].clear();
adjColumnViews[EDhDirection.WEST.compassIndex].clear();
for (EDhDirection direction : EDhDirection.CARDINAL_COMPASS) for (EDhDirection direction : EDhDirection.CARDINAL_COMPASS)
{ {
try try
@@ -160,8 +156,8 @@ public class ColumnRenderBufferBuilder
int xAdj = relX + direction.normal.x; int xAdj = relX + direction.normal.x;
int zAdj = relZ + direction.normal.z; int zAdj = relZ + direction.normal.z;
boolean isCrossRenderSourceBoundary = boolean isCrossRenderSourceBoundary =
(xAdj < 0 || xAdj >= ColumnRenderSource.WIDTH) || (xAdj < 0 || xAdj >= ColumnRenderSource.WIDTH)
(zAdj < 0 || zAdj >= ColumnRenderSource.WIDTH); || (zAdj < 0 || zAdj >= ColumnRenderSource.WIDTH);
ColumnRenderSource adjRenderSource; ColumnRenderSource adjRenderSource;
byte adjDetailLevel; byte adjDetailLevel;
@@ -230,7 +226,7 @@ public class ColumnRenderBufferBuilder
LodUtil.assertNotReach("Mismatch between adjacent detail level ["+adjDetailLevel+"] and this render source's detail level ["+thisDetailLevel+"]. Detail levels should be adj >= this."); LodUtil.assertNotReach("Mismatch between adjacent detail level ["+adjDetailLevel+"] and this render source's detail level ["+thisDetailLevel+"]. Detail levels should be adj >= this.");
} }
adjColumnViews[direction.compassIndex] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj); adjRenderSource.populateColumnView(adjColumnViews[direction.compassIndex], xAdj, zAdj);
} }
catch (RuntimeException e) catch (RuntimeException e)
{ {
@@ -244,9 +240,7 @@ public class ColumnRenderBufferBuilder
// build this render column // // build this render column //
//==========================// //==========================//
ColumnRenderSource.DebugSourceFlag debugSourceFlag = renderSource.debugGetFlag(relX, relZ); for (int i = 0; i < columnRenderData.size; i++)
for (int i = 0; i < columnRenderData.size(); i++)
{ {
// can be uncommented to limit which vertical LOD is generated // can be uncommented to limit which vertical LOD is generated
if (Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugEnable.get()) if (Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugEnable.get())
@@ -269,16 +263,15 @@ public class ColumnRenderBufferBuilder
} }
long topDataPoint = (i - 1) >= 0 ? columnRenderData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA; long topDataPoint = (i - 1) >= 0 ? columnRenderData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA;
long bottomDataPoint = (i + 1) < columnRenderData.size() ? columnRenderData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA; long bottomDataPoint = (i + 1) < columnRenderData.size ? columnRenderData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA;
addRenderDataPointToBuilder( addRenderDataPointToBuilder(
clientLevel, phantomArrayCheckout, clientLevel, phantomArrayCheckout,
data, topDataPoint, bottomDataPoint, data, topDataPoint, bottomDataPoint,
adjColumnViews, isSameDetailLevel, adjColumnViews, isSameDetailLevel,
thisDetailLevel, relX, relZ, thisDetailLevel, relX, relZ,
quadBuilder, debugSourceFlag); quadBuilder);
} }
}// for z }// for z
}// for x }// for x
}// phantom checkout }// phantom checkout
@@ -288,9 +281,9 @@ public class ColumnRenderBufferBuilder
private static void addRenderDataPointToBuilder( private static void addRenderDataPointToBuilder(
IDhClientLevel clientLevel, PhantomArrayListCheckout phantomArrayCheckout, IDhClientLevel clientLevel, PhantomArrayListCheckout phantomArrayCheckout,
long renderData, long topRenderData, long bottomRenderData, long renderData, long topRenderData, long bottomRenderData,
ColumnArrayView[] adjColumnViews, boolean[] isSameDetailLevel, ColumnRenderView[] adjColumnViews, boolean[] isSameDetailLevel,
byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ, byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ,
LodQuadBuilder quadBuilder, ColumnRenderSource.DebugSourceFlag debugSource) LodQuadBuilder quadBuilder)
{ {
long sectionPos = DhSectionPos.encode(detailLevel, renderSourceOffsetPosX, renderSourceOffsetPosZ); long sectionPos = DhSectionPos.encode(detailLevel, renderSourceOffsetPosX, renderSourceOffsetPosZ);
@@ -315,13 +308,13 @@ public class ColumnRenderBufferBuilder
int color; int color;
boolean fullBright = false; boolean fullBright = false;
EDhApiDebugRendering debugging = Config.Client.Advanced.Debugging.debugRendering.get(); EDhApiDebugRendering debugging = Config.Client.Advanced.Debugging.debugRenderingColors.get();
switch (debugging) switch (debugging)
{ {
case OFF: case OFF:
{ {
float saturationMultiplier = Config.Client.Advanced.Graphics.Quality.saturationMultiplier.get().floatValue(); float saturationMultiplier = Config.Client.Advanced.Graphics.Quality.saturationMultiplier.get();
float brightnessMultiplier = Config.Client.Advanced.Graphics.Quality.brightnessMultiplier.get().floatValue(); float brightnessMultiplier = Config.Client.Advanced.Graphics.Quality.brightnessMultiplier.get();
if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0) if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0)
{ {
color = RenderDataPointUtil.getColor(renderData); color = RenderDataPointUtil.getColor(renderData);
@@ -407,12 +400,6 @@ public class ColumnRenderBufferBuilder
fullBright = true; fullBright = true;
break; break;
} }
case SHOW_RENDER_SOURCE_FLAG:
{
color = debugSource == null ? ColorUtil.RED : debugSource.color;
fullBright = true;
break;
}
default: default:
throw new IllegalArgumentException("Unknown debug mode: " + debugging); throw new IllegalArgumentException("Unknown debug mode: " + debugging);
} }
@@ -0,0 +1,89 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class IndexBufferBuilder
{
//==========//
// building //
//==========//
//region
public static ByteBuffer createBuffer(int quadCount)
{
int indexCount = quadCount * 6; // 2 triangles per quad
ByteBuffer buffer = ByteBuffer.allocateDirect(indexCount * Integer.BYTES);
buffer.order(ByteOrder.nativeOrder());
buildBufferInt(quadCount, buffer);
return buffer;
}
private static void buildBufferByte(int quadCount, ByteBuffer buffer)
{
for (int i = 0; i < quadCount; i++)
{
int vIndex = i * 4;
// First triangle
buffer.put((byte) (vIndex));
buffer.put((byte) (vIndex + 1));
buffer.put((byte) (vIndex + 2));
// Second triangle
buffer.put((byte) (vIndex + 2));
buffer.put((byte) (vIndex + 3));
buffer.put((byte) (vIndex));
}
if (buffer.hasRemaining())
{
throw new IllegalStateException("QuadElementBuffer is not full somehow after building");
}
buffer.rewind();
}
private static void buildBufferShort(int quadCount, ByteBuffer buffer)
{
for (int i = 0; i < quadCount; i++)
{
int vIndex = i * 4;
// First triangle
buffer.putShort((short) (vIndex));
buffer.putShort((short) (vIndex + 1));
buffer.putShort((short) (vIndex + 2));
// Second triangle
buffer.putShort((short) (vIndex + 2));
buffer.putShort((short) (vIndex + 3));
buffer.putShort((short) (vIndex));
}
if (buffer.hasRemaining())
{
throw new IllegalStateException("QuadElementBuffer is not full somehow after building");
}
buffer.rewind();
}
private static void buildBufferInt(int quadCount, ByteBuffer buffer)
{
for (int i = 0; i < quadCount; i++)
{
int vIndex = i * 4;
// First triangle
buffer.putInt(vIndex);
buffer.putInt(vIndex + 1);
buffer.putInt(vIndex + 2);
// Second triangle
buffer.putInt(vIndex + 2);
buffer.putInt(vIndex + 3);
buffer.putInt(vIndex);
}
if (buffer.hasRemaining())
{
throw new IllegalStateException("QuadElementBuffer is not full somehow after building");
}
buffer.rewind();
}
//endregion
}
@@ -19,15 +19,19 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer; import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.util.objects.StatsMap; import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper;
import org.lwjgl.system.MemoryUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.IVertexBufferWrapper;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
@@ -42,12 +46,8 @@ public class LodBufferContainer implements AutoCloseable
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** number of bytes a single quad takes */ private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 4; private static final AbstractDhRenderApiDefinition RENDER_DEF = SingletonInjector.INSTANCE.get(AbstractDhRenderApiDefinition.class);
/** how big a single VBO can be in bytes */
public static final int MAX_VBO_BYTE_SIZE = 10 * 1024 * 1024; // 10 MB
public static final int MAX_QUADS_PER_BUFFER = MAX_VBO_BYTE_SIZE / QUADS_BYTE_SIZE;
public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
/** the position closest to minimum X/Z infinity and the level's lowest Y */ /** the position closest to minimum X/Z infinity and the level's lowest Y */
@@ -56,117 +56,186 @@ public class LodBufferContainer implements AutoCloseable
public boolean buffersUploaded = false; public boolean buffersUploaded = false;
public GLVertexBuffer[] vbos; public IVertexBufferWrapper[] vboOpaqueWrappers;
public GLVertexBuffer[] vbosTransparent; public IVertexBufferWrapper[] vboTransparentWrappers;
private CompletableFuture<LodBufferContainer> uploadFuture = null; public ILodContainerUniformBufferWrapper uniformContainer = WRAPPER_FACTORY.createLodContainerUniformWrapper();
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
//region
public LodBufferContainer(long pos, DhBlockPos minCornerBlockPos) private LodBufferContainer(long pos, DhBlockPos minCornerBlockPos)
{ {
this.pos = pos; this.pos = pos;
this.minCornerBlockPos = minCornerBlockPos; this.minCornerBlockPos = minCornerBlockPos;
this.vbos = new GLVertexBuffer[0]; this.vboOpaqueWrappers = new IVertexBufferWrapper[0];
this.vbosTransparent = new GLVertexBuffer[0]; this.vboTransparentWrappers = new IVertexBufferWrapper[0];
} }
//endregion
//==================// //==================//
// buffer uploading // // buffer uploading //
//==================// //==================//
//region
/** Should be run on a DH thread. */ /** Should be run on a DH thread. */
public synchronized CompletableFuture<LodBufferContainer> makeAndUploadBuffersAsync(LodQuadBuilder builder) public static CompletableFuture<LodBufferContainer> tryMakeAndUploadBuffersAsync(
long pos, IDhClientLevel clientLevel,
ArrayList<ByteBuffer> opaqueBuffers,
ArrayList<ByteBuffer> transparentBuffers
)
{ {
// separate variable to prevent race condition when checking null
CompletableFuture<LodBufferContainer> future = this.uploadFuture;
if (future != null)
{
// upload already in process
return future;
}
// new upload needed // new upload needed
future = new CompletableFuture<>(); CompletableFuture<LodBufferContainer> future = new CompletableFuture<>();
this.uploadFuture = future;
// make the buffers //================//
ArrayList<ByteBuffer> opaqueBuffers = builder.makeOpaqueVertexBuffers(); // create buffers //
ArrayList<ByteBuffer> transparentBuffers = builder.makeTransparentVertexBuffers(); //================//
//region
this.vbos = resizeBuffer(this.vbos, opaqueBuffers.size()); DhBlockPos minCornerBlockPos = new DhBlockPos(
this.vbosTransparent = resizeBuffer(this.vbosTransparent, transparentBuffers.size()); DhSectionPos.getMinCornerBlockX(pos),
clientLevel.getLevelWrapper().getMinHeight(),
DhSectionPos.getMinCornerBlockZ(pos));
LodBufferContainer bufferContainer = new LodBufferContainer(pos, minCornerBlockPos);
// update arrays to contain buffers
bufferContainer.vboOpaqueWrappers = resizeWrapperArray(bufferContainer.vboOpaqueWrappers, opaqueBuffers.size());
bufferContainer.vboTransparentWrappers = resizeWrapperArray(bufferContainer.vboTransparentWrappers, transparentBuffers.size());
// create CPU index buffers if needed.
// Mac requires separate IBO objects for each VBO when using OpenGL,
// all other OS's can share a single IBO for quicker loading times
boolean useSingleIbo = RENDER_DEF.useSingleIbo();
@Nullable ArrayList<ByteBuffer> opaqueIndexBuffers = useSingleIbo ? null : bufferContainer.createIndexBuffers(opaqueBuffers);
@Nullable ArrayList<ByteBuffer> transparentIndexBuffers = useSingleIbo ? null : bufferContainer.createIndexBuffers(transparentBuffers);
//endregion
// upload on MC's render thread
GLProxy.queueRunningOnRenderThread(() -> //=============//
// create VBOs //
//=============//
//region
CompletableFuture<Void> createFuture = new CompletableFuture<Void>();
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Setup", () ->
{ {
try try
{ {
// skip this event if requested // skip this event if requested
if (Thread.interrupted() if (Thread.interrupted()
|| this.uploadFuture.isCancelled()) || future.isCancelled())
{ {
throw new InterruptedException(); throw new InterruptedException();
} }
EDhApiGpuUploadMethod gpuUploadMethod = GLProxy.getInstance().getGpuUploadMethod(); createBufferWrappers(bufferContainer.vboOpaqueWrappers, opaqueBuffers);
createBufferWrappers(bufferContainer.vboTransparentWrappers, transparentBuffers);
// upload on the render thread createFuture.complete(null);
uploadBuffersDirect(this.vbos, opaqueBuffers, gpuUploadMethod);
uploadBuffersDirect(this.vbosTransparent, transparentBuffers, gpuUploadMethod);
this.buffersUploaded = true;
// success
this.uploadFuture.complete(this);
this.uploadFuture = null;
}
catch (InterruptedException ignore)
{
this.uploadFuture.complete(this);
this.uploadFuture = null;
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.error("Unexpected issue uploading buffer ["+this.minCornerBlockPos +"], error: ["+e.getMessage()+"].", e); if (!ExceptionUtil.isShutdownException(e))
this.uploadFuture.completeExceptionally(e);
this.uploadFuture = null;
}
finally
{ {
// all the buffers must be manually freed to prevent memory leaks LOGGER.error("Unexpected issue creating buffers for pos: ["+DhSectionPos.toString(bufferContainer.pos)+"], error: ["+e.getMessage()+"].", e);
for (ByteBuffer buffer : opaqueBuffers)
{
MemoryUtil.memFree(buffer);
} }
for (ByteBuffer buffer : transparentBuffers) bufferContainer.close();
{ createFuture.completeExceptionally(e);
MemoryUtil.memFree(buffer);
}
} }
}); });
//endregion
//====================//
// upload VBOs to GPU //
//====================//
//region
createFuture.exceptionally((Throwable e) ->
{
// create VBOs failed //
if (!ExceptionUtil.isShutdownException(e))
{
LOGGER.error("Unexpected issue creating buffer [" + bufferContainer.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e);
}
bufferContainer.close();
future.completeExceptionally(e);
return null;
});
createFuture.thenRun(() ->
{
CompletableFuture<Void> opaqueFuture = uploadBuffersAsync(future, bufferContainer.vboOpaqueWrappers, opaqueBuffers, opaqueIndexBuffers);
CompletableFuture<Void> transparentFuture = uploadBuffersAsync(future, bufferContainer.vboTransparentWrappers, transparentBuffers, transparentIndexBuffers);
CompletableFuture<Void> uploadFuture = CompletableFuture.allOf(opaqueFuture, transparentFuture);
uploadFuture.exceptionally((Throwable e) ->
{
// upload failed //
if (!ExceptionUtil.isShutdownException(e))
{
LOGGER.error("Unexpected issue uploading buffer [" + bufferContainer.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e);
}
bufferContainer.close();
future.completeExceptionally(e);
return null;
});
uploadFuture.thenRun(() ->
{
// upload success //
bufferContainer.buffersUploaded = true;
future.complete(bufferContainer);
});
});
//endregion
return future; return future;
} }
private static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
private ArrayList<ByteBuffer> createIndexBuffers(ArrayList<ByteBuffer> vertexBuffers)
{
ArrayList<ByteBuffer> indexBuffers = new ArrayList<>();
for (int i = 0; i < vertexBuffers.size(); i++)
{
ByteBuffer buffer = vertexBuffers.get(i);
int size = buffer.limit() - buffer.position();
int maxVertexCount = size / LodQuadBuilder.BYTES_PER_VERTEX;
int quadCount = (maxVertexCount / 4);
ByteBuffer indexBuffer = IndexBufferBuilder.createBuffer(quadCount);
indexBuffers.add(indexBuffer);
}
return indexBuffers;
}
private static IVertexBufferWrapper[] resizeWrapperArray(IVertexBufferWrapper[] vbos, int newSize)
{ {
if (vbos.length == newSize) if (vbos.length == newSize)
{ {
return vbos; return vbos;
} }
GLVertexBuffer[] newVbos = new GLVertexBuffer[newSize]; IVertexBufferWrapper[] newVbos = new IVertexBufferWrapper[newSize];
System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize)); System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize));
if (newSize < vbos.length) if (newSize < vbos.length)
{ {
@@ -180,109 +249,188 @@ public class LodBufferContainer implements AutoCloseable
} }
return newVbos; return newVbos;
} }
private static void uploadBuffersDirect(
GLVertexBuffer[] vbos, ArrayList<ByteBuffer> byteBuffers, private static void createBufferWrappers(IVertexBufferWrapper[] vboWrappers, ArrayList<ByteBuffer> vertexBuffers)
EDhApiGpuUploadMethod uploadMethod) throws InterruptedException
{ {
for (int i = 0; i < vertexBuffers.size(); i++)
{
if (i >= vboWrappers.length)
{
throw new RuntimeException("Too many vertex buffers!!");
}
if (vboWrappers[i] == null)
{
vboWrappers[i] = WRAPPER_FACTORY.createVboWrapper("distantHorizons:TerrainRenderer");
}
}
}
/** Index buffers should be null if {@link AbstractDhRenderApiDefinition#useSingleIbo()} returns true. */
private static CompletableFuture<Void> uploadBuffersAsync(
CompletableFuture<LodBufferContainer> parentFuture,
IVertexBufferWrapper[] vboWrappers,
ArrayList<ByteBuffer> vertexBuffers, @Nullable ArrayList<ByteBuffer> indexBuffers
)
{
ArrayList<CompletableFuture<Void>> uploadFutureList = new ArrayList<>();
int vboIndex = 0; int vboIndex = 0;
for (int i = 0; i < byteBuffers.size(); i++) for (int i = 0; i < vertexBuffers.size(); i++)
{ {
if (vboIndex >= vbos.length) if (vboIndex >= vboWrappers.length)
{ {
throw new RuntimeException("Too many vertex buffers!!"); throw new RuntimeException("Too many vertex buffers!!");
} }
// get or create the VBO
if (vbos[vboIndex] == null) // final variables for use in lambdas //
final IVertexBufferWrapper finalVboWrapper = vboWrappers[vboIndex];
final ByteBuffer finalVertexBuffer = vertexBuffers.get(vboIndex);
// index buffers are optional
@Nullable final ByteBuffer finalIndexBuffer = (indexBuffers != null) ? indexBuffers.get(vboIndex) : null;
final int finalVertexCount = vertexByteBufferToVertexCount(finalVertexBuffer);
//===============//
// vertex upload //
//===============//
//region
CompletableFuture<Void> vertexUploadFuture = new CompletableFuture<>();
uploadFutureList.add(vertexUploadFuture);
final StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer VBO Upload", () ->
{ {
vbos[vboIndex] = new GLVertexBuffer(uploadMethod.useBufferStorage);
}
GLVertexBuffer vbo = vbos[vboIndex];
ByteBuffer buffer = byteBuffers.get(i);
int size = buffer.limit() - buffer.position();
try try
{ {
vbo.bind(); // skip this event if requested
vbo.uploadBuffer(buffer, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), uploadMethod, FULL_SIZED_BUFFER); if (Thread.interrupted()
|| parentFuture.isCancelled())
{
throw new InterruptedException();
}
finalVboWrapper.uploadVertexBuffer(finalVertexBuffer, finalVertexCount);
vertexUploadFuture.complete(null);
} }
catch (Exception e) catch (Exception e)
{ {
vbos[vboIndex] = null; LOGGER.error("Failed to upload buffer. Error: [" + e.getMessage() + "].", e);
vbo.close(); vertexUploadFuture.completeExceptionally(e);
LOGGER.error("Failed to upload buffer. Error: ["+e.getMessage()+"].", e);
} }
});
//endregion
//==============//
// index upload //
//==============//
//region
if (finalIndexBuffer != null)
{
CompletableFuture<Void> indexUploadFuture = new CompletableFuture<>();
uploadFutureList.add(indexUploadFuture);
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer IBO Upload", () ->
{
try
{
// skip this event if requested
if (Thread.interrupted()
|| parentFuture.isCancelled())
{
throw new InterruptedException();
}
finalVboWrapper.uploadIndexBuffer(finalIndexBuffer, finalVertexCount);
indexUploadFuture.complete(null);
}
catch (Exception e)
{
finalVboWrapper.close();
indexUploadFuture.completeExceptionally(e);
}
});
}
//endregion
vboIndex++; vboIndex++;
} }
if (vboIndex < vbos.length) if (vboIndex < vboWrappers.length)
{ {
throw new RuntimeException("Too few vertex buffers!!"); throw new RuntimeException("Too few vertex buffers!!");
} }
// merge futures //
CompletableFuture<?>[] futureArray = new CompletableFuture[uploadFutureList.size()];
for (int i = 0; i < uploadFutureList.size(); i++)
{
futureArray[i] = uploadFutureList.get(i);
} }
return CompletableFuture.allOf(futureArray);
}
//endregion
//================// //================//
// helper methods // // helper methods //
//================// //================//
//region
private static int vertexByteBufferToVertexCount(ByteBuffer buffer)
{
int size = buffer.limit() - buffer.position();
int vertexCount = size / LodQuadBuilder.BYTES_PER_VERTEX;
return vertexCount;
}
/** can be used when debugging */ /** can be used when debugging */
public boolean hasNonNullVbos() { return this.vbos != null || this.vbosTransparent != null; } public boolean hasNonNullVbos() { return this.vboOpaqueWrappers != null || this.vboTransparentWrappers != null; }
/** can be used when debugging */ /** can be used when debugging */
public int vboBufferCount() public int vboBufferCount()
{ {
int count = 0; int count = 0;
if (this.vbos != null) if (this.vboOpaqueWrappers != null)
{ {
count += this.vbos.length; count += this.vboOpaqueWrappers.length;
} }
if (this.vbosTransparent != null) if (this.vboTransparentWrappers != null)
{ {
count += this.vbosTransparent.length; count += this.vboTransparentWrappers.length;
} }
return count; return count;
} }
public boolean uploadInProgress() { return this.uploadFuture != null; } //endregion
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RenderBuffers");
statsMap.incStat("SimpleRenderBuffers");
for (GLVertexBuffer vertexBuffer : vbos)
{
if (vertexBuffer != null)
{
statsMap.incStat("VBOs");
if (vertexBuffer.getSize() == FULL_SIZED_BUFFER)
{
statsMap.incStat("FullsizedVBOs");
}
if (vertexBuffer.getSize() == 0)
{
GLProxy.LOGGER.warn("VBO with size 0");
}
statsMap.incBytesStat("TotalUsage", vertexBuffer.getSize());
}
}
}
//================// //================//
// base overrides // // base overrides //
//================// //================//
//region
/** /**
* This method is called when object is no longer in use. * This method is called when object is no longer in use.
@@ -295,21 +443,33 @@ public class LodBufferContainer implements AutoCloseable
{ {
this.buffersUploaded = false; this.buffersUploaded = false;
for (GLVertexBuffer buffer : this.vbos) RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Close", () ->
{ {
tryCloseBufferWrapperArray(this.vboOpaqueWrappers);
tryCloseBufferWrapperArray(this.vboTransparentWrappers);
this.uniformContainer.close();
});
}
private static void tryCloseBufferWrapperArray(@Nullable IVertexBufferWrapper[] bufferWrappers)
{
if (bufferWrappers != null)
{
for (int i = 0; i < bufferWrappers.length; i++)
{
IVertexBufferWrapper buffer = bufferWrappers[i];
bufferWrappers[i] = null;
if (buffer != null) if (buffer != null)
{ {
buffer.destroyAsync(); buffer.close();
}
}
} }
} }
for (GLVertexBuffer buffer : this.vbosTransparent) //endregion
{
if (buffer != null)
{
buffer.destroyAsync();
}
}
}
} }
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*; import java.util.*;
import com.seibel.distanthorizons.api.enums.config.EDhApiGrassSideRendering; import com.seibel.distanthorizons.api.enums.config.EDhApiGrassSideRendering;
@@ -30,36 +31,29 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.coreapi.util.ColorUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.lwjgl.system.MemoryUtil;
/** /**
* Used to create the quads before they are converted to render-able buffers. <br><br> * Used to create the quads before they are converted to render-able buffers. <br><br>
* *
* Note: the magic number 6 you see throughout this method represents the number of sides on a cube. * Note: the magic number 6 you see throughout this method represents the number of sides on a cube.
*/ */
public class LodQuadBuilder public class LodQuadBuilder implements AutoCloseable
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
@SuppressWarnings("unchecked") /** ThreadLocal is the simplest way to allow each LOD loading thread to have their own builder */
private final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6]; private static final ThreadLocal<LodQuadBuilder> THREAD_LOCAL = ThreadLocal.withInitial(LodQuadBuilder::new);
@SuppressWarnings("unchecked")
private final ArrayList<BufferQuad>[] transparentQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
private final boolean doTransparency;
private final IClientLevelWrapper clientLevelWrapper;
private final EDhApiDebugRendering debugRenderingMode;
private final EDhApiGrassSideRendering grassSideRenderingMode;
/** the number of bytes for a single vertex */
public static final int BYTES_PER_VERTEX = 16;
public static final int BYTES_PER_QUAD = BYTES_PER_VERTEX * 4;
public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][] public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][]
//region
{ {
// X,Z // // X,Z //
{ // UP { // UP
@@ -107,6 +101,27 @@ public class LodQuadBuilder
{0, 0}, // 3 {0, 0}, // 3
}, },
}; };
//endregion
@SuppressWarnings("unchecked")
private final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
@SuppressWarnings("unchecked")
private final ArrayList<BufferQuad>[] transparentQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
/**
* Caching the BufferQuad objects reduces overhead slightly. <br>
* Caching is handled per builder (vs globally in {@link BufferQuad} itself)
* to prevent concurrency overhead.
*/
private final ArrayList<BufferQuad> bufferQuadCacheList = new ArrayList<>();
private boolean doTransparency;
private IClientLevelWrapper clientLevelWrapper;
private EDhApiDebugRendering debugRenderingMode;
private EDhApiGrassSideRendering grassSideRenderingMode;
private int premergeCount = 0; private int premergeCount = 0;
@@ -115,33 +130,48 @@ public class LodQuadBuilder
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
public LodQuadBuilder(boolean doTransparency, IClientLevelWrapper clientLevelWrapper) private LodQuadBuilder()
{ {
this.doTransparency = doTransparency;
for (int i = 0; i < 6; i++) for (int i = 0; i < 6; i++)
{ {
this.opaqueQuads[i] = new ArrayList<>(); this.opaqueQuads[i] = new ArrayList<>();
this.transparentQuads[i] = new ArrayList<>(); this.transparentQuads[i] = new ArrayList<>();
} }
}
public static LodQuadBuilder getBuilder(boolean doTransparency, IClientLevelWrapper clientLevelWrapper)
{
LodQuadBuilder builder = THREAD_LOCAL.get();
builder.set(doTransparency, clientLevelWrapper);
return builder;
}
private void set(boolean doTransparency, IClientLevelWrapper clientLevelWrapper)
{
this.doTransparency = doTransparency;
this.clientLevelWrapper = clientLevelWrapper; this.clientLevelWrapper = clientLevelWrapper;
this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRendering.get(); this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRenderingColors.get();
this.grassSideRenderingMode = Config.Client.Advanced.Graphics.Quality.grassSideRendering.get(); this.grassSideRenderingMode = Config.Client.Advanced.Graphics.Quality.grassSideRendering.get();
this.premergeCount = 0;
} }
//endregion
//===========// //===========//
// add quads // // add quads //
//===========// //===========//
//region
public void addQuadAdj( public void addQuadAdj(
EDhDirection dir, EDhDirection dir,
short x, short y, short z, short x, short y, short z,
short widthEastWest, short widthNorthSouthOrUpDown, short width, short height,
int color, byte irisBlockMaterialId, byte skyLight, byte blockLight) int color, byte irisBlockMaterialId, byte skyLight, byte blockLight)
{ {
if (dir == EDhDirection.DOWN) if (dir == EDhDirection.DOWN)
@@ -160,7 +190,8 @@ public class LodQuadBuilder
quadList = this.opaqueQuads[dir.ordinal()]; quadList = this.opaqueQuads[dir.ordinal()];
} }
BufferQuad quad = new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skyLight, blockLight, dir); BufferQuad quad = this.getOrCreateBufferQuad();
quad.set(x, y, z, width, height, color, irisBlockMaterialId, skyLight, blockLight, dir);
if (!quadList.isEmpty() if (!quadList.isEmpty()
&& ( && (
quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
@@ -175,32 +206,37 @@ public class LodQuadBuilder
} }
// XZ // XZ
public void addQuadUp(short minX, short maxY, short minZ, short widthEastWest, short widthNorthSouthOrUpDown, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) // TODO argument names are wrong public void addQuadUp(short minX, short maxY, short minZ, short blockWidth, int color, byte irisBlockMaterialId, byte skylight, byte blocklight)
{ {
boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255); boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255);
ArrayList<BufferQuad> quadList = isTransparent ArrayList<BufferQuad> quadList = isTransparent
? this.transparentQuads[EDhDirection.UP.ordinal()] ? this.transparentQuads[EDhDirection.UP.ordinal()]
: this.opaqueQuads[EDhDirection.UP.ordinal()]; : this.opaqueQuads[EDhDirection.UP.ordinal()];
BufferQuad quad = new BufferQuad(minX, maxY, minZ, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP); BufferQuad quad = this.getOrCreateBufferQuad();
quad.set(minX, maxY, minZ, blockWidth, blockWidth, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP);
quadList.add(quad); quadList.add(quad);
} }
public void addQuadDown(short x, short y, short z, short width, short wz, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) public void addQuadDown(short x, short y, short z, short blockWidth, int color, byte irisBlockMaterialId, byte skylight, byte blocklight)
{ {
ArrayList<BufferQuad> quadArray = (this.doTransparency && ColorUtil.getAlpha(color) < 255) ArrayList<BufferQuad> quadArray = (this.doTransparency && ColorUtil.getAlpha(color) < 255)
? this.transparentQuads[EDhDirection.DOWN.ordinal()] ? this.transparentQuads[EDhDirection.DOWN.ordinal()]
: this.opaqueQuads[EDhDirection.DOWN.ordinal()]; : this.opaqueQuads[EDhDirection.DOWN.ordinal()];
BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN); BufferQuad quad = this.getOrCreateBufferQuad();
quad.set(x, y, z, blockWidth, blockWidth, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN);
quadArray.add(quad); quadArray.add(quad);
} }
//endregion
//=================// //=================//
// data finalizing // // data finalizing //
//=================// //=================//
//region
/** Uses Greedy meshing to merge this builder's Quads. */ /** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads() public void mergeQuads()
@@ -269,11 +305,14 @@ public class LodQuadBuilder
return mergeCount; return mergeCount;
} }
//endregion
//==============// //==============//
// buffer setup // // buffer setup //
//==============// //==============//
//region
public ArrayList<ByteBuffer> makeOpaqueVertexBuffers() { return this.makeVertexBuffers(this.opaqueQuads); } public ArrayList<ByteBuffer> makeOpaqueVertexBuffers() { return this.makeVertexBuffers(this.opaqueQuads); }
public ArrayList<ByteBuffer> makeTransparentVertexBuffers() { return this.makeVertexBuffers(this.transparentQuads); } public ArrayList<ByteBuffer> makeTransparentVertexBuffers() { return this.makeVertexBuffers(this.transparentQuads); }
@@ -295,9 +334,11 @@ public class LodQuadBuilder
{ {
// if this is the first iteration or the buffer is full, // if this is the first iteration or the buffer is full,
// create a new buffer // create a new buffer
if (buffer == null || !buffer.hasRemaining()) if (buffer == null
|| buffer.remaining() < BYTES_PER_QUAD)
{ {
buffer = MemoryUtil.memAlloc(LodBufferContainer.FULL_SIZED_BUFFER); buffer = ByteBuffer.allocateDirect(getMaxBufferByteSize());
buffer.order(ByteOrder.nativeOrder());
byteBufferList.add(buffer); byteBufferList.add(buffer);
} }
@@ -319,7 +360,7 @@ public class LodQuadBuilder
{ {
int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()]; int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()];
short widthEastWest = quad.widthEastWest; short widthEastWest = quad.widthEastWest;
short widthNorthSouth = quad.widthNorthSouthOrUpDown; short widthNorthSouth = quad.widthNorthSouthOrHeight;
byte normalIndex = (byte) quad.direction.ordinal(); byte normalIndex = (byte) quad.direction.ordinal();
EDhDirection.Axis axis = quad.direction.axis; EDhDirection.Axis axis = quad.direction.axis;
for (int i = 0; i < quadBase.length; i++) for (int i = 0; i < quadBase.length; i++)
@@ -380,7 +421,7 @@ public class LodQuadBuilder
// for horizontal and bottom faces of grass blocks, use the dirt color to // for horizontal and bottom faces of grass blocks, use the dirt color to
// prevent green cliff walls // prevent green cliff walls
color = this.clientLevelWrapper.getDirtBlockColor(); color = this.clientLevelWrapper.getDirtBlockColor();
color = ColorUtil.applyShade(color, MC_RENDER.getShade(quad.direction)); color = ColorUtil.applyShade(color, this.clientLevelWrapper.getShade(quad.direction));
} }
} }
} }
@@ -437,11 +478,14 @@ public class LodQuadBuilder
bb.putShort((short) 0); // padding to make sure the vertex format as a whole is a multiple of 4 bb.putShort((short) 0); // padding to make sure the vertex format as a whole is a multiple of 4
} }
//endregion
//=========// //=========//
// getters // // getters //
//=========// //=========//
//region
public int getCurrentOpaqueQuadsCount() public int getCurrentOpaqueQuadsCount()
{ {
@@ -469,17 +513,96 @@ public class LodQuadBuilder
return i; return i;
} }
/** Returns how many GpuBuffers will be needed to render opaque quads in this builder. */ private static int maxBufferByteSize = -1;
public int getCurrentNeededOpaqueVertexBufferCount() { return MathUtil.ceilDiv(this.getCurrentOpaqueQuadsCount(), LodBufferContainer.MAX_QUADS_PER_BUFFER); } /**
/** Returns how many GpuBuffers will be needed to render transparent quads in this builder. */ * The max number of bytes we allow for a single Vertex buffer.
public int getCurrentNeededTransparentVertexBufferCount() * If an LOD has more data than this it will be split
* up into multiple buffers.
*/
public static int getMaxBufferByteSize()
{ {
if (!this.doTransparency) if (maxBufferByteSize != -1)
{ {
return 0; return maxBufferByteSize;
} }
return MathUtil.ceilDiv(this.getCurrentTransparentQuadsCount(), LodBufferContainer.MAX_QUADS_PER_BUFFER); // 2 MB
// note: this is relatively small (10 MB was the previous max) to reduce stuttering
// during the upload process by having smaller upload steps
int maxVboByteSize = 2 * 1024 * 1024;
int maxQuadsPerBuffer = maxVboByteSize / BYTES_PER_QUAD;
// integer truncation to remove decimal component
int fullSizedBuffer = maxQuadsPerBuffer * BYTES_PER_QUAD;
maxBufferByteSize = fullSizedBuffer;
return fullSizedBuffer;
} }
//endregion
//=====================//
// buffer quad pooling //
//=====================//
//region
private BufferQuad getOrCreateBufferQuad()
{
// start from the back of the list so we don't have
// to move the array around
int index = bufferQuadCacheList.size() - 1;
if (index < 0)
{
// cache empty, create a new object
return new BufferQuad();
}
BufferQuad quad = bufferQuadCacheList.remove(index);
if (quad != null) // shouldn't happen, but just in case
{
return quad;
}
return new BufferQuad();
}
private static void returnQuadsToCache(ArrayList<BufferQuad> quadCache, ArrayList<BufferQuad>[] quadsToReturn)
{
for (int i = 0; i < quadsToReturn.length; i++)
{
// manual add and loop to reduce GC pressure due to addAll() doing unnecessary
// array copies
for (int j = 0; j < quadsToReturn[i].size(); j++)
{
quadCache.add(quadsToReturn[i].get(j));
}
quadsToReturn[i].clear();
}
}
//endregion
//================//
// base overrides //
//================//
//region
// can be used/closed multiple times
@Override
public void close()
{
returnQuadsToCache(this.bufferQuadCacheList, this.opaqueQuads);
returnQuadsToCache(this.bufferQuadCacheList, this.transparentQuads);
}
//endregion
} }
@@ -1,241 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.dataObjects.render.columnViews;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
public final class ColumnArrayView implements IColumnDataView
{
public final LongArrayList data;
/**
* How many data points are currently being represented by this view. <br>
* Will be equal to or less than {@link ColumnArrayView#verticalSize}.
*/
public final int size;
/**
* Vertical size in data points. <Br>
* Can be 0 if this column was created for an empty data source.
*/
public final int verticalSize;
/**
* Where the relative starting index is in the {@link ColumnArrayView#data} array
* if this view is representing part of a {@link ColumnRenderSource}.
*/
public final int offset;
//=============//
// constructor //
//=============//
/** @throws IllegalArgumentException if the offset is greater than the data's size */
public ColumnArrayView(LongArrayList data, int size, int offset, int verticalSize) throws IllegalArgumentException
{
this.data = data;
this.size = size;
this.offset = offset;
this.verticalSize = verticalSize;
if (this.data.size() < this.offset)
{
throw new IllegalArgumentException("data size ["+this.data.size()+"] is shorter than offset ["+this.offset+"].");
}
}
//=====================//
// getters and setters //
//=====================//
@Override
public long get(int index)
{
try
{
return this.data.getLong(index + this.offset);
}
catch (IndexOutOfBoundsException e)
{
// we can fairly confidently say this is a concurrent exception over an actual
// index out of bounds, since we're generally iterating over the whole
// array any time we use this getter.
throw new ConcurrentModificationException("Potential concurrent modification detected. Make sure the parent ColumnRenderSource isn't being closed before the ColumnArrayView processing is complete.", e);
}
}
public void set(int index, long value) { data.set(index + offset, value); }
@Override
public int size() { return size; }
@Override
public int verticalSize() { return verticalSize; }
@Override
public int dataCount() { return (this.verticalSize != 0) ? (this.size / this.verticalSize) : 0; } // TODO what does the divide by mean?
@Override
public ColumnArrayView subView(int dataIndexStart, int dataCount)
{ return new ColumnArrayView(data, dataCount * verticalSize, offset + dataIndexStart * verticalSize, verticalSize); }
public void fill(long value) { Arrays.fill(data.elements(), offset, offset + size, value); }
public void copyFrom(IColumnDataView source) { copyFrom(source, 0); }
public void copyFrom(IColumnDataView source, int outputDataIndexOffset)
{
if (source.verticalSize() > verticalSize)
{
throw new IllegalArgumentException("source verticalSize must be <= self's verticalSize to copy");
}
else if (source.dataCount() + outputDataIndexOffset > dataCount())
{
throw new IllegalArgumentException("dataIndexStart + source.dataCount() must be <= self.dataCount() to copy");
}
else if (source.verticalSize() != verticalSize)
{
for (int i = 0; i < source.dataCount(); i++)
{
int outputOffset = offset + outputDataIndexOffset * verticalSize + i * verticalSize;
source.subView(i, 1).copyTo(data.elements(), outputOffset, source.verticalSize());
Arrays.fill(data.elements(), outputOffset + source.verticalSize(),
outputOffset + verticalSize, 0);
}
}
else
{
source.copyTo(data.elements(), offset + outputDataIndexOffset * verticalSize, source.size());
}
}
@Override
public void copyTo(long[] target, int offset, int size) { System.arraycopy(data.elements(), this.offset, target, offset, size); }
public boolean mergeWith(ColumnArrayView source, boolean override)
{
if (size != source.size)
{
throw new IllegalArgumentException("Cannot merge views of different sizes");
}
if (verticalSize != source.verticalSize)
{
throw new IllegalArgumentException("Cannot merge views of different vertical sizes");
}
boolean anyChange = false;
for (int o = 0; o < (source.size() * verticalSize); o += verticalSize)
{
if (override)
{
if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) >= 0)
{
anyChange = true;
System.arraycopy(source.data, source.offset + o, data, offset + o, verticalSize);
}
}
else
{
if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) > 0)
{
anyChange = true;
System.arraycopy(source.data, source.offset + o, data, offset + o, verticalSize);
}
}
}
return anyChange;
}
public void changeVerticalSizeFrom(IColumnDataView source)
{
if (this.dataCount() != source.dataCount())
{
throw new IllegalArgumentException("Cannot copy and resize to views with different dataCounts");
}
if (this.verticalSize >= source.verticalSize())
{
this.copyFrom(source);
}
else
{
for (int i = 0; i < this.dataCount(); i++)
{
RenderDataPointUtil.mergeMultiData(source.subView(i, 1), this.subView(i, 1));
}
}
}
//================//
// base overrides //
//================//
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("S:").append(this.size);
sb.append(" V:").append(this.verticalSize);
sb.append(" O:").append(this.offset);
sb.append(" [");
for (int i = 0; i < this.size; i++)
{
sb.append(RenderDataPointUtil.toString(this.data.getLong(this.offset + i)));
if (i < this.size - 1)
{
sb.append(",\n");
}
}
sb.append("]");
return sb.toString();
}
public int getDataHash() { return arrayHash(this.data, this.offset, this.size); }
private static int arrayHash(LongArrayList a, int offset, int length)
{
if (a == null)
{
return 0;
}
int result = 1;
int end = offset + length;
for (int i = offset; i < end; i++)
{
long element = a.getLong(i);
int elementHash = (int) (element ^ (element >>> 32));
result = 31 * result + elementHash;
}
return result;
}
}
@@ -1,143 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.dataObjects.render.columnViews;
import it.unimi.dsi.fastutil.longs.LongArrayList;
public class ColumnQuadView implements IColumnDataView
{
private final LongArrayList data;
private final int perColumnOffset; // per column (of columns of data) offset in longs
private final int xSize; // x size in datapoints
private final int zSize; // x size in datapoints
private final int offset; // offset in longs
private final int vertSize; // vertical size in longs
public ColumnQuadView(LongArrayList data, int dataZWidth, int dataVertSize, int viewXOffset, int viewZOffset, int xSize, int zSize)
{
if (viewXOffset + xSize > (data.size() / (dataZWidth * dataVertSize)) || viewZOffset + zSize > dataZWidth)
{
throw new IllegalArgumentException("View is out of bounds");
}
this.data = data;
this.xSize = xSize;
this.zSize = zSize;
this.vertSize = dataVertSize;
this.perColumnOffset = dataZWidth * dataVertSize;
this.offset = viewXOffset * perColumnOffset + viewZOffset * dataVertSize;
}
private ColumnQuadView(LongArrayList data, int perColumnOffset, int offset, int vertSize, int xSize, int zSize)
{
this.data = data;
this.perColumnOffset = perColumnOffset;
this.offset = offset;
this.vertSize = vertSize;
this.xSize = xSize;
this.zSize = zSize;
}
@Override
public long get(int index)
{
int x = index / perColumnOffset;
int z = (index % perColumnOffset) / vertSize;
int v = index % vertSize;
return get(x, z, v);
}
public long get(int x, int z, int v)
{
return data.getLong(offset + x * perColumnOffset + z * vertSize + v);
}
public long set(int x, int z, int v, long value)
{
return data.set(offset + x * perColumnOffset + z * vertSize + v, value);
}
public ColumnArrayView get(int x, int z)
{
return new ColumnArrayView(data, vertSize, offset + x * perColumnOffset + z * vertSize, vertSize);
}
public ColumnArrayView getRow(int x)
{
return new ColumnArrayView(data, zSize * vertSize, offset + x * perColumnOffset, vertSize);
}
public void set(int x, int z, IColumnDataView singleColumn)
{
if (singleColumn.verticalSize() != vertSize) throw new IllegalArgumentException("Vertical size of singleColumn must be equal to vertSize");
if (singleColumn.dataCount() != 1) throw new IllegalArgumentException("SingleColumn must contain exactly one data point");
singleColumn.copyTo(data.elements(), offset + x * perColumnOffset + z * vertSize, singleColumn.size());
}
@Override
public int size()
{
return xSize * zSize * vertSize;
}
@Override
public int verticalSize()
{
return vertSize;
}
@Override
public int dataCount()
{
return xSize * zSize;
}
@Override
public IColumnDataView subView(int dataIndexStart, int dataCount)
{
if (dataCount != 1) throw new UnsupportedOperationException("Fixme: subView for QUadView only support one data point!");
int x = dataIndexStart / xSize;
int z = dataIndexStart % xSize;
return new ColumnArrayView(data, vertSize * dataCount, offset + x * perColumnOffset + z * vertSize, vertSize);
}
public ColumnQuadView subView(int xOffset, int zOffset, int xSize, int zSize)
{
if (xOffset + xSize > this.xSize || zOffset + zSize > this.zSize) throw new IllegalArgumentException("SubView is out of bounds");
return new ColumnQuadView(data, perColumnOffset, offset + xOffset * perColumnOffset + zOffset * vertSize, vertSize, xSize, zSize);
}
@Override
public void copyTo(long[] target, int offset, int size)
{
if (size != this.size() && size > zSize * vertSize) throw new UnsupportedOperationException("Not supported yet");
if (size <= xSize * vertSize)
{
System.arraycopy(data, this.offset, target, offset, size);
}
else
{
for (int x = 0; x < xSize; x++)
{
System.arraycopy(data, this.offset + x * perColumnOffset, target, offset + x * xSize * vertSize, xSize * vertSize);
}
}
}
}
@@ -0,0 +1,309 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.dataObjects.render.columnViews;
import com.seibel.distanthorizons.api.enums.config.EDhApiVerticalQuality;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.util.RenderDataPointReducingList;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Maps to part of a {@link ColumnRenderSource} for easier handling.
*
* @see ColumnRenderSource
*/
public final class ColumnRenderView implements AutoCloseable
{
private static final ConcurrentLinkedQueue<ColumnRenderView> POOL = new ConcurrentLinkedQueue<>();
public LongArrayList data;
/**
* How many data points are currently being represented by this view. <br>
* Will be equal to or less than {@link ColumnRenderView#maxVerticalSliceCount}.
*/
public int size;
/**
* Vertical size in data points. <Br>
* Can be 0 if this column was created for an empty data source.
* @see EDhApiVerticalQuality#calculateMaxNumberOfVerticalSlicesAtDetailLevel(byte)
*/
public int maxVerticalSliceCount;
/**
* Where the relative starting index is in the {@link ColumnRenderView#data} array
* if this view is representing part of a {@link ColumnRenderSource}.
*/
public int offset;
//=============//
// constructor //
//=============//
//region
private ColumnRenderView() { }
/**
* returns an un-populated view. <br>
* {@link ColumnRenderView#populate(LongArrayList, int, int, int)} must be called before the
* view can be used.s
*/
public static ColumnRenderView getPooled() { return getPooled(null, 0, 0, 0); }
public static ColumnRenderView getPooled(LongArrayList data, int size, int offset, int maxVerticalSliceCount) throws IllegalArgumentException
{
// try getting an existing pooled object first
ColumnRenderView view = POOL.poll();
if (view == null)
{
// no pooled object
view = new ColumnRenderView();
}
// data will be null if the object will be populated at a later date
if (data != null)
{
view.populate(data, size, offset, maxVerticalSliceCount);
}
return view;
}
/**
* Mutates this object so the necessary data is visible.
* @throws IllegalArgumentException if the offset is greater than the data's size
*/
public void populate(LongArrayList data, int size, int offset, int maxVerticalSliceCount) throws IllegalArgumentException
{
this.data = data;
this.size = size;
this.offset = offset;
this.maxVerticalSliceCount = maxVerticalSliceCount;
if (this.data.size() < this.offset)
{
throw new IllegalArgumentException("data size ["+this.data.size()+"] is shorter than offset ["+this.offset+"].");
}
}
public void clear()
{
this.data = null;
this.size = 0;
this.offset = 0;
this.maxVerticalSliceCount = 0;
}
//endregion
//=====================//
// getters and setters //
//=====================//
//region
public long get(int index)
{
try
{
return this.data.getLong(index + this.offset);
}
catch (IndexOutOfBoundsException e)
{
// we can fairly confidently say this is a concurrent exception over an actual
// index out of bounds, since we're generally iterating over the whole
// array any time we use this getter.
throw new ConcurrentModificationException("Potential concurrent modification detected. Make sure the parent ColumnRenderSource isn't being closed before the ColumnRenderView processing is complete.", e);
}
}
public void set(int index, long value) { this.data.set(index + this.offset, value); }
public void fill(long value) { Arrays.fill(this.data.elements(), this.offset, this.offset + this.size, value); }
//endregion
//=========//
// subview //
//=========//
//region
/** should be called in a try-finally block for automatic cleanup */
public ColumnRenderView subView(int dataIndexStart, int dataCount)
{
return ColumnRenderView.getPooled(
this.data,
dataCount * this.maxVerticalSliceCount,
this.offset + dataIndexStart * this.maxVerticalSliceCount,
this.maxVerticalSliceCount);
}
/** can be used to determine sub-view starting indexes */
public int subViewCount() { return (this.maxVerticalSliceCount != 0) ? (this.size / this.maxVerticalSliceCount) : 0; }
//endregion
//======================//
// change vertical size //
//======================//
//region
public void changeVerticalSizeFrom(ColumnRenderView source, RenderDataPointReducingList reducingList)
{
if (this.subViewCount() != source.subViewCount())
{
throw new IllegalArgumentException("Cannot copy and resize to views with different dataCounts");
}
if (this.maxVerticalSliceCount >= source.maxVerticalSliceCount)
{
this.copyFrom(source, 0);
}
else
{
for (int i = 0; i < this.subViewCount(); i++)
{
try(ColumnRenderView sourceSubView = source.subView(i, 1);
ColumnRenderView thisSubView = this.subView(i, 1))
{
mergeMultiData(sourceSubView, reducingList, thisSubView);
}
}
}
}
private void copyFrom(ColumnRenderView source, int outputDataIndexOffset)
{
if (source.maxVerticalSliceCount > this.maxVerticalSliceCount)
{
throw new IllegalArgumentException("source verticalSize must be <= self's verticalSize to copy");
}
else if (source.subViewCount() + outputDataIndexOffset > this.subViewCount())
{
throw new IllegalArgumentException("dataIndexStart + source.dataCount() must be <= self.dataCount() to copy");
}
else if (source.maxVerticalSliceCount != this.maxVerticalSliceCount)
{
for (int i = 0; i < source.subViewCount(); i++)
{
int outputOffset = this.offset + (outputDataIndexOffset * this.maxVerticalSliceCount) + (i * this.maxVerticalSliceCount);
try(ColumnRenderView subView = source.subView(i, 1))
{
subView.copyTo(this.data.elements(), outputOffset, source.maxVerticalSliceCount);
Arrays.fill(this.data.elements(),
outputOffset + source.maxVerticalSliceCount,
outputOffset + this.maxVerticalSliceCount,
0);
}
}
}
else
{
source.copyTo(this.data.elements(), this.offset + outputDataIndexOffset * this.maxVerticalSliceCount, source.size);
}
}
private void copyTo(long[] target, int offset, int size) { System.arraycopy(this.data.elements(), this.offset, target, offset, size); }
/**
* This method merge column of multiple data together
*
* @param sourceData one or more columns of data
* @param output one column of space for the result to be written to
*/
private static void mergeMultiData(ColumnRenderView sourceData, RenderDataPointReducingList reducingList, ColumnRenderView output)
{
int target = output.maxVerticalSliceCount;
if (target <= 0)
{
// I expect this to never be the case,
// but RenderDataPointReducingList handles it sanely,
// so I might as well handle it sanely here too.
output.fill(RenderDataPointUtil.EMPTY_DATA);
}
else if (target == 1)
{
output.set(0, RenderDataPointReducingList.reduceToOne(sourceData));
for (int index = 1, size = output.size; index < size; index++)
{
output.set(index, RenderDataPointUtil.EMPTY_DATA);
}
}
else
{
reducingList.populate(sourceData);
reducingList.reduce(output.maxVerticalSliceCount);
reducingList.copyTo(output);
}
}
//endregion
//================//
// base overrides //
//================//
//region
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("S:").append(this.size);
sb.append(" V:").append(this.maxVerticalSliceCount);
sb.append(" O:").append(this.offset);
sb.append(" [");
for (int i = 0; i < this.size; i++)
{
sb.append(RenderDataPointUtil.toString(this.data.getLong(this.offset + i)));
if (i < this.size - 1)
{
sb.append(",\n");
}
}
sb.append("]");
return sb.toString();
}
@Override
public void close()
{
// no validation is done to make sure this object is only added to the pool once
// please only use this object in a try-finally so the close is handled implicitly
POOL.add(this);
}
//endregion
}
@@ -1,59 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.dataObjects.render.columnViews;
import it.unimi.dsi.fastutil.longs.LongIterator;
import java.util.Iterator;
public interface IColumnDataView
{
long get(int index);
// FIXME probably horizontal size in blocks?
int size();
default LongIterator iterator()
{
return new LongIterator()
{
private int index = 0;
private final int size = IColumnDataView.this.size();
@Override
public boolean hasNext() { return this.index < this.size; }
@Override
public long nextLong() { return IColumnDataView.this.get(this.index++); }
};
}
// FIXME measured in blocks?
int verticalSize();
// FIXME how many datapoints in this LOD?
int dataCount();
IColumnDataView subView(int dataIndexStart, int dataCount);
void copyTo(long[] target, int offset, int count);
}
@@ -24,22 +24,24 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap; import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnRenderView;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.*; import com.seibel.distanthorizons.core.util.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.HashSet; import java.util.HashSet;
@@ -108,11 +110,11 @@ public class FullDataToRenderDataTransformer
final long pos = fullDataSource.getPos(); final long pos = fullDataSource.getPos();
final byte dataDetail = fullDataSource.getDataDetailLevel(); final byte dataDetail = fullDataSource.getDataDetailLevel();
final int vertSize = Config.Client.Advanced.Graphics.Quality.verticalQuality.get().calculateMaxVerticalData(fullDataSource.getDataDetailLevel()); final int maxVertSliceCount = Config.Client.Advanced.Graphics.Quality.verticalQuality.get().calculateMaxNumberOfVerticalSlicesAtDetailLevel(fullDataSource.getDataDetailLevel());
final ColumnRenderSource columnSource = ColumnRenderSource.createEmpty(pos, vertSize, levelWrapper.getMinHeight()); final ColumnRenderSource columnSource = ColumnRenderSource.createEmpty(pos, maxVertSliceCount, levelWrapper.getMinHeight());
if (fullDataSource.isEmpty) if (fullDataSource.isEmpty)
{ {
return columnSource; return columnSource;
@@ -122,32 +124,41 @@ public class FullDataToRenderDataTransformer
int baseX = DhSectionPos.getMinCornerBlockX(pos); int baseX = DhSectionPos.getMinCornerBlockX(pos);
int baseZ = DhSectionPos.getMinCornerBlockZ(pos); int baseZ = DhSectionPos.getMinCornerBlockZ(pos);
try(ColumnRenderView columnArrayView = ColumnRenderView.getPooled();
PhantomArrayListCheckout phantomCheckout = ARRAY_LIST_POOL.checkoutLongArrays(1);
ColumnRenderView tempExpandingColumnView = ColumnRenderView.getPooled();
RenderDataPointReducingList reducingList = new RenderDataPointReducingList())
{
DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable();
for (int x = 0; x < FullDataSourceV2.WIDTH; x++) for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
{ {
for (int z = 0; z < FullDataSourceV2.WIDTH; z++) for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
{ {
ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z); columnSource.populateColumnView(columnArrayView, x, z);
LongArrayList dataColumn = fullDataSource.getColumnAtRelPos(x, z); LongArrayList dataColumn = fullDataSource.getColumnAtRelPos(x, z);
updateOrReplaceRenderDataViewColumnWithFullDataColumn( updateOrReplaceRenderDataViewColumnWithFullDataColumn(
levelWrapper, fullDataSource, levelWrapper, fullDataSource,
// bitshift is to account for LODs with a detail level greater than 0 so the block pos is correct // bit shift is to account for LODs with a detail level greater than 0 so the block pos is correct
baseX + BitShiftUtil.pow(x,dataDetail), baseZ + BitShiftUtil.pow(z,dataDetail), baseX + BitShiftUtil.pow(x, dataDetail), baseZ + BitShiftUtil.pow(z, dataDetail),
columnArrayView, dataColumn); columnArrayView, dataColumn,
// pooled references so we don't need to re-allocate/get them 4000 times per render source
phantomCheckout, tempExpandingColumnView, reducingList, mutableBlockPos);
}
} }
} }
columnSource.fillDebugFlag(0, 0, ColumnRenderSource.WIDTH, ColumnRenderSource.WIDTH, ColumnRenderSource.DebugSourceFlag.FULL);
return columnSource; return columnSource;
} }
/** Updates the given {@link ColumnArrayView} to match the incoming Full data {@link LongArrayList} */ /** Updates the given {@link ColumnRenderView} to match the incoming Full data {@link LongArrayList} */
public static void updateOrReplaceRenderDataViewColumnWithFullDataColumn( public static void updateOrReplaceRenderDataViewColumnWithFullDataColumn(
IClientLevelWrapper levelWrapper, IClientLevelWrapper levelWrapper,
FullDataSourceV2 fullDataSource, int blockX, int blockZ, FullDataSourceV2 fullDataSource, int blockX, int blockZ,
ColumnArrayView columnArrayView, ColumnRenderView columnArrayView,
LongArrayList fullDataColumn) LongArrayList fullDataColumn,
// pooled references
PhantomArrayListCheckout phantomCheckout, ColumnRenderView tempExpandingColumnView, RenderDataPointReducingList reducingList, DhBlockPosMutable mutableBlockPos)
{ {
// we can't do anything if the full data is missing or empty // we can't do anything if the full data is missing or empty
if (fullDataColumn == null if (fullDataColumn == null
@@ -157,44 +168,40 @@ public class FullDataToRenderDataTransformer
} }
int fullDataLength = fullDataColumn.size(); int fullDataLength = fullDataColumn.size();
if (fullDataLength <= columnArrayView.verticalSize()) if (fullDataLength <= columnArrayView.maxVerticalSliceCount)
{ {
// Directly use the arrayView since it fits. // Directly use the arrayView since it fits.
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn); setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn, mutableBlockPos);
} }
else else
{ {
PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutArrays(0, 0, 1); LongArrayList dataArrayList = phantomCheckout.getLongArray(0, fullDataLength);
LongArrayList dataArrayList = checkout.getLongArray(0, fullDataLength);
try
{
// expand the ColumnArrayView to fit the new larger max vertical size // expand the ColumnArrayView to fit the new larger max vertical size
ColumnArrayView newColumnArrayView = new ColumnArrayView(dataArrayList, fullDataLength, 0, fullDataLength); tempExpandingColumnView.populate(dataArrayList, fullDataLength, 0, fullDataLength);
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, newColumnArrayView, fullDataColumn); setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, tempExpandingColumnView, fullDataColumn, mutableBlockPos);
columnArrayView.changeVerticalSizeFrom(newColumnArrayView); columnArrayView.changeVerticalSizeFrom(tempExpandingColumnView, reducingList);
}
finally
{
ARRAY_LIST_POOL.returnCheckout(checkout);
}
} }
} }
private static void setRenderColumnView( private static void setRenderColumnView(
IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource, IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource,
int blockX, int blockZ, int blockX, int blockZ,
ColumnArrayView renderColumnData, LongArrayList fullColumnData) ColumnRenderView renderColumnData, LongArrayList fullColumnData, DhBlockPosMutable mutableBlockPos)
{ {
//===============// //===============//
// config values // // config values //
//===============// //===============//
boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING); boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Culling.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING);
boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get(); boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Culling.tintWithAvoidedBlocks.get();
final ObjectOpenHashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(levelWrapper);
final ObjectOpenHashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(levelWrapper);
final ObjectOpenHashSet<IBlockStateWrapper> waterSubsurfaceReplacementBlocks = WRAPPER_FACTORY.getWaterSubsurfaceReplacementBlocks(levelWrapper);
final ObjectOpenHashSet<IBlockStateWrapper> waterSurfaceReplacementBlocks = WRAPPER_FACTORY.getWaterSurfaceReplacementBlocks(levelWrapper);
final IBlockStateWrapper water = WRAPPER_FACTORY.getWaterBlockStateWrapper(levelWrapper);
HashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(levelWrapper);
HashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(levelWrapper);
// build snow block cache if needed // build snow block cache if needed
if (snowLayerBlockStates == null) if (snowLayerBlockStates == null)
@@ -222,6 +229,7 @@ public class FullDataToRenderDataTransformer
int colorToApplyToNextBlock = -1; int colorToApplyToNextBlock = -1;
int lastColor = 0; int lastColor = 0;
int lastBottom = -10_000; int lastBottom = -10_000;
IBlockStateWrapper lastBlock = null;
int skylightToApplyToNextBlock = -1; int skylightToApplyToNextBlock = -1;
int blocklightToApplyToNextBlock = -1; int blocklightToApplyToNextBlock = -1;
@@ -235,7 +243,8 @@ public class FullDataToRenderDataTransformer
FullDataPointIdMap fullDataMapping = fullDataSource.mapping; FullDataPointIdMap fullDataMapping = fullDataSource.mapping;
DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable(blockX, 0, blockZ); mutableBlockPos.setX(blockX);
mutableBlockPos.setZ(blockZ);
// goes from the top down // goes from the top down
for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++) for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++)
@@ -282,9 +291,19 @@ public class FullDataToRenderDataTransformer
// cave culling check // // cave culling check //
//====================// //====================//
if (waterSubsurfaceReplacementBlocks.contains(block)
&& (lastBlock == null || lastBlock.isAir()))
{
block = water;
}
boolean ignoreBlock = blockStatesToIgnore.contains(block); boolean ignoreBlock = blockStatesToIgnore.contains(block);
boolean caveBlock = caveBlockStatesToIgnore.contains(block); // TODO caves should also ignore transparent/non-solid blocks (IE grass and plants) wthout each being defined boolean caveBlock = caveBlockStatesToIgnore.contains(block);
if (caveBlock) if (caveBlock
// caves also ignore transparent/non-solid blocks (IE grass and plants) without each being defined
|| !block.isSolid()
|| block.isLiquid()
|| block.getOpacity() < LodUtil.BLOCK_FULLY_OPAQUE)
{ {
if (caveCullingEnabled if (caveCullingEnabled
// assume this data point is underground if it has no sky-light // assume this data point is underground if it has no sky-light
@@ -323,6 +342,9 @@ public class FullDataToRenderDataTransformer
else if (ignoreBlock) else if (ignoreBlock)
{ {
// this is an ignored block, but shouldn't be merged like a cave block // this is an ignored block, but shouldn't be merged like a cave block
// applying this sky light to the next block should prevent black spots for opaque covering blocks
skylightToApplyToNextBlock = skyLight;
continue; continue;
} }
@@ -338,34 +360,67 @@ public class FullDataToRenderDataTransformer
&& !block.isLiquid() && !block.isLiquid()
&& block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE; && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE;
// merge snow into the block below it // handle height reduction
if (snowLayerBlockStates.contains(block)) boolean isSnowLayer = snowLayerBlockStates.contains(block);
boolean isWaterSurfaceReplacement = waterSurfaceReplacementBlocks.contains(block);
if (isSnowLayer || isWaterSurfaceReplacement)
{ {
// sometimes a snow datapoint will be multiple blocks tall, if (isWaterSurfaceReplacement)
{
// replace the block with water
block = WRAPPER_FACTORY.getWaterBlockStateWrapper(levelWrapper);
}
// sometimes a datapoint will be multiple blocks tall,
// in that case we just want to drop the top by 1 // in that case we just want to drop the top by 1
blockHeight -= 1; blockHeight -= 1;
if (blockHeight == 0) if (blockHeight == 0)
{ {
// this snow block was entirely removed, just color the block below it // this block was entirely removed, just color the block below it
ignoreNonSolidBlock = true; ignoreNonSolidBlock = true;
if (isSnowLayer)
{
// snow is a special case where it should always tint the block
// below it, if not done grass will appear as gray
int snowColor = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
colorToApplyToNextBlock = ColorUtil.setAlpha(snowColor, 255);
}
else //if (isWaterSurfaceReplacement)
{
colorToApplyToNextBlock = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
}
} }
} }
if (ignoreNonSolidBlock) if (ignoreNonSolidBlock)
{ {
int ignoredColor = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
int ignoredAlpha = ColorUtil.getAlpha(ignoredColor);
if (colorBelowWithAvoidedBlocks) if (colorBelowWithAvoidedBlocks)
{ {
int tempColor = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
// don't transfer the color when alpha is 0 // don't transfer the color when alpha is 0
// this prevents issues if grass is transparent // this prevents issues if grass is transparent
if (ColorUtil.getAlpha(tempColor) != 0) if (ignoredAlpha != 0)
{ {
colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor, 255); colorToApplyToNextBlock = ColorUtil.setAlpha(ignoredColor, 255);
}
}
// Don't transfer the lighting when alpha is 0
// (the block below should have its own lighting).
if (ignoredAlpha != 0)
{
// Lighting is transferred even when "colorBelowWithAvoidedBlocks"
// is false, since otherwise the blocks underneath may have a light value of "0"
// which makes things look darker than they should.
// This can specifically manifest as grid lines on LOD borders
// (not entire sure why grid lines on LOD borders, maybe it has to do with the fact that those LODs aren't occluded?).
skylightToApplyToNextBlock = skyLight; skylightToApplyToNextBlock = skyLight;
blocklightToApplyToNextBlock = blockLight; blocklightToApplyToNextBlock = blockLight;
} }
}
// skip this non-colliding block // skip this non-colliding block
continue; continue;
@@ -377,15 +432,38 @@ public class FullDataToRenderDataTransformer
{ {
// use this block's color // use this block's color
color = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block); color = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block);
// use the skylight override if present
if (skylightToApplyToNextBlock != -1)
{
skyLight = skylightToApplyToNextBlock;
// remove the override so we don't accidentally override the next datapoint
skylightToApplyToNextBlock = -1;
}
if (blocklightToApplyToNextBlock != -1)
{
blockLight = blocklightToApplyToNextBlock;
blocklightToApplyToNextBlock = -1;
}
} }
else else
{ {
// use the previous block's color // use the previous block's color
color = colorToApplyToNextBlock; color = colorToApplyToNextBlock;
colorToApplyToNextBlock = -1; colorToApplyToNextBlock = -1;
// use the skylight override if present
if (skylightToApplyToNextBlock != -1)
{
skyLight = skylightToApplyToNextBlock; skyLight = skylightToApplyToNextBlock;
}
if (blocklightToApplyToNextBlock != -1)
{
blockLight = blocklightToApplyToNextBlock; blockLight = blocklightToApplyToNextBlock;
} }
}
@@ -413,6 +491,7 @@ public class FullDataToRenderDataTransformer
} }
lastBottom = bottomY; lastBottom = bottomY;
lastColor = color; lastColor = color;
lastBlock = block;
} }
@@ -154,7 +154,7 @@ public class LodDataBuilder
IBlockStateWrapper currentBlockState = AIR; IBlockStateWrapper currentBlockState = AIR;
int mappedId = dataSource.mapping.addIfNotPresentAndGetId(currentBiome, currentBlockState); int mappedId = dataSource.mapping.addIfNotPresentAndGetId(currentBiome, currentBlockState);
// Determine lighting (we are at the height limit. There are no torches here, and sky is not obscured.) // TODO: Per face lighting someday? // Determine lighting (we are at the height limit. There are no torches here, and sky is not obscured.)
byte blockLight = LodUtil.MIN_MC_LIGHT; byte blockLight = LodUtil.MIN_MC_LIGHT;
byte skyLight = LodUtil.MAX_MC_LIGHT; byte skyLight = LodUtil.MAX_MC_LIGHT;
@@ -321,8 +321,6 @@ public class LodDataBuilder
LongArrayList packedDataPoints = convertApiDataPointListToPackedLongArray(columnDataPoints, dataSource, apiChunk.bottomYBlockPos, runAdditionalValidation); LongArrayList packedDataPoints = convertApiDataPointListToPackedLongArray(columnDataPoints, dataSource, apiChunk.bottomYBlockPos, runAdditionalValidation);
// TODO add the ability for API users to define a different compression mode
// or add a "unkown" compression mode
dataSource.setSingleColumn( dataSource.setSingleColumn(
packedDataPoints, packedDataPoints,
relBlockX + relSourceBlockX, relBlockZ + relSourceBlockZ, relBlockX + relSourceBlockX, relBlockZ + relSourceBlockZ,
@@ -19,7 +19,7 @@
package com.seibel.distanthorizons.core.enums; package com.seibel.distanthorizons.core.enums;
import com.seibel.distanthorizons.core.util.math.Vec3i; import com.seibel.distanthorizons.core.util.math.DhVec3i;
/** /**
* Up <Br> * Up <Br>
@@ -32,17 +32,17 @@ import com.seibel.distanthorizons.core.util.math.Vec3i;
public enum EDhDirection public enum EDhDirection
{ {
/** negative Y */ /** negative Y */
DOWN("down", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Y, new Vec3i(0, -1, 0), -1), DOWN("down", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Y, new DhVec3i(0, -1, 0), -1),
/** positive Y */ /** positive Y */
UP("up", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Y, new Vec3i(0, 1, 0), -1), UP("up", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Y, new DhVec3i(0, 1, 0), -1),
/** negative Z */ /** negative Z */
NORTH("north", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Z, new Vec3i(0, 0, -1), 0), NORTH("north", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Z, new DhVec3i(0, 0, -1), 0),
/** positive Z */ /** positive Z */
SOUTH("south", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Z, new Vec3i(0, 0, 1), 1), SOUTH("south", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Z, new DhVec3i(0, 0, 1), 1),
/** negative X */ /** negative X */
WEST("west", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.X, new Vec3i(-1, 0, 0), 2), WEST("west", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.X, new DhVec3i(-1, 0, 0), 2),
/** positive X */ /** positive X */
EAST("east", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.X, new Vec3i(1, 0, 0), 3); EAST("east", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.X, new DhVec3i(1, 0, 0), 3);
/** Up, Down, West, East, North, South */ /** Up, Down, West, East, North, South */
@@ -68,7 +68,7 @@ public enum EDhDirection
public final String name; public final String name;
public final EDhDirection.Axis axis; public final EDhDirection.Axis axis;
public final EDhDirection.AxisDirection axisDirection; public final EDhDirection.AxisDirection axisDirection;
public final Vec3i normal; public final DhVec3i normal;
/** -1 if not a {@link EDhDirection#CARDINAL_COMPASS} direction */ /** -1 if not a {@link EDhDirection#CARDINAL_COMPASS} direction */
public final int compassIndex; public final int compassIndex;
@@ -78,7 +78,7 @@ public enum EDhDirection
// constructor // // constructor //
//=============// //=============//
EDhDirection(String name, EDhDirection.AxisDirection axisDirection, EDhDirection.Axis axis, Vec3i normal, int compassIndex) EDhDirection(String name, EDhDirection.AxisDirection axisDirection, EDhDirection.Axis axis, DhVec3i normal, int compassIndex)
{ {
this.name = name; this.name = name;
this.axis = axis; this.axis = axis;
@@ -1,286 +0,0 @@
package com.seibel.distanthorizons.core.file.fullDatafile;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.KeyedLockContainer;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
/**
* Used to batch together multiple data source updates that all
* affect the same position.
*/
public class DelayedFullDataSourceSaveCache implements AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/**
* a cache won't automatically clean itself unless we trigger it's clean method
* if not done then we'd only see the cache invalidate when new inserts happen,
* which causes weird behavior when placing/breaking blocks.
*/
private static final ThreadPoolExecutor BACKGROUND_CLEAN_UP_THREAD = ThreadUtil.makeSingleDaemonThreadPool("delayed save cache cleaner");
private static final Set<WeakReference<DelayedFullDataSourceSaveCache>> SAVE_CACHE_SET = Collections.newSetFromMap(new ConcurrentHashMap<>());
/** how long between clean up checks */
private static final int CLEANUP_CHECK_TIME_IN_MS = 1_000;
private final ConcurrentHashMap<Long, DataSourceSavedTimePair> dataSourceByPosition = new ConcurrentHashMap<Long, DataSourceSavedTimePair>();
/* don't let two threads load the same position at the same time */
protected final KeyedLockContainer<Long> saveLockContainer = new KeyedLockContainer<>();
private final ISaveDataSourceFunc onSaveTimeoutAsyncFunc;
private final int saveDelayInMs;
//=============//
// constructor //
//=============//
static
{
BACKGROUND_CLEAN_UP_THREAD.execute(() -> runCleanupLoop());
}
public DelayedFullDataSourceSaveCache(@NotNull ISaveDataSourceFunc onSaveTimeoutAsyncFunc, int saveDelayInMs)
{
this.onSaveTimeoutAsyncFunc = onSaveTimeoutAsyncFunc;
// we can't clean items faster than the cleanup timer fires
if (saveDelayInMs < CLEANUP_CHECK_TIME_IN_MS)
{
LOGGER.warn("The save delay ["+saveDelayInMs+"] shouldn't be less than the cleanup check timer interval ["+CLEANUP_CHECK_TIME_IN_MS+"].");
}
this.saveDelayInMs = saveDelayInMs;
SAVE_CACHE_SET.add(new WeakReference<>(this));
}
//==============//
// update queue //
//==============//
/**
* Writing into memory is done synchronously so inputDataSource can
* be closed after this method finishes.
*/
public void writeDataSourceToMemoryAndQueueSave(@NotNull FullDataSourceV2 inputDataSource)
{
long inputPos = inputDataSource.getPos();
ReentrantLock lockForPos = this.saveLockContainer.getLockForPos(inputPos);
try
{
lockForPos.lock();
FullDataSourceV2 memoryDataSource;
DataSourceSavedTimePair pair = this.dataSourceByPosition.getOrDefault(inputPos, null);
if (pair == null)
{
// no data currently in the memory cache for this position
memoryDataSource = FullDataSourceV2.createEmpty(inputPos);
pair = new DataSourceSavedTimePair(memoryDataSource);
DataSourceSavedTimePair oldPair = this.dataSourceByPosition.put(inputPos, pair);
if (oldPair != null)
{
// shouldn't happen, but just in case
this.handleDataSourceRemoval(oldPair.dataSource);
}
}
else
{
memoryDataSource = pair.dataSource;
}
// write the new data into memory
memoryDataSource.updateFromDataSource(inputDataSource);
// keep track of when the last time we saved something was
pair.updateLastWrittenTimestamp();
}
finally
{
lockForPos.unlock();
}
}
/** when this method is called the datasource should no longer be in the memory cache */
public void handleDataSourceRemoval(@NotNull FullDataSourceV2 removedDataSource)
{
this.onSaveTimeoutAsyncFunc.saveAsync(removedDataSource)
.handle((voidObj, throwable) ->
{
try
{
// if this close method is fired multiple times
// monoliths can appear due to concurrent writing to the
// backend arrays
removedDataSource.close();
}
catch (Exception e)
{
LOGGER.error("Unable to close datasource ["+ DhSectionPos.toString(removedDataSource.getPos()) +"], error: ["+e.getMessage()+"].", e);
}
return null;
});
}
//==============//
// List methods //
//==============//
public int getUnsavedCount() { return this.dataSourceByPosition.size(); }
public void flush() { this.cleanUp(true); }
/** Removes everything from the memory cache and fires the {@link DelayedFullDataSourceSaveCache#onSaveTimeoutAsyncFunc} for each. */
public void cleanUp(boolean flushAll)
{
Enumeration<Long> keyIterator = this.dataSourceByPosition.keys();
while (keyIterator.hasMoreElements())
{
Long pos = keyIterator.nextElement();
ReentrantLock posLock = this.saveLockContainer.getLockForPos(pos);
try
{
posLock.lock();
DataSourceSavedTimePair savedPair = this.dataSourceByPosition.getOrDefault(pos, null);
if (savedPair != null)
{
if (flushAll
|| savedPair.dataSourceHasTimedOut(this.saveDelayInMs))
{
this.dataSourceByPosition.remove(pos);
this.handleDataSourceRemoval(savedPair.dataSource);
}
}
}
finally
{
posLock.unlock();
}
}
}
//================//
// static cleanup //
//================//
private static void runCleanupLoop()
{
while (true)
{
try
{
try
{
Thread.sleep(CLEANUP_CHECK_TIME_IN_MS);
}
catch (InterruptedException ignore) { }
SAVE_CACHE_SET.forEach((cacheRef) ->
{
DelayedFullDataSourceSaveCache cache = cacheRef.get();
if (cache == null)
{
// shouldn't be necessary, but if we forget to manually close a cache, this will prevent leaking
SAVE_CACHE_SET.remove(cacheRef);
}
else
{
cache.cleanUp(false);
}
});
}
catch (Exception e)
{
LOGGER.error("Unexpected error in cleanup thread: [" + e.getMessage() + "].", e);
}
}
}
//================//
// base overrides //
//================//
@Override
public void close()
{
// not the fastest way to handle removing,
// but we shouldn't have more than 20 or so at once
// so this should be just fine
SAVE_CACHE_SET.removeIf((cacheRef) ->
{
DelayedFullDataSourceSaveCache cache = cacheRef.get();
return cache != null && cache.equals(this);
});
}
//================//
// helper classes //
//================//
@FunctionalInterface
public interface ISaveDataSourceFunc
{
/** called after the timeout expires */
CompletableFuture<Void> saveAsync(FullDataSourceV2 inputDataSource);
}
/**
* used to keep track of when data sources
* were written to so we can flush them once
* enough time has passed.
*/
private static class DataSourceSavedTimePair
{
@NotNull
public final FullDataSourceV2 dataSource;
/** the last unix millisecond time this data source was written to */
public long lastWrittenDateTimeMs;
public DataSourceSavedTimePair(@NotNull FullDataSourceV2 dataSource)
{
this.dataSource = dataSource;
this.lastWrittenDateTimeMs = System.currentTimeMillis();
}
public void updateLastWrittenTimestamp()
{ this.lastWrittenDateTimeMs = System.currentTimeMillis(); }
public boolean dataSourceHasTimedOut(long msTillTimeout)
{
long currentTime = System.currentTimeMillis();
long timeSinceUpdate = currentTime - this.lastWrittenDateTimeMs;
return (timeSinceUpdate > msTillTimeout);
}
}
}
@@ -20,28 +20,26 @@
package com.seibel.distanthorizons.core.file.fullDatafile; package com.seibel.distanthorizons.core.file.fullDatafile;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.api.internal.SharedApi; import com.seibel.distanthorizons.core.api.internal.chunkUpdating.WorldChunkUpdateManager;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataUpdatePropagatorV2; import com.seibel.distanthorizons.core.util.delayedSaveCache.DelayedDataSourceSaveCache;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue; import com.seibel.distanthorizons.core.generation.queues.IFullDataSourceRetrievalQueue;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState; import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.ExceptionUtil; import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -54,7 +52,6 @@ import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.IntStream; import java.util.stream.IntStream;
public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 implements IDebugRenderable public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 implements IDebugRenderable
@@ -65,9 +62,6 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
* Having this number too high causes the system to become overwhelmed by * Having this number too high causes the system to become overwhelmed by
* world gen requests and other jobs won't be done. <br> * world gen requests and other jobs won't be done. <br>
* IE: LODs won't update or render because world gen is hogging the CPU. * IE: LODs won't update or render because world gen is hogging the CPU.
* <br><br>
* TODO this should be dynamically allocated based on CPU load
* and abilities.
*/ */
public static final int MAX_WORLD_GEN_REQUESTS_PER_THREAD = 20; public static final int MAX_WORLD_GEN_REQUESTS_PER_THREAD = 20;
@@ -77,7 +71,9 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
private final AtomicReference<IFullDataSourceRetrievalQueue> worldGenQueueRef = new AtomicReference<>(null); private final AtomicReference<IFullDataSourceRetrievalQueue> worldGenQueueRef = new AtomicReference<>(null);
private final ArrayList<IOnWorldGenCompleteListener> onWorldGenTaskCompleteListeners = new ArrayList<>(); private final ArrayList<IOnWorldGenCompleteListener> onWorldGenTaskCompleteListeners = new ArrayList<>();
protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSaveAsync, 10_000); protected final DelayedDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedDataSourceSaveCache(this::onDataSourceSaveAsync, 10_000);
private final ConcurrentHashMap<Long, CompletableFuture<DataSourceRetrievalResult>> queuedRetrievalFutureByPos = new ConcurrentHashMap<>();
@@ -137,7 +133,17 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
LodUtil.assertTrue(genTaskResult.dataSource != null, "Successful retrieval object should have a datasource."); LodUtil.assertTrue(genTaskResult.dataSource != null, "Successful retrieval object should have a datasource.");
this.dataUpdater.updateDataSource(genTaskResult.dataSource); this.dataUpdater.updateDataSource(genTaskResult.dataSource);
this.fireOnGenPosSuccessListeners(genTaskResult.pos);
// synchronized to prevent a rare issue where the world generator is being shut down while this listener is firing
synchronized (this.onWorldGenTaskCompleteListeners)
{
// fire the event listeners
for (IOnWorldGenCompleteListener listener : this.onWorldGenTaskCompleteListeners)
{
listener.onWorldGenTaskComplete(genTaskResult.pos);
}
}
genTaskResult.dataSource.close(); genTaskResult.dataSource.close();
} }
else if (genTaskResult.state == ERetrievalResultState.REQUIRES_SPLITTING) else if (genTaskResult.state == ERetrievalResultState.REQUIRES_SPLITTING)
@@ -155,19 +161,9 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
{ {
LOGGER.error("Unexpected issue during onWorldGenTaskComplete, error: ["+e.getMessage()+"].", e); LOGGER.error("Unexpected issue during onWorldGenTaskComplete, error: ["+e.getMessage()+"].", e);
} }
} finally
// TODO only fire after the section has finished generated or once every X seconds
private void fireOnGenPosSuccessListeners(long pos)
{ {
// synchronized to prevent a rare issue where the world generator is being shut down while this listener is firing this.queuedRetrievalFutureByPos.remove(genPos);
synchronized (this.onWorldGenTaskCompleteListeners)
{
// fire the event listeners
for (IOnWorldGenCompleteListener listener : this.onWorldGenTaskCompleteListeners)
{
listener.onWorldGenTaskComplete(pos);
}
} }
} }
@@ -222,44 +218,27 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
} }
// we can't queue anything if the world generator isn't set up yet
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get(); IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue == null) if (worldGenQueue == null)
{ {
// we can't queue anything if the world generator isn't set up yet
return false; return false;
} }
PriorityTaskPicker.Executor renderLoadExecutor = ThreadPoolUtil.getRenderLoadingExecutor();
if (renderLoadExecutor == null
|| renderLoadExecutor.getQueueSize() >= FullDataUpdatePropagatorV2.getMaxPropagateTaskCount() / 2)
{
// don't queue additional world gen requests if the render loader handler is overwhelmed,
// otherwise LODs may not load in properly
return false;
}
PriorityTaskPicker.Executor fileHandlerExecutor = ThreadPoolUtil.getFileHandlerExecutor();
if (fileHandlerExecutor == null
|| fileHandlerExecutor.getQueueSize() >= FullDataUpdatePropagatorV2.getMaxPropagateTaskCount() / 2)
{
// don't queue additional world gen requests if the file handler is overwhelmed,
// otherwise LODs may not load in properly
return false;
}
int maxQueuedChunkCount = MAX_WORLD_GEN_REQUESTS_PER_THREAD * Config.Common.MultiThreading.numberOfThreads.get(); // for now we're just using the same logic as the world gen threads, it works well enough
if (SharedApi.INSTANCE.getQueuedChunkUpdateCount() >= maxQueuedChunkCount)
{
// don't queue additional world gen requests if there are
// a lot of chunks waiting to update
// (this is done to reduce thread starvation for chunk updates)
return false;
}
int maxWorldGenQueueCount = MAX_WORLD_GEN_REQUESTS_PER_THREAD * Config.Common.MultiThreading.numberOfThreads.get(); int maxWorldGenQueueCount = MAX_WORLD_GEN_REQUESTS_PER_THREAD * Config.Common.MultiThreading.numberOfThreads.get();
int currentQueueCount = WorldChunkUpdateManager.INSTANCE.getTotalQueuedCount();
// don't queue additional world gen requests if there are
// a lot of chunks waiting to update
if (currentQueueCount >= maxWorldGenQueueCount)
{
return false;
}
if (this.delayedFullDataSourceSaveCache.getUnsavedCount() >= maxWorldGenQueueCount) if (this.delayedFullDataSourceSaveCache.getUnsavedCount() >= maxWorldGenQueueCount)
{ {
@@ -307,7 +286,14 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
} }
CompletableFuture<DataSourceRetrievalResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)); CompletableFuture<DataSourceRetrievalResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL));
// only queue the when-complete once for each world gen task,
// otherwise we can end up trying to close the same datasource multiple times
CompletableFuture<DataSourceRetrievalResult> oldWorldGenFuture = this.queuedRetrievalFutureByPos.putIfAbsent(genPos, worldGenFuture);
if (oldWorldGenFuture == null)
{
worldGenFuture.whenComplete((r, e) -> this.onWorldGenTaskComplete(genPos, r, e)); worldGenFuture.whenComplete((r, e) -> this.onWorldGenTaskComplete(genPos, r, e));
}
return worldGenFuture; return worldGenFuture;
} }
@@ -351,7 +337,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// don't check any child positions if this position is already fully generated // don't check any child positions if this position is already fully generated
if (this.repo.existsWithKey(pos)) if (this.repo.existsWithKey(pos))
{ {
try(PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutArrays(1, 0, 0)) try(PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutByteArrays(1))
{ {
ByteArrayList columnGenStepArray = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH); ByteArrayList columnGenStepArray = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH);
this.repo.getColumnGenerationStepForPos(pos, columnGenStepArray); this.repo.getColumnGenerationStepForPos(pos, columnGenStepArray);
@@ -398,7 +384,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
{ {
EDhApiWorldGenerationStep currentMinWorldGenStep = EDhApiWorldGenerationStep.LIGHT; EDhApiWorldGenerationStep currentMinWorldGenStep = EDhApiWorldGenerationStep.LIGHT;
try(PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutArrays(1, 0, 0)) try(PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutByteArrays(1))
{ {
ByteArrayList columnGenerationSteps = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH); ByteArrayList columnGenerationSteps = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH);
this.repo.getColumnGenerationStepForPos(genPos, columnGenerationSteps); this.repo.getColumnGenerationStepForPos(genPos, columnGenerationSteps);
@@ -22,10 +22,10 @@ package com.seibel.distanthorizons.core.file.fullDatafile;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue; import com.seibel.distanthorizons.core.generation.queues.RemoteWorldRetrievalQueue;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.LodRequestModule; import com.seibel.distanthorizons.core.generation.queues.LodRequestModule;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState; import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue; import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
@@ -1,9 +1,12 @@
package com.seibel.distanthorizons.core.file.fullDatafile.V1; package com.seibel.distanthorizons.core.file.fullDatafile.V1;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo; import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
@@ -29,6 +32,8 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("V1DTO");
protected final ReentrantLock closeLock = new ReentrantLock(); protected final ReentrantLock closeLock = new ReentrantLock();
protected volatile boolean isShutdown = false; protected volatile boolean isShutdown = false;
@@ -64,7 +69,9 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
protected FullDataSourceV1 createDataSourceFromDto(FullDataSourceV1DTO dto) throws InterruptedException, IOException, DataCorruptedException protected FullDataSourceV1 createDataSourceFromDto(FullDataSourceV1DTO dto) throws InterruptedException, IOException, DataCorruptedException
{ {
FullDataSourceV1 dataSource = FullDataSourceV1.createEmpty(dto.pos); FullDataSourceV1 dataSource = FullDataSourceV1.createEmpty(dto.pos);
try (DhDataInputStream inputStream = dto.getInputStream()) try (PhantomArrayListCheckout checkout = ARRAY_LIST_POOL.checkoutByteArrays(1);
// LZ4 was used by DH before 2.1.0 and as such must be used until the data format is changed to record the compressor)
DhDataInputStream inputStream = DhDataInputStream.create(dto.dataArray, EDhApiDataCompressionMode.LZ4, checkout))
{ {
dataSource.populateFromStream(dto, inputStream, this.level); dataSource.populateFromStream(dto, inputStream, this.level);
} }
@@ -9,8 +9,6 @@ import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen; import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhSectionPos; 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.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import java.io.File; import java.io.File;
@@ -22,7 +20,7 @@ import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class DataMigratorV1 implements IDebugRenderable, AutoCloseable public class DataMigratorV1 implements AutoCloseable
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@@ -332,12 +330,6 @@ public class DataMigratorV1 implements IDebugRenderable, AutoCloseable
// overrides // // overrides //
//===========// //===========//
@Override
public void debugRender(DebugRenderer renderer)
{
// nothing currently needed
}
@Override @Override
public void close() public void close()
{ {
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.file.fullDatafile.V2;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode; import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc; import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
@@ -30,7 +31,7 @@ import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo; import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
@@ -57,6 +58,8 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final AbstractDebugWireframeRenderer DEBUG_WIREFRAME_RENDERER = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
private static final Set<String> CORRUPT_DATA_ERRORS_LOGGED = Collections.newSetFromMap(new ConcurrentHashMap<>()); private static final Set<String> CORRUPT_DATA_ERRORS_LOGGED = Collections.newSetFromMap(new ConcurrentHashMap<>());
/** /**
@@ -108,7 +111,7 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
this.updatePropagator = new FullDataUpdatePropagatorV2(this, this.dataUpdater, this.levelId); this.updatePropagator = new FullDataUpdatePropagatorV2(this, this.dataUpdater, this.levelId);
this.dataMigratorV1 = new DataMigratorV1(this.dataUpdater, this.level, this.levelId, this.saveDir); this.dataMigratorV1 = new DataMigratorV1(this.dataUpdater, this.level, this.levelId, this.saveDir);
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataUpdateStatus); DEBUG_WIREFRAME_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataUpdateStatus);
} }
@@ -447,11 +450,10 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
//===========// //===========//
@Override @Override
public void debugRender(DebugRenderer renderer) public void debugRender(AbstractDebugWireframeRenderer renderer)
{ {
this.dataUpdater.debugRender(renderer); this.dataUpdater.debugRender(renderer);
this.updatePropagator.debugRender(renderer); this.updatePropagator.debugRender(renderer);
this.dataMigratorV1.debugRender(renderer);
} }
@Override @Override
@@ -465,6 +467,8 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
this.updatePropagator.close(); this.updatePropagator.close();
this.dataMigratorV1.close(); this.dataMigratorV1.close();
DEBUG_WIREFRAME_RENDERER.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataUpdateStatus);
this.repo.close(); this.repo.close();
} }
@@ -7,14 +7,13 @@ import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.Nullable;
import java.awt.*; import java.awt.*;
import java.util.HashMap; import java.util.HashMap;
@@ -35,11 +34,10 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
/** indicates how long the update queue thread should wait between queuing ticks */ /** indicates how long the update queue thread should wait between queuing ticks */
protected static final int PROPAGATE_QUEUE_THREAD_DELAY_IN_MS = 250; protected static final int PROPAGATE_QUEUE_THREAD_DELAY_IN_MS = 250;
public static final int NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD = 5; public static final int NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD = 10;
/** how many parent update tasks can be in the queue at once */ /** how many parent update tasks can be in the queue at once */
public static int getMaxPropagateTaskCount() public static int getMaxPropagateTaskCount() { return NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD * Config.Common.MultiThreading.numberOfThreads.get(); }
{ return NUMBER_OF_PARENT_UPDATE_TASKS_PER_THREAD * Config.Common.MultiThreading.numberOfThreads.get(); }
@@ -49,12 +47,11 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
*/ */
private final Set<Long> updatingPosSet = ConcurrentHashMap.newKeySet(); private final Set<Long> updatingPosSet = ConcurrentHashMap.newKeySet();
// TODO only run thread if modifications happened recently
/** /**
* Will be null on the dedicated server since updates don't need to be propagated, * It'd be better if we could be told when changes are available,
* only the highest detail level is needed. * then run the update thread, but having a constantly running background
* thread is simpler to deal with and gets the job done.
*/ */
@Nullable
public final ThreadPoolExecutor updateQueueProcessor; public final ThreadPoolExecutor updateQueueProcessor;
private final AtomicBoolean isShutdownRef = new AtomicBoolean(false); private final AtomicBoolean isShutdownRef = new AtomicBoolean(false);
@@ -69,6 +66,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
public FullDataUpdatePropagatorV2(FullDataSourceProviderV2 provider, FullDataUpdaterV2 dataUpdater, String levelId) public FullDataUpdatePropagatorV2(FullDataSourceProviderV2 provider, FullDataUpdaterV2 dataUpdater, String levelId)
{ {
@@ -81,11 +79,14 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
this.updateQueueProcessor.execute(this::runUpdateQueue); this.updateQueueProcessor.execute(this::runUpdateQueue);
} }
//endregion
//================// //================//
// parent updates // // parent updates //
//================// //================//
//region
private void runUpdateQueue() private void runUpdateQueue()
{ {
@@ -101,8 +102,6 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
continue; continue;
} }
// TODO it might be worth skipping this logic if no parent updates happened
// update positions closest to the player (if not on a server) // update positions closest to the player (if not on a server)
// to make world gen appear faster // to make world gen appear faster
DhBlockPos targetBlockPos = DhBlockPos.ZERO; DhBlockPos targetBlockPos = DhBlockPos.ZERO;
@@ -136,9 +135,13 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
int maxUpdateTaskCount = getMaxPropagateTaskCount(); int maxUpdateTaskCount = getMaxPropagateTaskCount();
// queue parent updates // queue parent updates
if (executor.getQueueSize() < maxUpdateTaskCount if (executor.getQueueSize() > maxUpdateTaskCount
&& this.updatingPosSet.size() < maxUpdateTaskCount) || this.updatingPosSet.size() > maxUpdateTaskCount)
{ {
return;
}
// get the positions that need to be applied to their parents // get the positions that need to be applied to their parents
LongArrayList parentUpdatePosList = this.provider.repo.getPositionsToUpdate(targetBlockPos.getX(), targetBlockPos.getZ(), maxUpdateTaskCount); LongArrayList parentUpdatePosList = this.provider.repo.getPositionsToUpdate(targetBlockPos.getX(), targetBlockPos.getZ(), maxUpdateTaskCount);
@@ -162,12 +165,17 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
{ {
// stop if there are already a bunch of updates queued // stop if there are already a bunch of updates queued
if (this.updatingPosSet.size() > maxUpdateTaskCount if (this.updatingPosSet.size() > maxUpdateTaskCount
|| executor.getQueueSize() > maxUpdateTaskCount || executor.getQueueSize() > maxUpdateTaskCount)
|| !this.updatingPosSet.add(parentUpdatePos))
{ {
break; break;
} }
// skip any already-queued positions
if (!this.updatingPosSet.add(parentUpdatePos))
{
continue;
}
try try
{ {
executor.execute(() -> executor.execute(() ->
@@ -253,7 +261,6 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
} }
} }
} }
}
/** stops if it finds any LOD data */ /** stops if it finds any LOD data */
private void runChildUpdates(PriorityTaskPicker.Executor executor, DhBlockPos targetBlockPos) private void runChildUpdates(PriorityTaskPicker.Executor executor, DhBlockPos targetBlockPos)
{ {
@@ -371,17 +378,20 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
} }
} }
//endregion
//===========// //===========//
// overrides // // overrides //
//===========// //===========//
//region
@Override @Override
public void debugRender(DebugRenderer renderer) public void debugRender(AbstractDebugWireframeRenderer renderer)
{ {
this.updatingPosSet this.updatingPosSet
.forEach((pos) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 80f, 0.20f, Color.MAGENTA)); }); .forEach((pos) -> { renderer.renderBox(new AbstractDebugWireframeRenderer.Box(pos, -32f, 80f, 0.20f, Color.MAGENTA)); });
} }
@Override @Override
@@ -393,6 +403,8 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
} }
} }
//endregion
} }
@@ -7,7 +7,7 @@ import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListen
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.threading.PositionalLockProvider; import com.seibel.distanthorizons.core.util.threading.PositionalLockProvider;
@@ -225,20 +225,18 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable
//===========// //===========//
@Override @Override
public void debugRender(DebugRenderer renderer) public void debugRender(AbstractDebugWireframeRenderer renderer)
{ {
this.lockedPosSet this.lockedPosSet
.forEach((pos) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 74f, 0.15f, Color.PINK)); }); .forEach((pos) -> { renderer.renderBox(new AbstractDebugWireframeRenderer.Box(pos, -32f, 74f, 0.15f, Color.PINK)); });
this.queuedUpdateCountsByPos this.queuedUpdateCountsByPos
.forEach((pos, updateCountRef) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 80f + (updateCountRef.get() * 16f), 0.20f, Color.WHITE)); }); .forEach((pos, updateCountRef) -> { renderer.renderBox(new AbstractDebugWireframeRenderer.Box(pos, -32f, 80f + (updateCountRef.get() * 16f), 0.20f, Color.WHITE)); });
} }
@Override @Override
public void close() public void close() { this.isShutdownRef.set(true); }
{
this.isShutdownRef.set(true);
}
} }
@@ -20,13 +20,14 @@
package com.seibel.distanthorizons.core.generation; package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
import com.seibel.distanthorizons.core.util.FullDataPointUtil; import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
@@ -52,6 +53,8 @@ public class DhLightingEngine
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static final DhLightingEngine INSTANCE = new DhLightingEngine(); public static final DhLightingEngine INSTANCE = new DhLightingEngine();
private static final AbstractDebugWireframeRenderer DEBUG_RENDERER = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
/** /**
* Minor garbage collection optimization. <br> * Minor garbage collection optimization. <br>
* Since these objects are always mutated anyway, using a {@link ThreadLocal} will allow us to * Since these objects are always mutated anyway, using a {@link ThreadLocal} will allow us to
@@ -725,9 +728,9 @@ public class DhLightingEngine
// a color can be set to null if you only want to troubleshoot up to a certain light level // a color can be set to null if you only want to troubleshoot up to a certain light level
if (color != null) if (color != null)
{ {
DebugRenderer.makeParticle( DEBUG_RENDERER.makeParticle(
new DebugRenderer.BoxParticle( new AbstractDebugWireframeRenderer.BoxParticle(
new DebugRenderer.Box(DhSectionPos.encode((byte) 0, chunkMinX + x, chunkMinZ + z), y, y + 1, 0.2f, color), new AbstractDebugWireframeRenderer.Box(DhSectionPos.encode((byte) 0, chunkMinX + x, chunkMinZ + z), y, y + 1, 0.2f, color),
10.0, 0f 10.0, 0f
) )
); );
@@ -9,7 +9,6 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider; import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.util.FormatUtil; import com.seibel.distanthorizons.core.util.FormatUtil;
@@ -93,7 +92,8 @@ public class PregenManager
private final AtomicInteger nextSectionSpiralIndex = new AtomicInteger(0); private final AtomicInteger nextSectionSpiralIndex = new AtomicInteger(0);
private final AtomicLong lastTaskFinishTime = new AtomicLong(System.currentTimeMillis()); private final AtomicLong lastTaskFinishTime = new AtomicLong(System.currentTimeMillis());
private final RollingAverage averageTaskCompletionIntervalMs = new RollingAverage(1000); private RollingAverage averageTaskCompletionIntervalMs = new RollingAverage(1000);
private final RollingAverage averageTaskCompletionIntervalMsShort = new RollingAverage(50);
private final AtomicLong lastLogTime = new AtomicLong(); private final AtomicLong lastLogTime = new AtomicLong();
@@ -112,6 +112,7 @@ public class PregenManager
long timeSincePreviousTaskFinish = System.currentTimeMillis() - this.lastTaskFinishTime.getAndSet(System.currentTimeMillis()); long timeSincePreviousTaskFinish = System.currentTimeMillis() - this.lastTaskFinishTime.getAndSet(System.currentTimeMillis());
this.averageTaskCompletionIntervalMs.add(timeSincePreviousTaskFinish); this.averageTaskCompletionIntervalMs.add(timeSincePreviousTaskFinish);
this.averageTaskCompletionIntervalMsShort.add(timeSincePreviousTaskFinish);
PregenState.this.fillPendingQueue(); PregenState.this.fillPendingQueue();
}) })
@@ -182,6 +183,13 @@ public class PregenManager
int chunkRatePerSecond = (int) (1000 / this.averageTaskCompletionIntervalMs.getAverage() * 4 * 4); int chunkRatePerSecond = (int) (1000 / this.averageTaskCompletionIntervalMs.getAverage() * 4 * 4);
double etaMs = this.averageTaskCompletionIntervalMs.getAverage() * (this.sectionsToGenerate - this.nextSectionSpiralIndex.get()); double etaMs = this.averageTaskCompletionIntervalMs.getAverage() * (this.sectionsToGenerate - this.nextSectionSpiralIndex.get());
// Reset long rolling average if short average diverged too much (<0.5 / >2.0)
double averageRatio = this.averageTaskCompletionIntervalMsShort.getAverage() / this.averageTaskCompletionIntervalMs.getAverage();
if (averageRatio < 0.5 || averageRatio > 2.0)
{
this.averageTaskCompletionIntervalMs = new RollingAverage(1000);
}
return MessageFormat.format("Generated radius: {0,number,#.###} / {1,number,#.#} chunks ({2} cps, {3,number,#.###%}), ETA: {4}", return MessageFormat.format("Generated radius: {0,number,#.###} / {1,number,#.#} chunks ({2} cps, {3,number,#.###%}), ETA: {4}",
this.generatedRadius.getValue(), this.generatedRadius.getValue(),
chunksToGenerate, chunksToGenerate,

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