Compare commits

...

425 Commits

Author SHA1 Message Date
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
s809 2a4bfef7a6 Add correct descriptions 2026-01-18 22:38:01 +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
s809 7be65a2258 Split off server generation into a separate toggle 2026-01-17 01:43:05 +05:00
s809 1a540cf2bc Make sure payload chunk is readable 2026-01-14 22:17:46 +05:00
James Seibel 20fc2efb46 Improve concurrent iterating in QuadTree 2026-01-10 17:03:43 -06:00
James Seibel d8beba2498 minor cleanup in LodBufferContainer cleanup 2026-01-10 17:02:56 -06:00
James Seibel 9f0cb5a394 Add forge specific icon/logo
Done to fix a forge limitation where logos can't contain a file pathhttps://github.com/MinecraftForge/MinecraftForge/issues/7348
2026-01-10 11:56:08 -06:00
James Seibel df63401d11 DB updater use correct classloader 2026-01-10 08:21:09 -06:00
James Seibel db95951ade minor reformat and comment 2026-01-10 08:20:44 -06:00
s809 1e020f93a6 Reapply "Run plugin messages on a DH thread"
This reverts commit ff3145336d.
2026-01-09 20:29:23 +05:00
James Seibel 7aee6dfb44 Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons-core 2026-01-07 07:50:25 -06:00
James Seibel 546a51a295 expand distant beacon beams for visiblity 2026-01-07 07:50:22 -06:00
James Seibel ec7e791e9f Change EMinecraftColor -> MinecraftTextFormat
No need for an enum when all the values are strings
2026-01-06 07:10:40 -06:00
James Seibel d60dec3d82 Merge branch 'main' into 'main'
Fix typo in high vanilla render distance warning

See merge request distant-horizons-team/distant-horizons-core!94
2026-01-05 13:00:17 +00:00
s809 89a80103f0 Wrong message target 2026-01-04 20:04:30 +05:00
s809 8e14a7223c Add a chat message for incompatible messages 2026-01-04 19:36:24 +05:00
meanwhile131 7cf1e901f5 Fix typo in high vanilla render distance warning 2026-01-01 15:06:30 +04:00
James Seibel ba923fa829 Fix neoforge thread causing resource loading to fail 2025-12-26 14:13:27 -06:00
s809 505dbe2f62 Replace the failure state with future exceptions 2025-12-27 00:51:30 +05:00
James Seibel 48c5828e8f up version number 2.4.5 -> 2.4.6-dev 2025-12-24 22:41:27 -06:00
James Seibel eb2317934f up version number 2.4.4 -> 2.4.5 2025-12-24 22:06:53 -06:00
James Seibel 60537cda1b Replace MC color code strings with an enum 2025-12-24 22:04:50 -06:00
James Seibel 508ff2b776 Fix null pointer in ChunkUpdateQueueManager 2025-12-24 21:53:39 -06:00
James Seibel 7c4ac2bd7e remove dev from version number 2025-12-23 22:55:40 -06:00
James Seibel 8c13c2cf47 Fix toggling world gen not recreating queue 2025-12-23 22:55:40 -06:00
James Seibel 802019ff72 up DH api version 5.0.0 -> 5.1.0 2025-12-23 20:01:06 -06:00
James Seibel 141890556c Revert "remove deprecated getHeight() from DhApiLevelWrapper"
This reverts commit 50bdb73a52.
2025-12-23 19:56:28 -06:00
James Seibel 353838db41 add experimental option to ignore rendering dimensions by name 2025-12-23 12:22:00 -06:00
James Seibel f1547477c9 add clientLevelWrapper to DhApiRenderParam 2025-12-23 12:20:42 -06:00
James Seibel 535a645a84 minor internal API cleanup 2025-12-23 12:19:14 -06:00
James Seibel 2dc7f02b32 Remove experimental option onlyLoadCenterLods
option is now merged into main
2025-12-23 12:18:28 -06:00
James Seibel 50bdb73a52 remove deprecated getHeight() from DhApiLevelWrapper
use getMaxHeight() instead
2025-12-23 12:06:15 -06:00
James Seibel 53e6c95432 commenting DhTerrainShaderProgram 2025-12-23 08:57:51 -06:00
James Seibel 36f0029e45 Fix earth curvature shader compiling 2025-12-23 08:47:44 -06:00
s809 5067e970a2 Use another method to create a buffer 2025-12-23 12:50:02 +05:00
James Seibel 167ca94e69 Remove deprecated disableVanillaFog config 2025-12-22 20:31:24 -06:00
James Seibel 8d94b86bfd Hide network config changes by default 2025-12-22 14:51:29 -06:00
James Seibel a29567430e Net only log changed config values 2025-12-22 14:37:34 -06:00
James Seibel fb2dae48e2 re-enable remote timestamp getting 2025-12-22 14:21:12 -06:00
James Seibel 948b4bfd9c comment out debug log 2025-12-22 14:17:57 -06:00
James Seibel ca44256ca9 disable full data debug phantom array stacks 2025-12-22 14:17:47 -06:00
James Seibel a29b6a5aab remove unnecessary config appearance check 2025-12-22 14:17:13 -06:00
James Seibel 868254ccc8 try fixing rare leak in delayed data source cache
Didn't fix the problem, but shouldn't hurt
2025-12-22 14:16:34 -06:00
James Seibel 195fde8d73 quad tree spilt request cleanup 2025-12-22 13:58:26 -06:00
James Seibel ce7b9b94b6 fix/improve world gen/retrieval error handling 2025-12-22 13:58:26 -06:00
James Seibel 1f0c2e286a fix network splitting requests 2025-12-22 13:58:26 -06:00
James Seibel f79fd5e06f error handling in AbstactDhLevel chunk update 2025-12-22 13:58:26 -06:00
James Seibel 47c1d3955f failed attmpt to fix leaks
Breaks split world gen requests
2025-12-22 13:57:49 -06:00
James Seibel 2c5f5a3d4c minor refactors 2025-12-22 09:46:21 -06:00
James Seibel 81c533051e close errored data sources in full data provider 2025-12-22 08:35:15 -06:00
James Seibel 5cbe5ecfd8 Fix dis/re-enabling world gen queuing 2025-12-21 19:44:48 -06:00
James Seibel d4b4d28c9f Fix null error log in Data source provider 2025-12-21 08:53:27 -06:00
James Seibel b8e653b5f7 Fix phantom checkout not updating stack trace 2025-12-21 08:52:44 -06:00
James Seibel 80fea09598 Fix concurrency error in LodQuadTree 2025-12-21 08:52:25 -06:00
James Seibel 1d4f914a9f Merge branch 'worldGenRefactor' 2025-12-20 10:53:39 -06:00
James Seibel bf92dea2eb reduce stuttering at the cost of lighting quality 2025-12-20 10:52:51 -06:00
s809 2dd675b8da Handle generated LOD updates outside the render thread 2025-12-20 15:22:26 +05:00
s809 ff3145336d Revert "Run plugin messages on a DH thread"
This reverts commit 280181c91e.
2025-12-20 14:32:39 +05:00
James Seibel 280181c91e Run plugin messages on a DH thread 2025-12-19 16:54:29 -06:00
James Seibel 60232e713b refactor world gen queue 2025-12-19 16:54:07 -06:00
James Seibel 55d9030954 Remove extra particle for world gen 2025-12-18 10:20:01 -06:00
James Seibel 452bd75f5d remove chunkWrapper.isStillValid() 2025-12-18 10:18:07 -06:00
James Seibel 72be1e2602 Remove LodRenderSection.isFullyGenerated() 2025-12-18 10:17:36 -06:00
James Seibel 1c30213aca up version number 2.4.3 -> 2.4.4-dev 2025-12-18 10:04:41 -06:00
James Seibel e9a044308f remove dev from version number 2025-12-18 09:35:07 -06:00
James Seibel 1aabc0c792 remove chunkWrapper.isStillValid() 2025-12-18 09:35:02 -06:00
James Seibel 4a1513ed65 fix compiling 2025-12-17 22:41:22 -06:00
James Seibel 6d98c9cb84 start world gen refactoring 2025-12-17 22:39:23 -06:00
James Seibel b1b0642fbe LodRenderSection commenting/regions 2025-12-17 09:32:12 -06:00
James Seibel eecb28d11f Fix GLProxy error in multiplayer
Make some GLProxy methods static to prevent setup order issues
2025-12-17 09:02:07 -06:00
James Seibel 90564f2537 fix javadoc in LevelWrapper 2025-12-16 16:39:03 -06:00
James Seibel ded0b979cf Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons-core 2025-12-16 14:45:57 -06:00
James Seibel ed9cc5485c Add SSAO fade out distance 2025-12-16 14:45:53 -06:00
s809 cbd5974657 Fix packet handle errors not showing on F3 screen 2025-12-17 00:15:55 +05:00
James Seibel 0e5fba58ab minor shader program refactor 2025-12-16 09:13:22 -06:00
James Seibel 2943e63382 slight light engine optimization 2025-12-15 14:37:15 -06:00
James Seibel 30564aade7 up version number 2.4.2 -> 2.4.3-dev 2025-12-15 10:17:28 -06:00
James Seibel aabb90ada6 remove dev from version number 2025-12-15 09:00:15 -06:00
James Seibel 963a8dc53f comment LevelWrapper getDimensionName() 2025-12-15 08:55:40 -06:00
James Seibel aa6d69385b Move GC warning into the log 2025-12-15 08:44:06 -06:00
James Seibel f42c9cf8fb Improve initial library check error handling 2025-12-14 22:29:08 -06:00
James Seibel 92e0011c8d Fix auto update success dialog 2025-12-14 21:50:56 -06:00
James Seibel c20d95a7c7 improve spacing for self updater version log 2025-12-14 21:21:45 -06:00
James Seibel 353aa1ed2c maybe improve ZStd version check 2025-12-14 21:20:42 -06:00
James Seibel 5aa43ebcc8 hide LODs when underwater 2025-12-14 17:22:35 -06:00
James Seibel b6145461b6 add note to ignored block CSV 2025-12-14 17:02:53 -06:00
James Seibel 478e431076 up version number 2.4.1 -> 2.4.2-dev 2025-12-14 17:00:34 -06:00
James Seibel 6feb7f1b42 remove dev from version number 2025-12-14 13:46:04 -06:00
James Seibel 016fc66293 Print a warning if G1GC is used
G1GC is known to cause stuttering
2025-12-13 16:46:59 -06:00
James Seibel 6d3e30d425 add Zstd decompress lib check in initalizer 2025-12-13 15:48:05 -06:00
James Seibel 5be5c5a5bc replace client ticks with a timer
Prevents DH loading issues when MC ticks are paused
2025-12-13 11:19:33 -06:00
James Seibel ed5aeb8951 minor texture setup reformatting 2025-12-13 10:43:01 -06:00
James Seibel 7f0ddadf26 up version number 2.4.0 -> 2.4.1-dev 2025-12-13 10:20:44 -06:00
James Seibel a2c61ed278 up version number 2.3.7 -> 2.4.0 2025-12-13 10:19:50 -06:00
James Seibel 99eb4ac8a1 Fix infinite loop in DhSectionPos 2025-12-13 09:10:12 -06:00
James Seibel c75902d9d6 debug particle cleanup 2025-12-13 08:50:15 -06:00
James Seibel 1743949ba5 fix GeneratedFullDataSourceProvider not adding update listener 2025-12-13 08:49:45 -06:00
James Seibel a74a37a0e8 world gen queue refactoring 2025-12-13 08:49:31 -06:00
James Seibel 4ed7941288 fix missing localization 2025-12-12 07:45:12 -06:00
James Seibel ec59a5f754 comment cleanup and enum renaming for API use 2025-12-11 07:35:37 -06:00
James Seibel 895e04b7cc Remove unused wrapper functions and refactor 2025-12-10 18:50:35 -06:00
James Seibel 8f0930fa02 Allow world gen limits on singleplayer 2025-12-10 07:09:29 -06:00
James Seibel c1c4328fa5 rename API getSoftCache -> createSoftCahe 2025-12-09 20:57:27 -06:00
James Seibel 91240e4f7a disable mip-mapping on textures
necessary to fix MC 1.21.11 rendering
2025-12-09 20:57:09 -06:00
James Seibel 17c61a97cc revert long windows filepath char 2025-12-09 07:21:40 -06:00
James Seibel b78b852ffb Merge branch 'batchGenRefactor' 2025-12-09 07:16:18 -06:00
James Seibel 26d4220967 Add logging/messaging for corrupted DB files 2025-12-09 07:12:33 -06:00
James Seibel 5edc73cc03 enable long file paths for the config file 2025-12-06 12:28:22 -06:00
James Seibel 6fcfc9379e Fix repo unit tests 2025-12-06 12:27:53 -06:00
James Seibel 149fbccfa5 Merge branch 'batchGenRefactor' 2025-12-06 12:19:17 -06:00
James Seibel 5ca754d2ac Fix world gen progress config resetting on reboot 2025-12-06 09:18:34 -06:00
James Seibel f13744e858 Add thread pool priority setting
Setting this to 1 higher than C2ME can reduce issues with Chunky overwhelming DH.
2025-12-05 07:35:16 -06:00
James Seibel 64ac218003 Improve empty LOD debugging slightly 2025-12-05 07:28:57 -06:00
James Seibel 385bd326cf minor world gen related refactoring 2025-12-04 07:39:09 -06:00
James Seibel 4e9559f230 enable long file paths on windows for the DB 2025-12-02 07:07:17 -06:00
James Seibel 6ea864ef6b TEST 2025-11-29 09:59:33 -06:00
James Seibel 4e96728c25 maybe fix concurrency error during world gen shutdown 2025-11-28 16:29:47 -06:00
James Seibel 1c44ef7f0c minor reformatting 2025-11-28 16:23:36 -06:00
James Seibel 227d0d09ba fix getDataPointAtBlockPos() relative Y 2025-11-28 15:53:47 -06:00
James Seibel d7ba3fa724 fix LOD only mode when transparency is disabled 2025-11-28 15:53:38 -06:00
James Seibel 7e46adf469 add the ability to ignore update chunk pos 2025-11-28 10:48:42 -06:00
James Seibel f43e2fa441 don't render thick snow layers 2025-11-28 09:39:03 -06:00
James Seibel f9819d3d46 fix vanilla fading for MC versions before 1.21.5 2025-11-28 08:42:20 -06:00
James Seibel 19b23bea5f add slow world gen warning config 2025-11-27 09:59:16 -06:00
James Seibel d1c0f7ebb4 Update .editorconfig 2025-11-26 13:55:33 -06:00
James Seibel 5a4ddafbbb Z_std_stream localization 2025-11-26 13:52:17 -06:00
James Seibel 7c40d96f2e DhApiTerrainDataPoint to string 2025-11-26 13:52:07 -06:00
James Seibel b535be16c0 auto merge API world gen data
done to reduce memory use with broken API world generators
2025-11-26 13:51:58 -06:00
James Seibel 22f5608f9a hide the compressor config option 2025-11-24 14:31:42 -06:00
James Seibel a498422843 stream cleanup 3 2025-11-24 14:30:17 -06:00
James Seibel bfd6efb4a4 handle ZStd streams 2025-11-24 14:28:06 -06:00
James Seibel c8c9df3a34 data stream cleanup 2025-11-24 14:15:23 -06:00
James Seibel 3349e5b898 clean up DhDataInputStream 2025-11-24 13:51:48 -06:00
James Seibel ed7511ff6a proof-of-concept block Zstd compression 2025-11-24 12:40:49 -06:00
James Seibel 8516e8f9ab re-enable varint unit tests 2025-11-24 12:38:34 -06:00
James Seibel 47a4d1535f minor variable refactoring 2025-11-22 11:01:53 -06:00
James Seibel 33a55dc7cd Delete EventTimer.java 2025-11-22 09:30:00 -06:00
James Seibel 1b4f9e8942 minor throw/this cleanup 2025-11-22 09:24:31 -06:00
James Seibel 2537c4a259 Rename IBatchGeneratorEvnWrapper 2025-11-22 08:16:30 -06:00
James Seibel b74b6e8068 minor RollingAverage refactor 2025-11-22 08:16:11 -06:00
James Seibel 25979d6a76 Move some exception logic into ExceptionUtil 2025-11-21 06:59:03 -06:00
James Seibel 3f287388d5 re-add biome blending to API config options 2025-11-18 07:42:43 -06:00
James Seibel 72d2ba6aae comment out phantom buffer cleanup log 2025-11-18 07:32:58 -06:00
James Seibel 611ed4e24a add mod note in memory low message 2025-11-18 07:32:48 -06:00
James Seibel eac7a38e73 hopefully reduce the chance of downsampling holes 2025-11-18 07:32:18 -06:00
James Seibel afd7da7763 Optimize full data update processing 2025-11-18 07:16:50 -06:00
James Seibel ff7abb6a18 Fix rendering when Iris isn't installed 2025-11-16 16:11:40 -06:00
James Seibel ca3f5da5de Add unit test for data source merging speed 2025-11-16 15:30:16 -06:00
James Seibel 69012ab7e6 rename and cleanup data source update methods 2025-11-16 15:29:13 -06:00
James Seibel e5e502b4f8 Remove unused/broken FullData LevelMinY 2025-11-15 19:09:16 -06:00
James Seibel 42dc0903de Fix shaders when far clip fading is active 2025-11-15 18:20:47 -06:00
James Seibel 4b20637e47 Fix WorldGen after restarting generation 2025-11-15 12:07:53 -06:00
James Seibel 3257ae8480 replace server tick/world gen tick with a timer 2025-11-15 09:47:15 -06:00
James Seibel a6ddc561a0 up protocol version 12 -> 13 2025-11-15 09:42:38 -06:00
James Seibel 7c82c9eb7b add adj data to DTO en/decoding 2025-11-15 09:42:20 -06:00
James Seibel 3c62e18502 Fix gitlab getter Long/Int cast 2025-11-15 07:55:56 -06:00
James Seibel eea5198fb6 Merge branch 'adjData' 2025-11-14 07:46:37 -06:00
James Seibel b82a59ecbc Speed up shutdown and reduce logging 2025-11-14 07:46:02 -06:00
James Seibel 6bfcf36687 Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons-core 2025-11-13 07:19:19 -06:00
James Seibel 6fe0df7d0f Don't duplicate adjacent data 2025-11-13 07:18:09 -06:00
James Seibel b9746381eb Add varint encoding for full data
Closes Merge !93
Thanks Ryan Hitchman!
2025-11-12 07:21:54 -06:00
s809 91dffa3c3e Prevent auto-pause while pregen is running 2025-11-11 23:48:13 +05:00
James Seibel 6eb24ecde1 re-add GPU upload config including "none" 2025-11-10 07:33:03 -06:00
James Seibel 767753c004 add logging to infinite repo unit test 2025-11-10 06:56:24 -06:00
James Seibel 97442f8833 Fix config min/max validation default setup 2025-11-08 19:11:56 -06:00
James Seibel 62359e3dde remove LOD load pref logging 2025-11-08 19:08:30 -06:00
James Seibel b5199cfa87 Optimize ColumnBox building 2025-11-08 18:08:02 -06:00
James Seibel f0acc73c56 Add compass Index to Edirection 2025-11-08 17:48:30 -06:00
James Seibel f9dfc38bf1 Separate BlockBiomeWrapperPair from FullDataPointIdMap 2025-11-08 17:47:50 -06:00
James Seibel 5c5d39738e minor reformating 2025-11-08 17:44:08 -06:00
James Seibel 27fb629c22 default unsafe UI values to config option 2025-11-08 17:41:07 -06:00
James Seibel c374bf7ca8 test 2025-11-08 08:14:03 -06:00
James Seibel 7e04b12e37 Optimize PrefRecorder slightly 2025-11-07 07:41:59 -06:00
James Seibel 67637dbf10 detail level renaming 2025-11-06 21:50:43 -06:00
James Seibel 6456651d27 Handle non-adjacent data conversion 2025-11-06 21:28:25 -06:00
James Seibel 9343854b4a Clean up data source getters 2025-11-06 07:42:58 -06:00
James Seibel 5fd8ed840f Add adjacent data to FullDataDTO for faster loading 2025-11-06 07:35:23 -06:00
James Seibel 4d4d8fd8e9 Split up full data source provider into multiple classes 2025-11-04 07:46:06 -06:00
James Seibel bf05965015 remove IDataSource 2025-11-02 07:20:07 -06:00
James Seibel 47569f2b3c minor dataSourceHandler refactor 2025-11-01 16:33:07 -04:00
James Seibel 0567195f73 minor datasource renaming 2025-11-01 16:27:54 -04:00
James Seibel e355366ffc Clean up EDhDirection 2025-11-01 09:06:53 -04:00
James Seibel 3681d50eb2 minor comment cleanup 2025-11-01 08:42:25 -04:00
James Seibel e0c143881f Fix compression mode javadoc 2025-11-01 08:34:02 -04:00
James Seibel 2a49fdee7f Add experimental loading option and perfRecorder 2025-10-28 07:46:53 -05:00
James Seibel f39e06b6dc remove unused interrupt check 2025-10-28 07:36:28 -05:00
James Seibel 0d5c454dd4 remove unused ColumnQuadView methods 2025-10-28 07:24:24 -05:00
James Seibel 1b447fdc98 Fix logger builder doubling DH name 2025-10-28 07:23:58 -05:00
James Seibel d84ba05380 minor style reformatting 2025-10-27 06:52:36 -05:00
James Seibel 3e7f160fcd Merge Fade apply shaders 2025-10-25 11:54:32 -05:00
James Seibel dcaf334828 use same fade apply frag shader 2025-10-25 11:39:27 -05:00
James Seibel 789306ccff Add far clip fading 2025-10-25 11:06:19 -05:00
James Seibel e33fa3cb5e Rename fade renderer -> Vanilla Fade renderer 2025-10-25 09:36:11 -05:00
James Seibel 8f99117066 Fix iris not setting face culling in the MC state manager 2025-10-25 08:38:29 -05:00
James Seibel 2136c0fe83 framebuffer name consistency fix 2025-10-25 08:37:11 -05:00
James Seibel 7a6cffe19d Move getKeyedLevelDimensionName() to implementation 2025-10-23 07:17:46 -05:00
James Seibel 06bef93c82 run occlusion culling whenever saving a LOD
Also run culling for every column in an LOD, which improves compression by about 20%
- Thanks Scaevolus
2025-10-22 07:25:04 -05:00
James Seibel 939e45ce62 minor RenderBufferHandler optimization and bugfix 2025-10-19 16:40:57 -05:00
James Seibel 7f958269e4 Fix not reloading LODs on horizontal quality change 2025-10-19 16:16:35 -05:00
James Seibel 07e3091d13 Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons-core 2025-10-19 16:06:24 -05:00
James Seibel f7ece2b02e Clean up LodRendering logic 2025-10-19 16:06:00 -05:00
s809 bd796c2ce0 Fix handling of empty server keys 2025-10-19 22:58:07 +05:00
s809 4e6be35da9 Merge branch 'feature/server-keys' 2025-10-19 22:57:50 +05:00
James Seibel 0e0e1e1b0f Make LodRenderer a singleton 2025-10-18 11:42:36 -05:00
James Seibel f4ab101403 Dh and level wrapper refactoring and commenting 2025-10-17 07:21:16 -05:00
James Seibel 0902d3f0f5 merge loggers and add logger builder 2025-10-15 17:37:08 -05:00
James Seibel 75c2758fd5 up version number 2.3.6 -> 2.3.7 2025-10-13 18:03:19 -05:00
James Seibel 9ddd917f3b remove dev from version number 2025-10-13 16:32:29 -05:00
James Seibel c5945b1254 remove unused EConfigEntryPerformance
It is a cool idea, but one that unfortunately never got implemented
2025-10-13 07:46:53 -05:00
James Seibel 9060579615 Fix world gen progress ui button 2025-10-13 07:42:46 -05:00
James Seibel 8f0217185f Improve config gui object casting 2025-10-13 07:33:27 -05:00
James Seibel 656971b0b9 typo fixing for major config refactoring 2025-10-12 21:10:18 -05:00
James Seibel 5fa3a11024 major config backend refactoring 2025-10-12 20:56:15 -05:00
James Seibel ed3d00bfce up version number 2.3.5 -> 2.3.6 2025-10-11 20:55:36 -05:00
s809 034ec7d656 Bump protocol version 2025-08-16 21:01:45 +05:00
s809 fb5e15a2f1 Add a server keys feature 2025-08-16 20:59:28 +05:00
426 changed files with 20686 additions and 24473 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ insert_final_newline = false
max_line_length = 1000
tab_width = 4
trim_trailing_whitespace = false
ij_continuation_indent_size = 8
ij_continuation_indent_size = 4
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = true
+20 -4
View File
@@ -2,10 +2,26 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id "java"
id "com.github.johnrengelman.shadow" version '8.1.1' apply false
id "com.gradleup.shadow"
}
repositories {
mavenCentral()
}
tasks.withType(JavaCompile).configureEach {
options.release = 8
options.encoding = "UTF-8"
}
configurations {
testImplementation.extendsFrom compileOnly
}
dependencies {
compileOnly "org.apache.logging.log4j:log4j-api:${rootProject.log4j_version}"
testImplementation "junit:junit:4.13"
}
shadowJar {
// required for basic shadowJar setup
@@ -21,7 +37,7 @@ task addSourcesToCompiledJar(type: ShadowJar) {
doFirst {
System.out.println("Adding source files from: \n" +
"[" + sourceJarPath + "] to compiled API jar: \n" +
"[" + shadowJar.archivePath + "]")
"[" + shadowJar.archiveFile.get().asFile + "]")
// Validate the input JAR file
if (!secondJarFile.exists()) {
@@ -42,7 +58,7 @@ task addSourcesToCompiledJar(type: ShadowJar) {
}
// set the jars to merge
from shadowJar.archivePath
from shadowJar.archiveFile.get().asFile
from secondJarFile
// alternative method to Include the source files in the combined JAR
@@ -39,10 +39,6 @@ package com.seibel.distanthorizons.api.enums;
*/
public enum EDhApiDetailLevel
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/**
* detail level: 0 <Br>
* width in Blocks: 1
@@ -28,10 +28,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/
public enum EDhApiBlocksToAvoid
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
NONE(false),
NON_COLLIDING(true);
@@ -22,7 +22,9 @@ package com.seibel.distanthorizons.api.enums.config;
/**
* UNCOMPRESSED <br>
* LZ4 <br>
* XZ <br><br>
* Z_STD <br>
* Z_STD_STREAM <br>
* LZMA2 <br><br>
*
* Note: speed and compression ratios are examples
* and should only be used for estimated comparisons.
@@ -32,10 +34,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/
public enum EDhApiDataCompressionMode
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/**
* Should only be used internally and for unit testing. <br><br>
*
@@ -56,17 +54,31 @@ public enum EDhApiDataCompressionMode
LZ4(1),
/**
* Decent speed and good compression. <br><br>
* Great speed and good compression. <br><br>
*
* Read Speed: 2.1 MS / DTO <br>
* Write Speed: 4.9 MS / DTO <br>
* Compression ratio: 0.2606 <br>
*/
Z_STD_BLOCK(4),
/**
* Similar to {@link EDhApiDataCompressionMode#Z_STD_BLOCK}
* except slower. <br><br>
*
* This option is only provided for legacy support when processing old databases. <br><br>
*
* Read Speed: 9.31 MS / DTO <br>
* Write Speed: 15.13 MS / DTO <br>
* Compression ratio: 0.2606 <br>
*/
Z_STD(2),
@Deprecated
@DisallowSelectingViaConfigGui
Z_STD_STREAM(2),
/**
* Extremely slow, but very good compression. <br><br>
* Extremely slow, but very good compression. <br>
* Often causes whole computer stuttering due to memory bandwidth saturation. <br><br>
*
* Read Speed: 13.29 MS / DTO <br>
* Write Speed: 70.95 MS / DTO <br>
@@ -35,25 +35,12 @@ public enum EDhApiGpuUploadMethod
/** Picks the best option based on the GPU the user has. */
AUTO(false, false),
// commented out since it isn't currently in use
//BUFFER_STORAGE_MAPPING(true, true),
/** Fast rendering, no stuttering. */
BUFFER_STORAGE(false, true),
/** Fast rendering but may stutter when uploading. */
SUB_DATA(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. */
DATA(false, false);
@@ -29,10 +29,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/
public enum EDhApiGrassSideRendering
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
AS_GRASS,
FADE_TO_DIRT,
AS_DIRT;
@@ -31,11 +31,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/
public enum EDhApiHorizontalQuality
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
// Note: any quadraticBase less than 2.0f has issues with DetailDistanceUtil, and will always return the lowest detail level.
// So for now we are limiting the lowest value to 2.0
// LOWEST was originally 1.0f and LOW was 1.5f
@@ -29,10 +29,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/
public enum EDhApiLodShading
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/**
* Uses Minecraft's shading for LODs. <Br>
* This means if Minecraft's shading is disabled DH's shading will be as well.
@@ -17,23 +17,37 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.generation.tasks;
package com.seibel.distanthorizons.api.enums.config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import javax.annotation.Nullable;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import org.apache.logging.log4j.Level;
/**
* @author Leetom
* @version 2022-11-25
* ALL
* DEBUG
* INFO
* WARN
* ERROR
* DISABLED
*
* @since API 5.0.0
* @version 2024-4-6
*/
public interface IWorldGenTaskTracker
public enum EDhApiLoggerLevel
{
@Nullable
Consumer<FullDataSourceV2> getDataSourceConsumer();
// ordered from most to least broad
ALL(Level.ALL),
DEBUG(Level.DEBUG),
INFO(Level.INFO),
WARN(Level.WARN),
ERROR(Level.ERROR),
DISABLED(Level.OFF),
;
public final Level level;
EDhApiLoggerLevel(Level level)
{ this.level = level; }
CompletableFuture<Boolean> shouldGenerateSplitChild(long pos);
}
@@ -1,55 +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.api.enums.config;
import org.apache.logging.log4j.Level;
/**
* @since API 2.0.0
* @version 2024-4-6
*/
public enum EDhApiLoggerMode
{
DISABLED(Level.OFF, Level.OFF),
LOG_ALL_TO_FILE(Level.ALL, Level.OFF),
LOG_ERROR_TO_CHAT(Level.ALL, Level.ERROR),
LOG_WARNING_TO_CHAT(Level.ALL, Level.WARN),
LOG_INFO_TO_CHAT(Level.ALL, Level.INFO),
LOG_DEBUG_TO_CHAT(Level.ALL, Level.DEBUG),
LOG_ALL_TO_CHAT(Level.ALL, Level.ALL),
LOG_ERROR_TO_CHAT_AND_FILE(Level.ERROR, Level.ERROR),
LOG_WARNING_TO_CHAT_AND_FILE(Level.WARN, Level.WARN),
LOG_INFO_TO_CHAT_AND_FILE(Level.INFO, Level.INFO),
LOG_DEBUG_TO_CHAT_AND_FILE(Level.DEBUG, Level.DEBUG),
LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.WARN),
LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.ERROR),
LOG_ERROR_TO_CHAT_AND_WARNING_TO_FILE(Level.ERROR, Level.WARN),
;
public final Level levelForFile;
public final Level levelForChat;
EDhApiLoggerMode(Level levelForFile, Level levelForChat)
{
this.levelForFile = levelForFile;
this.levelForChat = levelForChat;
}
}
@@ -64,10 +64,7 @@ public enum EDhApiMaxHorizontalResolution
/** How wide each LOD DataPoint is */
public final int dataPointWidth;
/**
* This is the same as detailLevel in LodQuadTreeNode,
* lowest is 0 highest is 9
*/
/** This is the same as detailLevel in LodQuadTreeNode */
public final byte detailLevel;
/* Start/End X/Z give the block positions
@@ -33,10 +33,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/
public enum EDhApiMcRenderingFadeMode
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/**
* No fading is done, there will be a pronounced border between
* Minecraft and Distant Horizons. <br>
@@ -0,0 +1,19 @@
package com.seibel.distanthorizons.api.enums.config;
import com.seibel.distanthorizons.coreapi.ModInfo;
/**
* AUTO, <br>
* OPEN_GL, <br>
* BLAZE_3D, <br><br>
*
* @since API 6.0.0
* @version 2026-3-10
*/
public enum EDhApiRenderApi
{
AUTO,
OPEN_GL,
BLAZE_3D;
}
@@ -35,11 +35,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/
public enum EDhApiServerFolderNameMode
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** Only use the server name */
NAME_ONLY,
@@ -34,11 +34,6 @@ package com.seibel.distanthorizons.api.enums.config;
@Deprecated // not currently in use, if the config this enum represents is re-implemented, the deprecated flag can be removed
public enum EDhApiVanillaOverdraw
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/**
* Don't draw LODs where a minecraft chunk could be.
* Use Overdraw Offset to tweak the border thickness.
@@ -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
int index = MathUtil.clamp(0, dataDetail, this.maxVerticalData.length - 1);
@@ -28,10 +28,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/
public enum EDhApiWorldCompressionMode
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/**
* Every block/biome change is recorded in the database. <br>
* This is what DH 2.0 and 2.0.1 all used by default and will store a lot of data.
@@ -35,10 +35,6 @@ import com.seibel.distanthorizons.api.enums.config.DisallowSelectingViaConfigGui
*/
public enum EDhApiQualityPreset
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
@DisallowSelectingViaConfigGui
CUSTOM,
@@ -34,10 +34,6 @@ import com.seibel.distanthorizons.api.enums.config.DisallowSelectingViaConfigGui
*/
public enum EDhApiThreadPreset
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
@DisallowSelectingViaConfigGui
CUSTOM,
@@ -33,11 +33,6 @@ package com.seibel.distanthorizons.api.enums.rendering;
*/
public enum EDhApiDebugRendering
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** LODs are rendered normally */
OFF,
@@ -48,11 +43,7 @@ public enum EDhApiDebugRendering
SHOW_BLOCK_MATERIAL,
/** Only draw overlapping LOD quads. */
SHOW_OVERLAPPING_QUADS,
/** LOD colors are based on renderSource flags. */
SHOW_RENDER_SOURCE_FLAG;
SHOW_OVERLAPPING_QUADS;
public static EDhApiDebugRendering next(EDhApiDebugRendering type)
{
@@ -65,7 +56,7 @@ public enum EDhApiDebugRendering
case SHOW_BLOCK_MATERIAL:
return SHOW_OVERLAPPING_QUADS;
case SHOW_OVERLAPPING_QUADS:
return SHOW_RENDER_SOURCE_FLAG;
return OFF;
default:
return OFF;
}
@@ -76,8 +67,6 @@ public enum EDhApiDebugRendering
switch (type)
{
case OFF:
return SHOW_RENDER_SOURCE_FLAG;
case SHOW_RENDER_SOURCE_FLAG:
return SHOW_OVERLAPPING_QUADS;
case SHOW_OVERLAPPING_QUADS:
return SHOW_DETAIL;
@@ -33,10 +33,6 @@ package com.seibel.distanthorizons.api.enums.rendering;
@Deprecated
public enum EDhApiFogDrawMode
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/**
* Use whatever Fog setting Optifine is using.
* If Optifine isn't installed this defaults to {@link EDhApiFogDrawMode#FOG_ENABLED}.
@@ -30,11 +30,6 @@ package com.seibel.distanthorizons.api.enums.rendering;
*/
public enum EDhApiFogFalloff
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
LINEAR(0),
EXPONENTIAL(1),
EXPONENTIAL_SQUARED(2);
@@ -33,11 +33,6 @@ package com.seibel.distanthorizons.api.enums.rendering;
*/
public enum EDhApiHeightFogDirection
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
ABOVE_CAMERA (true, true, false),
BELOW_CAMERA (true, false, true),
ABOVE_AND_BELOW_CAMERA (true, true, true),
@@ -20,22 +20,17 @@
package com.seibel.distanthorizons.api.enums.rendering;
/**
* Default <br>
* Debug <br>
* Disabled <br>
* DEFAULT <br>
* DEBUG_TRIANGLE <br>
* DISABLED <br>
*
* @since API 2.0.0
* @version 2024-4-6
* @version 2026-03-23
*/
public enum EDhApiRendererMode
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
DEFAULT,
DEBUG,
DEBUG_TRIANGLE,
DISABLED;
@@ -45,8 +40,8 @@ public enum EDhApiRendererMode
switch (type)
{
case DEFAULT:
return DEBUG;
case DEBUG:
return DEBUG_TRIANGLE;
case DEBUG_TRIANGLE:
return DISABLED;
default:
return DEFAULT;
@@ -60,10 +55,10 @@ public enum EDhApiRendererMode
{
case DEFAULT:
return DISABLED;
case DEBUG:
case DEBUG_TRIANGLE:
return DEFAULT;
default:
return DEBUG;
return DEBUG_TRIANGLE;
}
}
@@ -29,11 +29,6 @@ package com.seibel.distanthorizons.api.enums.rendering;
*/
public enum EDhApiTransparency
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
DISABLED(false, false),
FAKE(true, true),
COMPLETE(true, false);
@@ -34,11 +34,6 @@ package com.seibel.distanthorizons.api.enums.worldGeneration;
*/
public enum EDhApiDistantGeneratorMode
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** Don't generate any new terrain, just generate LODs for already generated chunks. */
PRE_EXISTING_ONLY((byte) 1),
@@ -31,11 +31,6 @@ package com.seibel.distanthorizons.api.enums.worldGeneration;
*/
public enum EDhApiDistantGeneratorProgressDisplayLocation
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
OVERLAY,
CHAT,
LOG,
@@ -23,10 +23,10 @@ import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
/**
* Distant Horizons' fog configuration. <br><br>
* Distant Horizons' SSAO configuration. <br><br>
*
* @author James Seibel
* @version 2022-9-6
* @version 2026-02-05
* @since API 1.0.0
*/
public interface IDhApiAmbientOcclusionConfig extends IDhApiConfigGroup
@@ -34,32 +34,4 @@ public interface IDhApiAmbientOcclusionConfig extends IDhApiConfigGroup
/** Determines if Ambient Occlusion is rendered */
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>
* 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
@@ -54,18 +54,18 @@ public interface IDhApiFarFogConfig extends IDhApiConfigGroup
* 0.0 = fog ends at the camera <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. */
IDhApiConfigValue<Double> farFogMinThickness();
IDhApiConfigValue<Float> farFogMinThickness();
/** Defines how opaque the fog is at its thickest point. */
IDhApiConfigValue<Double> farFogMaxThickness();
IDhApiConfigValue<Float> farFogMaxThickness();
/** Defines how the fog changes in thickness. */
IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff();
/** Defines the fog density. */
IDhApiConfigValue<Double> farFogDensity();
IDhApiConfigValue<Float> farFogDensity();
}
@@ -104,7 +104,7 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
* 2 = blending of 5x5 <br>
* ... <br>
*/
// IDhApiConfigValue<Integer> getBiomeBlending();
IDhApiConfigValue<Integer> getBiomeBlending();
@@ -124,19 +124,19 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
*
* @since API 2.0.0
*/
IDhApiConfigValue<Double> overdrawPreventionRadius();
IDhApiConfigValue<Float> overdrawPreventionRadius();
/**
* Modifies how bright fake chunks are. <br>
* 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>
* 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. */
IDhApiConfigValue<Boolean> caveCullingEnabled();
@@ -150,12 +150,6 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
/** If enabled vanilla chunk rendering is disabled and only fake chunks are rendered. */
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.
*
@@ -52,24 +52,24 @@ public interface IDhApiHeightFogConfig extends IDhApiConfigGroup
* Defines the height fog's base height if {@link IDhApiHeightFogConfig#heightFogDirection()}
* 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. */
IDhApiConfigValue<Double> heightFogStartingHeightPercent();
IDhApiConfigValue<Float> heightFogStartingHeightPercent();
/** 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. */
IDhApiConfigValue<Double> heightFogMinThickness();
IDhApiConfigValue<Float> heightFogMinThickness();
/** 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. */
IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff();
/** 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. */
IDhApiConfigValue<Integer> noiseSteps();
/** Defines how intense the noise will be. */
IDhApiConfigValue<Double> noiseIntensity();
/** Defines how intense the noise will be, between 0.0 and 1.0. */
IDhApiConfigValue<Float> noiseIntensity();
/**
* 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;
/**
* 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
*
* @author James Seibel
* @version 2024-7-14
* @version 2026-1-29
* @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.
@@ -32,7 +32,7 @@ import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint;
* @see IDhApiTerrainDataCache
*
* @author James Seibel
* @version 2023-6-22
* @version 2026-02-03
* @since API 1.0.0
*/
public interface IDhApiTerrainDataRepo
@@ -42,24 +42,17 @@ public interface IDhApiTerrainDataRepo
// 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.
* @since API 3.0.0
*/
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.
* @since API 3.0.0
*/
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>
*
@@ -71,8 +64,6 @@ public interface IDhApiTerrainDataRepo
*/
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>
*
@@ -84,8 +75,6 @@ public interface IDhApiTerrainDataRepo
*/
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>
* 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
* at the end of the given ray. <br><br>
@@ -161,9 +135,16 @@ public interface IDhApiTerrainDataRepo
//=========//
/**
* Creates a new cache you manage that can be used to speed up repeat
* read operations. <br>
* Without a cache each operation must: hit the backing database file,
* decompress it, and parse it; which is a fairly slow process. <br>
*
* @return a {@link IDhApiTerrainDataCache} backed by {@link java.lang.ref.SoftReference}'s.
* @since API 3.0.0
* @since API 5.0.0
*/
IDhApiTerrainDataCache getSoftCache();
IDhApiTerrainDataCache createSoftCache();
}
@@ -40,7 +40,7 @@ public interface IDhApiEventInjector extends IDependencyInjector<IDhApiEvent>
* @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
*/
// 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
boolean unbind(Class<? extends IDhApiEvent> dependencyInterface, Class<? extends IDhApiEvent> dependencyClassToRemove) throws IllegalArgumentException;
@@ -169,7 +169,7 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
*
* After the {@link IDhApiWorldGenerator} has been generated, it should be passed into the
* resultConsumer's {@link Consumer#accept(Object)} method.
* Note: if air blocks aren't included in the with the {@link DhApiChunk} with proper lighting, lower detail levels will appear as black/unlit.
* Note: if air blocks aren't included in the {@link IDhApiFullDataSource} with proper lighting, lower detail levels will appear as black/unlit.
*
* @implNote the default implementation of this method throws an {@link UnsupportedOperationException},
* and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}.
@@ -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.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import java.util.List;
@@ -18,7 +19,7 @@ import java.util.List;
* @version 2024-7-3
* @since API 3.0.0
*/
public interface IDhApiCustomRenderObjectFactory
public interface IDhApiCustomRenderObjectFactory extends IBindable
{
/**
* Creates a {@link IDhApiRenderableBoxGroup} from for the given {@link DhApiRenderableBox}
@@ -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.
*/
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
@@ -33,7 +33,7 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
* @since API 2.0.0
* @deprecated Replaced by {@link DhApiBeforeColorDepthTextureCreatedEvent} since this event's name isn't obvious when it fires.
*/
@Deprecated
@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>
{
/** Fired before Distant Horizons creates. */
@@ -20,6 +20,8 @@
package com.seibel.distanthorizons.api.methods.events.sharedParameterObjects;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldLoadEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
@@ -27,7 +29,7 @@ import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
* Contains information relevant to Distant Horizons and Minecraft rendering.
*
* @author James Seibel
* @version 2024-1-31
* @version 2025-12-23
* @since API 1.0.0
*/
public class DhApiRenderParam implements IDhApiEventParam
@@ -58,25 +60,34 @@ public class DhApiRenderParam implements IDhApiEventParam
public final DhApiMat4f dhProjectionMatrix;
/** The model view matrix Distant Horizons is using to render this frame. */
public final DhApiMat4f dhModelViewMatrix;
/** combination of the MVM and projection matrices */
public final DhApiMat4f dhMvmProjMatrix;
public final int worldYOffset;
/**
* The level currently being rendered.
*
* @since API 5.1.0
*/
public final IDhApiLevelWrapper clientLevelWrapper;
//==============//
// constructors //
//==============//
public DhApiRenderParam(DhApiRenderParam parent)
{
this(
parent.renderPass,
parent.partialTicks,
parent.nearClipPlane, parent.farClipPlane,
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(),
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(),
parent.worldYOffset
parent.renderPass,
parent.partialTicks,
parent.nearClipPlane, parent.farClipPlane,
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(),
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(),
parent.worldYOffset,
parent.clientLevelWrapper
);
}
public DhApiRenderParam(
@@ -85,8 +96,9 @@ public class DhApiRenderParam implements IDhApiEventParam
float nearClipPlane, float farClipPlane,
DhApiMat4f newMcProjectionMatrix, DhApiMat4f newMcModelViewMatrix,
DhApiMat4f newDhProjectionMatrix, DhApiMat4f newDhModelViewMatrix,
int worldYOffset
)
int worldYOffset,
IDhApiLevelWrapper clientLevelWrapper
)
{
this.renderPass = renderPass;
@@ -101,7 +113,12 @@ public class DhApiRenderParam implements IDhApiEventParam
this.dhProjectionMatrix = newDhProjectionMatrix;
this.dhModelViewMatrix = newDhModelViewMatrix;
DhApiMat4f combinedMatrix = new DhApiMat4f(this.dhProjectionMatrix);
combinedMatrix.multiply(this.dhModelViewMatrix);
this.dhMvmProjMatrix = combinedMatrix;
this.worldYOffset = worldYOffset;
this.clientLevelWrapper = clientLevelWrapper;
}
@@ -112,9 +129,8 @@ public class DhApiRenderParam implements IDhApiEventParam
//================//
@Override
public DhApiRenderParam copy()
{
return new DhApiRenderParam(this);
}
public DhApiRenderParam copy() { return new DhApiRenderParam(this); }
}
@@ -31,7 +31,7 @@ import java.util.List;
* Contains a list of {@link DhApiTerrainDataPoint} representing the blocks in a Minecraft chunk.
*
* @author Builderb0y, James Seibel
* @version 2024-7-21
* @version 2025-12-11
* @since API 2.0.0
*
* @see IDhApiWrapperFactory
@@ -54,27 +54,12 @@ public class DhApiChunk
// constructors //
//==============//
/**
* Deprecated due to the topYBlockPos and bottomYBlockPos variables being put in the wrong order.
* They should have been in bottom -> top order.
*
* @see DhApiChunk#create(int, int, int, int)
*/
@Deprecated
public DhApiChunk(int chunkPosX, int chunkPosZ, int topYBlockPos, int bottomYBlockPos)
{ this(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos, false); }
/**
* @since API 3.0.0
*/
/** @since API 3.0.0 */
public static DhApiChunk create(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos)
{ return new DhApiChunk(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos, false); }
{ return new DhApiChunk(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos); }
/**
* Only visible to internal DH methods
* @param ignoredParameter is only present to differentiate the two constructors and isn't actually used
*/
private DhApiChunk(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos, boolean ignoredParameter)
/** Only visible to internal DH methods */
private DhApiChunk(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos)
{
this.chunkPosX = chunkPosX;
this.chunkPosZ = chunkPosZ;
@@ -29,7 +29,7 @@ import java.util.ArrayList;
* Holds a single datapoint of terrain data.
*
* @author James Seibel
* @version 2024-7-20
* @version 2025-11-15
* @since API 1.0.0
*/
public class DhApiTerrainDataPoint
@@ -47,6 +47,10 @@ public class DhApiTerrainDataPoint
public final int blockLightLevel;
public final int skyLightLevel;
/**
* An unsigned block position of the bottom vertex for this LOD relative to the level's minimum height.
* Should be greater than or equal to 0.
*/
public final int bottomYBlockPos;
public final int topYBlockPos;
@@ -59,28 +63,7 @@ public class DhApiTerrainDataPoint
// constructors //
//==============//
/**
* Deprecated due to the topYBlockPos and bottomYBlockPos variables being put in the wrong order.
* They should have been in bottom -> top order.
*
* @see DhApiTerrainDataPoint#create(byte, int, int, int, int, IDhApiBlockStateWrapper, IDhApiBiomeWrapper)
*/
@Deprecated
public DhApiTerrainDataPoint(
byte detailLevel,
int blockLightLevel, int skyLightLevel,
int topYBlockPos, int bottomYBlockPos,
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper)
{
this(detailLevel, blockLightLevel, skyLightLevel,
bottomYBlockPos, topYBlockPos,
blockStateWrapper, biomeWrapper,
false);
}
/**
* @since API 3.0.0
*/
/** @since API 3.0.0 */
public static DhApiTerrainDataPoint create(
byte detailLevel,
int blockLightLevel, int skyLightLevel,
@@ -91,20 +74,15 @@ public class DhApiTerrainDataPoint
return new DhApiTerrainDataPoint(
detailLevel, blockLightLevel, skyLightLevel,
bottomYBlockPos, topYBlockPos,
blockStateWrapper, biomeWrapper,
false);
blockStateWrapper, biomeWrapper);
}
/**
* Only visible to internal DH methods
* @param ignoredParameter is only present to differentiate the two constructors and isn't actually used
*/
/** Only visible to internal DH methods */
private DhApiTerrainDataPoint(
byte detailLevel,
int blockLightLevel, int skyLightLevel,
int bottomYBlockPos, int topYBlockPos,
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper,
boolean ignoredParameter
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper
)
{
this.detailLevel = detailLevel;
@@ -118,4 +96,24 @@ public class DhApiTerrainDataPoint
this.biomeWrapper = biomeWrapper;
}
//================//
// base overrides //
//================//
@Override
public String toString()
{
return "[Block:" + this.blockStateWrapper.getSerialString() +
",Biome:" + this.biomeWrapper.getName() +
",TopY:" + this.topYBlockPos +
",BottomY:" + this.bottomYBlockPos +
",BlockLight:" + this.blockLightLevel +
",SkyLight:" + this.skyLightLevel +
"]";
}
}
@@ -23,6 +23,7 @@ public class DhApiRenderableBox
public DhApiVec3d maxPos;
public Color color;
/** @see EDhApiBlockMaterial */
public byte material;
@@ -43,9 +43,11 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
protected final boolean allowDuplicateBindings;
//==============//
// constructors //
//==============//
//region
public DependencyInjector(Class<BindableType> newBindableInterface, boolean newAllowDuplicateBindings)
{
@@ -53,11 +55,14 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
this.allowDuplicateBindings = newAllowDuplicateBindings;
}
//endregion
//=========//
// binding //
//=========//
//region
@Override
public void bind(Class<? extends BindableType> dependencyInterface, BindableType dependencyImplementation) throws IllegalStateException, IllegalArgumentException
@@ -69,6 +74,11 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
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
boolean implementsInterface = this.checkIfClassImplements(dependencyImplementation.getClass(), dependencyInterface) ||
@@ -131,13 +141,27 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
@Override
public boolean checkIfClassExtends(Class<?> classToTest, Class<?> extensionToLookFor) { return extensionToLookFor.isAssignableFrom(classToTest); }
//endregion
//===========//
// 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
{
// check if this object is bound
@@ -174,30 +198,27 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
this.dependencies.remove(dependencyInterface);
}
//endregion
//=========//
// getters //
//=========//
//region
@SuppressWarnings("unchecked")
@Override
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
public <T extends BindableType> ArrayList<T> getAll(Class<T> interfaceClass) throws ClassCastException
{
return this.getInternalLogic(interfaceClass, false);
}
{ return this.getInternalLogic(interfaceClass, false); }
@Override
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,
@@ -230,6 +251,7 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
return emptyList;
}
//endregion
/** Removes all bound dependencies. */
@@ -31,26 +31,26 @@ public final class ModInfo
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. */
public static final int PROTOCOL_VERSION = 11;
public static final int PROTOCOL_VERSION = 13;
public static final String WRAPPER_PACKET_PATH = "message";
/** The internal mod name */
public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.3.5-b";
public static final String VERSION = "3.0.0-b-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */
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 */
public static final int API_MAJOR_VERSION = 4;
public static final int API_MAJOR_VERSION = 6;
/** 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 */
public static final int API_PATCH_VERSION = 0;
/** 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. */
public static final String THREAD_NAME_PREFIX = "DH-";
@@ -1,86 +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.coreapi.interfaces.config;
import java.util.function.Consumer;
/**
* Use for making the config variables
*
* @author coolGi
* @version 2022-5-26
*/
public interface IConfigEntry<T>
{
/** Gets the default value of the option */
T getDefaultValue();
void setApiValue(T newApiValue);
T getApiValue();
/** @return true if this config is able to be overridden by the API and an API user has set it */
boolean apiIsOverriding();
/** Returns true if this config can be set via the API. */
boolean getAllowApiOverride();
void set(T newValue);
T get();
/** gets the option ignoring what the API has overridden */
T getTrueValue();
/** Sets the value without saving */
void setWithoutSaving(T newValue);
/** Gets the min value */
T getMin();
/** Sets the min value */
void setMin(T newMin);
/** Gets the max value */
T getMax();
/** Sets the max value */
void setMax(T newMax);
/** Sets the min and max in 1 setter */
void setMinMax(T newMin, T newMax);
/** Gets the comment */
String getComment();
/** Sets the comment */
void setComment(String newComment);
/**
* Checks if the option is valid
*
* 0 == valid
* 2 == invalid
* 1 == number too high
* -1 == number too low
*/
byte isValid(); // TODO replace with an enum
/** Checks if a value is valid */
byte isValid(T value);
/** Is the value of this equal to another */
boolean equals(IConfigEntry<?> obj);
void addValueChangeListener(Consumer<T> onValueChangeFunc);
}
+45 -30
View File
@@ -1,18 +1,23 @@
plugins {
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 {
mainClass.set("com.seibel.distanthorizons.core.jar.JarMain")
tasks.withType(JavaCompile).configureEach {
options.release = 8
options.encoding = "UTF-8"
}
configurations {
shadowedArtifact // Used by DH to specify that we want to implement the shadowed core JAR file instead of the regular JAR file
shade
implementation.extendsFrom shade
testImplementation.extendsFrom compileOnly
}
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
@@ -20,36 +25,46 @@ OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePl
// Set the OS lwjgl is using to the current os
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
// Imports most of lwjgl's libraries (well, only the ones that we need)
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
dependencies {
// API project dependency
implementation project(":api")
// REMEMBER: Dont shadow stuff here, these are just the libs that are included in Minecraft so that the core can use
implementation "org.lwjgl:lwjgl"
implementation "org.lwjgl:lwjgl-assimp"
implementation "org.lwjgl:lwjgl-glfw"
implementation "org.lwjgl:lwjgl-openal"
implementation "org.lwjgl:lwjgl-opengl"
implementation "org.lwjgl:lwjgl-stb"
implementation "org.lwjgl:lwjgl-tinyfd"
runtimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-assimp::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-openal::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-tinyfd::$lwjglNatives"
// MC-provided libraries (available at runtime via Minecraft)
compileOnly platform("org.lwjgl:lwjgl-bom:${rootProject.lwjgl_version}")
compileOnly "org.lwjgl:lwjgl"
compileOnly "org.lwjgl:lwjgl-assimp"
compileOnly "org.lwjgl:lwjgl-glfw"
compileOnly "org.lwjgl:lwjgl-stb"
compileOnly "org.lwjgl:lwjgl-tinyfd"
testRuntimeOnly platform("org.lwjgl:lwjgl-bom:${rootProject.lwjgl_version}")
testRuntimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
testRuntimeOnly "org.lwjgl:lwjgl-assimp::$lwjglNatives"
testRuntimeOnly "org.lwjgl:lwjgl-glfw::$lwjglNatives"
testRuntimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
testRuntimeOnly "org.lwjgl:lwjgl-tinyfd::$lwjglNatives"
// FIXME for some reason this line doesn't actually shade in the library
// shade "it.unimi.dsi:fastutil:${rootProject.fastutil_version}" // Add our own fastutil version
compileOnly("org.apache.logging.log4j:log4j-api:${rootProject.log4j_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.common:google-collect:0.5")
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
implementation("org.jetbrains:annotations:16.0.2")
implementation("com.google.code.findbugs:jsr305:3.0.2")
implementation("com.google.common:google-collect:0.5")
implementation("com.google.guava:guava:31.1-jre")
// JUnit (core tests only)
compileOnly("junit:junit:4.13")
compileOnly("org.junit.jupiter:junit-jupiter:5.8.2")
compileOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2")
}
artifacts {
@@ -20,36 +20,49 @@
package com.seibel.distanthorizons.core;
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.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
import com.seibel.distanthorizons.core.api.external.methods.config.DhApiConfig;
import com.seibel.distanthorizons.core.api.external.methods.data.DhApiTerrainDataRepo;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import net.jpountz.lz4.LZ4FrameOutputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.sqlite.SQLiteJDBCLoader;
import org.sqlite.util.OSInfo;
import org.tukaani.xz.XZOutputStream;
import java.awt.*;
import java.io.File;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.List;
/** Handles first time Core setup. */
public class Initializer
{
private static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME + "-" + Initializer.class.getSimpleName());
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static void init()
{
//============================//
// check referenced libraries //
//============================//
//region
LOGGER.info("Running library validation...");
// confirm that all referenced libraries are available to use
try
{
@@ -57,6 +70,17 @@ public class Initializer
// will throw an error (not an exception)
Class<?> lz4Compressor = LZ4FrameOutputStream.class;
Class<?> zstdCompressor = ZstdOutputStream.class;
{
byte[] testCompressByteArray = new byte[1024];
for (int i = 0; i < testCompressByteArray.length; i++)
{
testCompressByteArray[i] = (byte) (i % 126);
}
byte[] compressedBytes = com.github.luben.zstd.Zstd.compress(testCompressByteArray);
com.github.luben.zstd.Zstd.decompress(compressedBytes);
}
Class<?> lzmaCompressor = XZOutputStream.class;
//Class<?> networking = ByteBuf.class;
Class<?> config = com.electronwill.nightconfig.core.Config.class;
@@ -73,12 +97,18 @@ public class Initializer
}
catch (Throwable e)
{
LOGGER.fatal("Critical programmer error: One or more libraries aren't present. Error: [" + e.getMessage() + "].", e);
// throwing here should crash the game, notifying the developer that something is wrong
throw new RuntimeException(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
{
int scriptCount = DatabaseUpdater.getAutoUpdateScriptCount();
@@ -89,10 +119,18 @@ public class Initializer
}
catch (Exception e)
{
LOGGER.fatal("Critical programmer error: Can't read SQL Scripts resource folder is either missing or malformed. Error: [" + e.getMessage() + "].");
throw new RuntimeException(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
// to lock up and refuse the load (there's a bug with Java.awt texture loading)
//if (MC_CLIENT != null)
@@ -109,18 +147,99 @@ public class Initializer
// }
//}
//endregion
//===================//
// API delayed setup //
//===================//
//region
// link Core's config to the API
DhApi.Delayed.configs = DhApiConfig.INSTANCE;
DhApi.Delayed.terrainRepo = DhApiTerrainDataRepo.INSTANCE;
DhApi.Delayed.worldProxy = DhApiWorldProxy.INSTANCE;
DhApi.Delayed.renderProxy = DhApiRenderProxy.INSTANCE;
DhApi.Delayed.customRenderObjectFactory = GenericRenderObjectFactory.INSTANCE;
DhApi.Delayed.wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
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
//==============================//
// G1 Garbage collector warning //
//==============================//
//region
// log a warning if G1GC is being used
// (this garbage collector is known to cause stuttering)
{
boolean g1GcInUse = false;
StringBuilder garbageCollectorNames = new StringBuilder();
List<GarbageCollectorMXBean> gcMxBeans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean gcMxBean : gcMxBeans)
{
if (!garbageCollectorNames.toString().isEmpty())
{
garbageCollectorNames.append(", ");
}
garbageCollectorNames.append(gcMxBean.getName());
// "G1 Young Generation" // "G1 Concurrent GC" // "G1 Old Generation"
if (gcMxBean.getName().toLowerCase().contains("g1 "))
{
g1GcInUse = true;
}
}
LOGGER.info("Garbage collectors: ["+garbageCollectorNames+"]");
if (g1GcInUse)
{
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(
warningMessageHeader + "\n" +
warningMessageBody +
"");
}
if (Config.Common.Logging.Warning.showGarbageCollectorWarning.get())
{
ClientApi.INSTANCE.showChatMessageNextFrame(
MinecraftTextFormat.ORANGE + warningMessageHeader + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
warningMessageBody +
"");
}
}
}
//endregion
}
}
@@ -21,7 +21,7 @@ package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiAmbientOcclusionConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
public class DhApiAmbientOcclusionConfig implements IDhApiAmbientOcclusionConfig
@@ -35,30 +35,6 @@ public class DhApiAmbientOcclusionConfig implements IDhApiAmbientOcclusionConfig
@Override
public IDhApiConfigValue<Boolean> enabled()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Ssao.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); }
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.enableSsao); }
}
@@ -21,7 +21,7 @@ package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiDebuggingConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
@@ -34,7 +34,7 @@ public class DhApiDebuggingConfig implements IDhApiDebuggingConfig
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()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.enableDebugKeybindings); }
@@ -22,7 +22,7 @@ package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogFalloff;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiFarFogConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
public class DhApiFarFogConfig implements IDhApiFarFogConfig
@@ -34,27 +34,27 @@ public class DhApiFarFogConfig implements IDhApiFarFogConfig
@Override
public IDhApiConfigValue<Double> farFogStartDistance()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogStart); }
public IDhApiConfigValue<Float> farFogStartDistance()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogStart); }
@Override
public IDhApiConfigValue<Double> farFogEndDistance()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogEnd); }
public IDhApiConfigValue<Float> farFogEndDistance()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogEnd); }
@Override
public IDhApiConfigValue<Double> farFogMinThickness()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogMin); }
public IDhApiConfigValue<Float> farFogMinThickness()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogMin); }
@Override
public IDhApiConfigValue<Double> farFogMaxThickness()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogMax); }
public IDhApiConfigValue<Float> farFogMaxThickness()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogMax); }
@Override
public IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff()
{ return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.farFogFalloff); }
@Override
public IDhApiConfigValue<Double> farFogDensity()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.farFogDensity); }
public IDhApiConfigValue<Float> farFogDensity()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.farFogDensity); }
}
@@ -25,9 +25,10 @@ import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
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.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.coreapi.util.converters.ApiFogDrawModeConverter;
import com.seibel.distanthorizons.core.config.api.converters.ApiFogDrawModeConverter;
import com.seibel.distanthorizons.core.config.api.converters.InvertedBoolConverter;
public class DhApiFogConfig implements IDhApiFogConfig
{
@@ -67,7 +68,7 @@ public class DhApiFogConfig implements IDhApiFogConfig
@Override
@Deprecated
public IDhApiConfigValue<Boolean> disableVanillaFog()
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.disableVanillaFog); }
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.enableVanillaFog, new InvertedBoolConverter()); }
@Override
public IDhApiConfigValue<Boolean> enableVanillaFog()
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.enableVanillaFog); }
@@ -21,7 +21,7 @@ package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiGenericRenderingConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
public class DhApiGenericRenderingConfig implements IDhApiGenericRenderingConfig
@@ -23,10 +23,10 @@ import com.seibel.distanthorizons.api.enums.config.*;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiTransparency;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.*;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.coreapi.util.converters.RenderModeEnabledConverter;
import com.seibel.distanthorizons.core.config.api.converters.RenderModeEnabledConverter;
public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
{
@@ -97,10 +97,9 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
public IDhApiConfigValue<Boolean> tintWithAvoidedBlocks()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks); }
// TODO re-implement
// @Override
// public IDhApiConfigValue<Integer> getBiomeBlending()
// { return new DhApiConfigValue<Integer, Integer>(Quality.lodBiomeBlending); }
@Override
public IDhApiConfigValue<Integer> getBiomeBlending()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.Quality.lodBiomeBlending); }
@@ -109,16 +108,16 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
//===========================//
@Override
public IDhApiConfigValue<Double> overdrawPreventionRadius()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Culling.overdrawPrevention); }
public IDhApiConfigValue<Float> overdrawPreventionRadius()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Culling.overdrawPrevention); }
@Override
public IDhApiConfigValue<Double> brightnessMultiplier()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Quality.brightnessMultiplier); }
public IDhApiConfigValue<Float> brightnessMultiplier()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Quality.brightnessMultiplier); }
@Override
public IDhApiConfigValue<Double> saturationMultiplier()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Quality.saturationMultiplier); }
public IDhApiConfigValue<Float> saturationMultiplier()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Quality.saturationMultiplier); }
@Override
public IDhApiConfigValue<Boolean> caveCullingEnabled()
@@ -136,10 +135,6 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
public IDhApiConfigValue<Boolean> 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
public IDhApiConfigValue<EDhApiLodShading> lodShading()
{ return new DhApiConfigValue<EDhApiLodShading, EDhApiLodShading>(Config.Client.Advanced.Graphics.Quality.lodShading); }
@@ -24,7 +24,7 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMixMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogDirection;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiHeightFogConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
public class DhApiHeightFogConfig implements IDhApiHeightFogConfig
@@ -44,31 +44,31 @@ public class DhApiHeightFogConfig implements IDhApiHeightFogConfig
{ return new DhApiConfigValue<EDhApiHeightFogDirection, EDhApiHeightFogDirection>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDirection); }
@Override
public IDhApiConfigValue<Double> heightFogBaseHeight()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogBaseHeight); }
public IDhApiConfigValue<Float> heightFogBaseHeight()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogBaseHeight); }
@Override
public IDhApiConfigValue<Double> heightFogStartingHeightPercent()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogStart); }
public IDhApiConfigValue<Float> heightFogStartingHeightPercent()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogStart); }
@Override
public IDhApiConfigValue<Double> heightFogEndingHeightPercent()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogEnd); }
public IDhApiConfigValue<Float> heightFogEndingHeightPercent()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogEnd); }
@Override
public IDhApiConfigValue<Double> heightFogMinThickness()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMin); }
public IDhApiConfigValue<Float> heightFogMinThickness()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMin); }
@Override
public IDhApiConfigValue<Double> heightFogMaxThickness()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMax); }
public IDhApiConfigValue<Float> heightFogMaxThickness()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMax); }
@Override
public IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff()
{ return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogFalloff); }
@Override
public IDhApiConfigValue<Double> heightFogDensity()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDensity); }
public IDhApiConfigValue<Float> heightFogDensity()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDensity); }
}
@@ -21,7 +21,7 @@ package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiMultiThreadingConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
public class DhApiMultiThreadingConfig implements IDhApiMultiThreadingConfig
@@ -21,7 +21,7 @@ package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiMultiplayerConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.api.enums.config.EDhApiServerFolderNameMode;
@@ -21,7 +21,7 @@ package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiNoiseTextureConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
public class DhApiNoiseTextureConfig implements IDhApiNoiseTextureConfig
@@ -41,8 +41,8 @@ public class DhApiNoiseTextureConfig implements IDhApiNoiseTextureConfig
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.NoiseTexture.noiseSteps); }
@Override
public IDhApiConfigValue<Double> noiseIntensity()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.NoiseTexture.noiseIntensity); }
public IDhApiConfigValue<Float> noiseIntensity()
{ return new DhApiConfigValue<Float, Float>(Config.Client.Advanced.Graphics.NoiseTexture.noiseIntensity); }
@Override
public IDhApiConfigValue<Integer> noiseDropoff()
@@ -21,7 +21,7 @@ package com.seibel.distanthorizons.core.api.external.methods.config.common;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.both.IDhApiWorldGenerationConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.core.config.Config;
@@ -2,10 +2,10 @@ package com.seibel.distanthorizons.core.api.external.methods.data;
import com.seibel.distanthorizons.api.interfaces.data.IDhApiTerrainDataCache;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.SoftReference;
@@ -13,15 +13,16 @@ import java.lang.ref.SoftReference;
public class DhApiTerrainDataCache implements IDhApiTerrainDataCache
{
private final Object modificationLock = new Object();
private Long2ReferenceOpenHashMap<SoftReference<FullDataSourceV2>> posToFullDataRef = new Long2ReferenceOpenHashMap<>();
private final Long2ReferenceOpenHashMap<SoftReference<FullDataSourceV2>> posToFullDataRef = new Long2ReferenceOpenHashMap<>();
private static final Logger LOGGER = LogManager.getLogger(DhApiTerrainDataCache.class.getSimpleName());
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
//==================//
// internal methods //
//==================//
//region
public void add(long pos, FullDataSourceV2 dataSource)
{
@@ -48,11 +49,14 @@ public class DhApiTerrainDataCache implements IDhApiTerrainDataCache
}
}
//endregion
//=============//
// API methods //
//=============//
//region
@Override
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
}
@@ -31,9 +31,10 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
import com.seibel.distanthorizons.core.util.DhApiTerrainDataPointUtil;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -48,8 +49,7 @@ import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3i;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
@@ -64,7 +64,9 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
{
public static DhApiTerrainDataRepo INSTANCE = new DhApiTerrainDataRepo();
private static final Logger LOGGER = LogManager.getLogger(DhApiTerrainDataRepo.class.getSimpleName());
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final AbstractDebugWireframeRenderer DEBUG_RENDERER = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
// debugging values
private static volatile boolean debugThreadRunning = false;
@@ -89,30 +91,30 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
//================//
@Override
public DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, dataCache); }
public DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, IDhApiTerrainDataCache dataCache)
{ return getTerrainDataAtBlockYPos(levelWrapper, DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, dataCache); }
@Override
public DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataColumnArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, dataCache); }
public DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, IDhApiTerrainDataCache dataCache)
{ return getTerrainDataColumnArray(levelWrapper, DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, dataCache); }
@Override
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPosX, chunkPosZ), dataCache); }
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, DhSectionPos.encode(LodUtil.CHUNK_DETAIL_LEVEL, chunkPosX, chunkPosZ), dataCache); }
@Override
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ), dataCache); }
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, DhSectionPos.encode(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ), dataCache); }
@Override
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(detailLevel, posX, posZ), dataCache); }
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ, IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, DhSectionPos.encode(detailLevel, posX, posZ), dataCache); }
// private getters //
/** 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);
if (result.success && result.payload.length > 0)
@@ -126,7 +128,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>
* 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>
@@ -135,11 +137,14 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
* will stop and return the in progress data if any errors are encountered.
*/
private static DhApiResult<DhApiTerrainDataPoint[][][]> getTerrainDataOverAreaForPositionDetailLevel(
IDhApiLevelWrapper levelWrapper, DhLodPos requestedAreaPos,
@Nullable IDhApiTerrainDataCache dataCache)
IDhApiLevelWrapper levelWrapper, long requestedAreaPos,
IDhApiTerrainDataCache dataCache)
{
DhLodPos startingBlockPos = requestedAreaPos.getCornerLodPos(LodUtil.BLOCK_DETAIL_LEVEL);
int widthOfAreaInBlocks = BitShiftUtil.powerOfTwo(requestedAreaPos.detailLevel);
byte requestedDetailLevel = DhSectionPos.getDetailLevel(requestedAreaPos);
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][];
int dataColumnsReturned = 0;
@@ -149,7 +154,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
{
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);
if (result.success)
{
@@ -175,8 +180,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
*/
private static DhApiResult<DhApiTerrainDataPoint[]> getTerrainDataColumnArray(
IDhApiLevelWrapper levelWrapper,
DhLodPos requestedColumnPos, Integer nullableBlockYPos,
@Nullable IDhApiTerrainDataCache apiDataCache)
long requestedColumnPos, Integer nullableBlockYPos,
IDhApiTerrainDataCache apiDataCache)
{
//============//
// validation //
@@ -197,9 +202,14 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper;
// the data cache can be null, but must be our own implementation
if (apiDataCache != null
&& !(apiDataCache instanceof DhApiTerrainDataCache))
// require a data cache to prevent horrible performance (especially on ray-casts)
if (apiDataCache == null)
{
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.");
}
@@ -213,12 +223,12 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
}
// 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);
// get the positions for this request
long sectionPos = requestedColumnPos.getSectionPosWithSectionDetailLevel(sectionDetailLevel);
DhLodPos relativePos = requestedColumnPos.getDhSectionRelativePositionForDetailLevel();
long sectionPos = DhSectionPos.convertToDetailLevel(requestedColumnPos, sectionDetailLevel);
long relativePos = DhSectionPos.getDhSectionRelativePositionForDetailLevel(requestedColumnPos, DhSectionPos.getDetailLevel(requestedColumnPos));
@@ -258,7 +268,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
//===============================//
FullDataPointIdMap mapping = dataSource.mapping;
LongArrayList dataColumn = dataSource.get(relativePos.x, relativePos.z);
LongArrayList dataColumn = dataSource.getColumnAtRelPos(DhSectionPos.getX(relativePos), DhSectionPos.getZ(relativePos));
if (dataColumn != null)
{
int dataColumnIndexCount = dataColumn.size();
@@ -277,7 +287,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
if (!getSpecificYCoordinate)
{
// if we aren't look for a specific datapoint, add each datapoint to the return array
returnArray[i] = DhApiTerrainDataPointUtil.createApiDatapoint(levelWrapper, mapping, requestedDetailLevel, dataPoint);
returnArray[i] = DhApiTerrainDataPointUtil.createApiDatapoint(levelWrapper.getMinHeight(), mapping, requestedDetailLevel, dataPoint);
}
else
{
@@ -294,7 +304,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
if (bottomY <= requestedY && requestedY < topY) // blockPositions start from the bottom of the block, thus "<=" for bottomY, just "<" for topY
{
// this datapoint contains the requested block position, return it
DhApiTerrainDataPoint apiTerrainData = DhApiTerrainDataPointUtil.createApiDatapoint(levelWrapper, mapping, requestedDetailLevel, dataPoint);
DhApiTerrainDataPoint apiTerrainData = DhApiTerrainDataPointUtil.createApiDatapoint(levelWrapper.getMinHeight(), mapping, requestedDetailLevel, dataPoint);
return DhApiResult.createSuccess(new DhApiTerrainDataPoint[]{apiTerrainData});
}
}
@@ -345,7 +355,10 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
@Nullable
IDhApiTerrainDataCache dataCache)
{
return this.raycastLodData(levelWrapper, new Vec3d(rayOriginX, rayOriginY, rayOriginZ), new Vec3f(rayDirectionX, rayDirectionY, rayDirectionZ), maxRayBlockLength, dataCache);
return this.raycastLodData(levelWrapper,
new Vec3d(rayOriginX, rayOriginY, rayOriginZ),
new Vec3f(rayDirectionX, rayDirectionY, rayDirectionZ),
maxRayBlockLength, dataCache);
}
/**
@@ -363,8 +376,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
{
rayDirection.normalize();
int minBlockHeight = levelWrapper.getMinHeight();
int maxBlockHeight = levelWrapper.getMaxHeight();
int minLevelBlockHeight = levelWrapper.getMinHeight();
int maxLevelBlockHeight = levelWrapper.getMaxHeight();
@@ -380,7 +393,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
DhApiRaycastResult closetFoundDataPoint = null;
while (blockPos.y >= minBlockHeight && blockPos.y < maxBlockHeight
while (blockPos.y >= minLevelBlockHeight
&& blockPos.y < maxLevelBlockHeight
&& currentLength <= maxRayBlockLength)
{
// get the LOD columns around this position
@@ -403,7 +417,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
{
// does this LOD contain the given Y position?
Vec3i dataPointPos = new Vec3i(columnPos.x, dataPoint.bottomYBlockPos, columnPos.z);
if (exactPos.y >= dataPoint.bottomYBlockPos && exactPos.y <= dataPoint.topYBlockPos)
if (exactPos.y >= dataPoint.bottomYBlockPos
&& exactPos.y <= dataPoint.topYBlockPos)
{
if (closetFoundDataPoint == null)
{
@@ -503,7 +518,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
// this will throw a cast exception if the chunk object array isn't correct
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();
@@ -516,7 +531,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
//=============//
@Override
public IDhApiTerrainDataCache getSoftCache() { return new DhApiTerrainDataCache(); }
public IDhApiTerrainDataCache createSoftCache() { return new DhApiTerrainDataCache(); }
@@ -536,10 +551,10 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
Thread thread = new Thread(() -> {
try
{
DhApiResult<DhApiTerrainDataPoint> single = getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(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> single = getTerrainDataAtBlockYPos(levelWrapper, DhSectionPos.encode(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, 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);
@@ -572,12 +587,16 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
}
// draw raycast position
if (rayCast.success && rayCast.payload != null)
if (rayCast.success
&& rayCast.payload != null)
{
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(
DhSectionPos.encode((byte) 0, rayCast.payload.pos.x, rayCast.payload.pos.z), rayCast.payload.dataPoint.bottomYBlockPos, rayCast.payload.dataPoint.topYBlockPos, -0.1f, Color.RED),
DEBUG_RENDERER.makeParticle(
new AbstractDebugWireframeRenderer.BoxParticle(
new AbstractDebugWireframeRenderer.Box(
DhSectionPos.encode((byte) 0, rayCast.payload.pos.x, rayCast.payload.pos.z),
rayCast.payload.dataPoint.bottomYBlockPos,
rayCast.payload.dataPoint.topYBlockPos,
-0.1f, Color.RED),
1.0, 0f
)
);
@@ -23,18 +23,27 @@ import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.api.internal.rendering.RenderState;
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
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.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.render.renderer.FadeRenderer;
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.util.TimerUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
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.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession;
@@ -43,30 +52,22 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
import com.seibel.distanthorizons.core.logging.SpamReducedLogger;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.renderer.TestRenderer;
import com.seibel.distanthorizons.core.util.RenderUtil;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientServerWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld;
import com.seibel.distanthorizons.core.world.IDhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import java.io.File;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
/**
* This holds the methods that should be called
@@ -75,18 +76,14 @@ import java.util.concurrent.TimeUnit;
*/
public class ClientApi
{
private static final Logger LOGGER = LogManager.getLogger();
public static boolean prefLoggerEnabled = false;
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final DhLogger RATE_LIMITED_LOGGER = new DhLoggerBuilder().maxCountPerSecond(1).build();
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 IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
/** this includes the is dev build message and low allocated memory warning */
private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000;
@@ -98,14 +95,20 @@ public class ClientApi
*
* Only downside is making sure each variable is populated before rendering.
*/
public static final RenderState RENDER_STATE = new RenderState();
public static final DhRenderState RENDER_STATE = new DhRenderState();
/**
* 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 lowMemoryWarningPrinted = false;
private boolean highVanillaRenderDistanceWarningPrinted = false;
/** when the last static */
private long lastStaticWarningMessageSentMsTime = 0L;
private final Queue<String> chatMessageQueueForNextFrame = new LinkedBlockingQueue<>();
@@ -113,8 +116,6 @@ public class ClientApi
public boolean rendererDisabledBecauseOfExceptions = false;
private long lastFlushNanoTime = 0;
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 */
@@ -126,8 +127,24 @@ public class ClientApi
/** Holds any chunks that were loaded before the {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} was fired. */
public final HashMap<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new HashMap<>();
/** re-set every frame during the opaque rendering stage */
private boolean renderingCancelledForThisFrame;
/** publicly available so {@link F3Screen} can display the error */
@Nullable
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
*/
public RollingAverage cameraSpeedRollingAverage = new RollingAverage(40);
private Vec3d lastCameraPosForSpeedCheck = new Vec3d();
private long msSinceLastSpeedCheck = 0L;
@@ -142,6 +159,7 @@ public class ClientApi
//==============//
// world events //
//==============//
///region
/**
* May be fired slightly before or after the associated
@@ -167,17 +185,16 @@ public class ClientApi
if (Config.Common.Logging.Warning.showReplayWarningOnStartup.get())
{
MC_CLIENT.sendChatMessage("\u00A76" + "Distant Horizons: Replay detected." + "\u00A7r"); // gold color
MC_CLIENT.sendChatMessage(MinecraftTextFormat.ORANGE + "Distant Horizons: Replay detected." + MinecraftTextFormat.CLEAR_FORMATTING);
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("\u00A77"+".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+"\u00A7r"); // light gray color
MC_CLIENT.sendChatMessage("This can be disabled in DH's config under Advanced -> Logging.");
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 message can be disabled in DH's config under Advanced -> Logging.");
MC_CLIENT.sendChatMessage("");
}
}
// firing after clientLevelLoadEvent
// TODO if level has prepped to load it should fire level load event
DhClientWorld world = new DhClientWorld();
SharedApi.setDhWorld(world);
@@ -220,11 +237,14 @@ public class ClientApi
this.waitingClientLevels.clear();
}
///endregion
//==============//
// level events //
//==============//
///region
public void clientLevelUnloadEvent(IClientLevelWrapper level)
{
@@ -241,7 +261,6 @@ public class ClientApi
if (world != null)
{
world.unloadLevel(level);
SharedApi.INSTANCE.clearQueuedChunkUpdates();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
}
else
@@ -256,8 +275,15 @@ public class ClientApi
}
}
public void clientLevelLoadEvent(IClientLevelWrapper levelWrapper)
public void clientLevelLoadEvent(@Nullable IClientLevelWrapper levelWrapper)
{
// can happen if there was an issue during level load
if (levelWrapper == null)
{
return;
}
// wait a moment before loading the level to give the server a chance to handle the client's login request
if (MC_CLIENT.clientConnectedToDedicatedServer())
{
@@ -318,7 +344,7 @@ public class ClientApi
if (levelWrapper.equals(level))
{
IChunkWrapper chunkWrapper = this.waitingChunkByClientLevelAndPos.get(levelChunkPair);
SharedApi.INSTANCE.chunkLoadEvent(chunkWrapper, levelWrapper);
SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, levelWrapper);
keysToRemove.add(levelChunkPair);
}
}
@@ -330,54 +356,14 @@ public class ClientApi
}
}
//===============//
// render events //
//===============//
public void clientTickEvent()
{
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
profiler.push("DH-ClientTick");
try
{
boolean doFlush = System.nanoTime() - this.lastFlushNanoTime >= SPAM_LOGGER_FLUSH_NS;
if (doFlush)
{
this.lastFlushNanoTime = System.nanoTime();
SpamReducedLogger.flushAll();
}
ConfigBasedLogger.updateAll();
ConfigBasedSpamLogger.updateAll(doFlush);
IDhClientWorld clientWorld = SharedApi.getIDhClientWorld();
if (clientWorld != null)
{
clientWorld.clientTick();
// Ignore local world gen, as it's managed by server ticking
if (!(clientWorld instanceof DhClientServerWorld))
{
SharedApi.worldGenTick(clientWorld::worldGenTick);
}
}
}
catch (Exception e)
{
// handle errors here to prevent blowing up a mixin or API up stream
LOGGER.error("Unexpected error in ClientApi.clientTickEvent(), error: "+e.getMessage(), e);
}
profiler.pop();
}
///endregion
//============//
// networking //
//============//
///region
/**
* Forwards a decoded message into the registered handlers.
@@ -386,18 +372,38 @@ public class ClientApi
*/
public void pluginMessageReceived(@NotNull AbstractNetworkMessage message)
{
NetworkSession networkSession = this.pluginChannelApi.networkSession;
if (networkSession != null)
@Nullable ThreadPoolExecutor executor = ThreadPoolUtil.networkClientHandlerExecutor();
if (executor == null)
{
networkSession.tryHandleMessage(message);
LOGGER.warn("warn");
return;
}
try
{
executor.execute(() ->
{
NetworkSession networkSession = this.pluginChannelApi.networkSession;
if (networkSession != null)
{
networkSession.tryHandleMessage(message);
}
});
}
catch (RejectedExecutionException e)
{
LOGGER.warn("Plugin message executor rejected");
}
}
///endregion
//===========//
// rendering //
//===========//
//===============//
// LOD rendering //
//===============//
///region
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
public void renderLods() { this.renderLodLayer(false); }
@@ -408,30 +414,101 @@ public class ClientApi
*/
public void renderDeferredLodsForShaders() { this.renderLodLayer(true); }
public static long firstRenderTimeMs = 0;
private void renderLodLayer(boolean renderingDeferredLayer)
{
// A global render state variable is used since MC has split up their
// render prep and actual rendering into different threads/methods
// 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
IClientLevelWrapper levelWrapper = RENDER_STATE.clientLevelWrapper;
Mat4f mcModelViewMatrix = RENDER_STATE.mcModelViewMatrix;
Mat4f mcProjectionMatrix = RENDER_STATE.mcProjectionMatrix;
float partialTicks = RENDER_STATE.frameTime;
// logging //
this.sendQueuedChatMessages();
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("DH-RenderLevel");
// render parameter setup //
//===========//
// debugging //
//===========//
//region
//DhApiTerrainDataRepo.asyncDebugMethod(
// RENDER_STATE.clientLevelWrapper,
// MC_CLIENT.getPlayerBlockPos().getX(),
// MC_CLIENT.getPlayerBlockPos().getY(),
// MC_CLIENT.getPlayerBlockPos().getZ()
//);
//endregion
//=====================//
// render thread tasks //
//=====================//
///region
// only run these tasks once per frame
if (!renderingDeferredLayer)
{
profiler.push("DH render thread tasks");
//===============//
// chat messages //
//===============//
this.sendQueuedChatMessages();
//======================//
// GL Proxy queued jobs //
//======================//
try
{
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
RenderThreadTaskHandler.INSTANCE.runRenderThreadTasks();
}
catch (Exception e)
{
LOGGER.error("Unexpected issue running render thread tasks, error: [" + e.getMessage() + "].", e);
}
//==============//
// camera speed //
//==============//
long nowMs = System.currentTimeMillis();
if (this.msSinceLastSpeedCheck + MIN_MS_BETWEEN_SPEED_CHECKS < nowMs)
{
// calc time since last check
double secSinceLastCheck = (nowMs - this.msSinceLastSpeedCheck) / 1_000.0;
this.msSinceLastSpeedCheck = nowMs;
// get the distance traveled since last frame
Vec3d 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;
}
profiler.pop();
}
///endregion
//=================//
// parameter setup //
//=================//
///region
EDhApiRenderPass renderPass;
if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
@@ -450,86 +527,76 @@ public class ClientApi
renderPass = EDhApiRenderPass.OPAQUE_AND_TRANSPARENT;
}
DhApiRenderParam renderEventParam =
new DhApiRenderParam(
renderPass,
partialTicks,
RenderUtil.getNearClipPlaneDistanceInBlocks(partialTicks), RenderUtil.getFarClipPlaneDistanceInBlocks(),
mcProjectionMatrix, mcModelViewMatrix,
RenderUtil.createLodProjectionMatrix(mcProjectionMatrix, partialTicks), RenderUtil.createLodModelViewMatrix(mcModelViewMatrix),
levelWrapper.getMinHeight()
);
// A global render state variable is used since MC has split up their
// render prep and actual rendering into different threads/methods
// 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
RenderParams renderParams = new RenderParams(renderPass, RENDER_STATE);
///endregion
//Mat4f mcCombined = mcModelViewMatrix.copy();
//mcCombined.multiply(mcProjectionMatrix);
//
//com.seibel.distanthorizons.api.objects.math.DhApiMat4f dhCombined = renderEventParam.dhModelViewMatrix.copy();
//dhCombined.multiply(renderEventParam.dhProjectionMatrix);
//
//LOGGER.info("\n\n" +
// "API\n" +
// "Mc MVM: \n" + mcModelViewMatrix.toString() + "\n" +
// "Mc Proj: \n" + mcProjectionMatrix + "\n" +
// "Mc Combined:\n" + mcCombined.toString() + "\n" +
// "\n" +
// "DH MVM: \n" + renderEventParam.dhModelViewMatrix.toString() + "\n" +
// "DH Proj: \n" + renderEventParam.dhProjectionMatrix + "\n" +
// "DH Combined:\n" + mcCombined.toString()
//);
//============//
// validation //
//============//
///region
if (firstRenderTimeMs == 0)
{
firstRenderTimeMs = System.currentTimeMillis();
}
String validationMessage = renderParams.getValidationErrorMessage(firstRenderTimeMs);
if (validationMessage != null)
{
// store the error message so it can be seen on the F3 screen
this.lastRenderParamValidationMessage = validationMessage;
return;
}
else
{
this.lastRenderParamValidationMessage = null;
}
if (this.rendererDisabledBecauseOfExceptions)
{
// re-enable rendering if the user toggles DH rendering
if (!Config.Client.quickEnableRendering.get())
{
LOGGER.info("DH Renderer re-enabled after exception. Some rendering issues may occur. Please reboot Minecraft if you see any rendering issues.");
this.rendererDisabledBecauseOfExceptions = false;
Config.Client.quickEnableRendering.set(true);
}
return;
}
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DISABLED)
{
return;
}
///endregion
// render validation //
//===========//
// rendering //
//===========//
///region
try
{
// TODO write this message to the F3 menu so people can see when a different mod screws with the lightmap
String reasonLodsCannotRender = RenderUtil.shouldLodsRender(levelWrapper, renderEventParam);
if (reasonLodsCannotRender != null)
{
return;
}
IDhClientWorld dhClientWorld = SharedApi.getIDhClientWorld();
if (dhClientWorld == null)
{
return;
}
IDhClientLevel level = (IDhClientLevel) dhClientWorld.getLevel(levelWrapper);
if (level == null)
{
return;
}
if (this.rendererDisabledBecauseOfExceptions)
{
// re-enable rendering if the user toggles DH rendering
if (!Config.Client.quickEnableRendering.get())
{
LOGGER.info("DH Renderer re-enabled after exception. Some rendering issues may occur. Please reboot Minecraft if you see any rendering issues.");
this.rendererDisabledBecauseOfExceptions = false;
Config.Client.quickEnableRendering.set(true);
}
return;
}
// render pass //
if (!renderingDeferredLayer)
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
{
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
if (!renderingDeferredLayer)
{
this.renderingCancelledForThisFrame = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, renderEventParam);
if (!this.renderingCancelledForThisFrame)
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, renderParams);
if (!renderingCancelled)
{
level.render(renderEventParam, profiler);
LodRenderer.INSTANCE.render(renderParams, profiler);
}
if (!DhApi.Delayed.renderProxy.getDeferTransparentRendering())
@@ -537,25 +604,42 @@ public class ClientApi
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
}
}
else if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEBUG)
else
{
profiler.push("Render Debug");
ClientApi.TEST_RENDERER.render();
profiler.pop();
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, renderParams);
if (!renderingCancelled)
{
LodRenderer.INSTANCE.renderDeferred(renderParams, profiler);
}
if (DhApi.Delayed.renderProxy.getDeferTransparentRendering())
{
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
}
}
}
else
{
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, renderEventParam);
if (!renderingCancelled)
if (!renderingDeferredLayer)
{
level.renderDeferred(renderEventParam, profiler);
}
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(renderParams);
testRenderer.render(renderParams);
if (DhApi.Delayed.renderProxy.getDeferTransparentRendering())
{
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
metaRenderer.runRenderPassCleanup(renderParams);
}
else
{
RATE_LIMITED_LOGGER.warn("Unable to find singleton [" + IDhTestTriangleRenderer.class.getSimpleName() + "]");
}
}
}
}
@@ -564,43 +648,54 @@ public class ClientApi
this.rendererDisabledBecauseOfExceptions = true;
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
MC_CLIENT.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons renderer has encountered an exception!");
MC_CLIENT.sendChatMessage("\u00A74Renderer disabled to try preventing GL state corruption.");
MC_CLIENT.sendChatMessage("\u00A74Toggle DH rendering via the config UI to re-activate DH rendering.");
MC_CLIENT.sendChatMessage("\u00A74Error: " + e);
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "" + MinecraftTextFormat.BOLD + "ERROR: Distant Horizons renderer has encountered an exception!" + MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Renderer disabled to try preventing GL state corruption." + MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Toggle DH rendering via the config UI to re-activate DH rendering." + MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Error: " + MinecraftTextFormat.CLEAR_FORMATTING + e);
}
finally
{
try
{
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
GLProxy.getInstance().runRenderThreadTasks();
}
catch (Exception e)
{
LOGGER.error("Unexpected issue running render thread tasks.", e);
}
///endregion
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
}
profiler.pop(); // end LOD
}
///endregion
//================//
// fade rendering //
//================//
///region
/**
* The first fade pass.
* Called after MC finishes rendering the opaque passes.
*/
public void renderFadeOpaque()
{
IDhVanillaFadeRenderer fadeRenderer = SingletonInjector.INSTANCE.get(IDhVanillaFadeRenderer.class);
if (fadeRenderer == null)
{
return;
}
// only fade when DH is rendering
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT
// only fade when requested
&& Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() == EDhApiMcRenderingFadeMode.DOUBLE_PASS
if (Config.Client.Advanced.Debugging.rendererMode.get() != EDhApiRendererMode.DISABLED
&&
(
// only fade when requested
Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() == EDhApiMcRenderingFadeMode.DOUBLE_PASS
// or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|| Config.Client.Advanced.Debugging.lodOnlyMode.get()
)
// don't fade when Iris shaders are active, otherwise the rendering can get weird
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{
FadeRenderer.INSTANCE.render(RENDER_STATE.mcModelViewMatrix, RENDER_STATE.mcProjectionMatrix, RENDER_STATE.frameTime, RENDER_STATE.clientLevelWrapper);
RenderParams renderParams = new RenderParams(EDhApiRenderPass.OPAQUE, RENDER_STATE);
fadeRenderer.render(renderParams);
}
}
/**
@@ -610,8 +705,14 @@ public class ClientApi
*/
public void renderFadeTransparent()
{
IDhVanillaFadeRenderer fadeRenderer = SingletonInjector.INSTANCE.get(IDhVanillaFadeRenderer.class);
if (fadeRenderer == null)
{
return;
}
// 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 =
(
@@ -624,17 +725,20 @@ public class ClientApi
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering();
if (renderFade)
{
FadeRenderer.INSTANCE.render(RENDER_STATE.mcModelViewMatrix, RENDER_STATE.mcProjectionMatrix, RENDER_STATE.frameTime, RENDER_STATE.clientLevelWrapper);
RenderParams renderParams = new RenderParams(EDhApiRenderPass.TRANSPARENT, RENDER_STATE);
fadeRenderer.render(renderParams);
}
}
}
///endregion
//=================//
// DEBUG USE //
//=================//
//==========//
// keyboard //
//==========//
///region
/** Trigger once on key press, with CLIENT PLAYER. */
public void keyPressedEvent(int glfwKey)
@@ -646,23 +750,32 @@ public class ClientApi
}
if (glfwKey == GLFW.GLFW_KEY_F8)
{
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)
if (glfwKey == GLFW.GLFW_KEY_F6)
{
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());
}
else if (glfwKey == GLFW.GLFW_KEY_P)
else if (glfwKey == GLFW.GLFW_KEY_F7)
{
prefLoggerEnabled = !prefLoggerEnabled;
MC_CLIENT.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
Config.Client.Advanced.Debugging.lodOnlyMode.set(!Config.Client.Advanced.Debugging.lodOnlyMode.get());
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
private void sendQueuedChatMessages()
{
// this includes if the current build is a dev build
@@ -705,15 +818,15 @@ public class ClientApi
{
// dev build
if (ModInfo.IS_DEV_BUILD
&& !this.isDevBuildMessagePrinted && MC_CLIENT.playerExists())
&& !this.isDevBuildMessagePrinted
&& MC_CLIENT.playerExists())
{
this.isDevBuildMessagePrinted = true;
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
// remind the user that this is a development build
String message =
// green text
"\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r\n" +
MinecraftTextFormat.DARK_GREEN + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Issues may occur with this version.\n" +
"Here be dragons!\n";
MC_CLIENT.sendChatMessage(message);
@@ -737,7 +850,7 @@ public class ClientApi
{
String message =
// orange text
"\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r \n" +
MinecraftTextFormat.ORANGE + "Distant Horizons: Low memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Stuttering or low FPS may occur. \n" +
"Please increase Minecraft's available memory to 4 GB or more. \n" +
"This warning can be disabled in DH's config under Advanced -> Logging. \n";
@@ -751,22 +864,21 @@ public class ClientApi
if (!this.highVanillaRenderDistanceWarningPrinted
&& Config.Common.Logging.Warning.showHighVanillaRenderDistanceWarning.get())
{
this.highVanillaRenderDistanceWarningPrinted = true;
// DH generally doesn't need a vanilla render distance above 12
if (MC_RENDER.getRenderDistance() > 12)
{
this.highVanillaRenderDistanceWarningPrinted = true;
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
String message =
// yellow text
"\u00A7e" + "Distant Horizons: High vanilla render distance detected." + "\u00A7r \n" +
"Using a high vanilla render distance uses a lot of CPU power \n" +
"and doesn't improve graphics much after about 12.\n" +
"Lowing your vanilla render distance will give you better FPS\n" +
"and reduce stuttering at a similar visual quality.\n" +
// gray text
"\u00A77" + "A vanilla render distance of 8 is recommended." + "\u00A7r \n" +
"This message can be disabled in DH's config under Advanced -> Logging.\n";
MinecraftTextFormat.YELLOW + "Distant Horizons: High vanilla render distance detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Using a high vanilla render distance uses a lot of CPU power \n" +
"and doesn't improve graphics much after about 12.\n" +
"Lowering your vanilla render distance will give you better FPS\n" +
"and reduce stuttering at a similar visual quality.\n" +
MinecraftTextFormat.GRAY + "A vanilla render distance of 8 is recommended." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"This message can be disabled in DH's config under Advanced -> Logging.\n";
MC_CLIENT.sendChatMessage(message);
}
}
@@ -796,6 +908,8 @@ public class ClientApi
*/
public void showOverlayMessageNextFrame(String message) { this.overlayMessageQueueForNextFrame.add(message); }
///endregion
}
@@ -4,13 +4,14 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.apache.logging.log4j.LogManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -22,8 +23,10 @@ import java.util.function.Consumer;
*/
public class ClientPluginChannelApi
{
private static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
() -> Config.Common.Logging.logNetworkEvent.get());
private static final DhLogger LOGGER = new DhLoggerBuilder()
.fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
.build();
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
@@ -75,14 +78,19 @@ public class ClientPluginChannelApi
private void onLevelInitMessage(LevelInitMessage msg)
{
if (!msg.levelKey.matches(LevelInitMessage.VALIDATION_REGEX))
if (!msg.serverKey.isEmpty() && !msg.serverKey.matches(LevelInitMessage.SERVER_KEY_REGEX))
{
throw new IllegalArgumentException("Server sent invalid server key.");
}
if (!msg.levelKey.matches(LevelInitMessage.LEVEL_KEY_REGEX))
{
throw new IllegalArgumentException("Server sent invalid level key.");
}
LOGGER.info("Server level key received: [" + msg.levelKey + "].");
MC.executeOnRenderThread(() ->
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("ClientPluginChannelApi onLevelInitMessage", () ->
{
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
@@ -105,10 +113,12 @@ public class ClientPluginChannelApi
this.levelUnloadHandler.accept(clientLevel);
}
if (existingKeyedClientLevel == null || !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
if (existingKeyedClientLevel == null
|| !existingKeyedClientLevel.getServerKey().equals(msg.serverKey)
|| !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
{
LOGGER.info("Loading level with key: [" + msg.levelKey + "].");
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.levelKey);
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.serverKey, msg.levelKey);
this.levelLoadHandler.accept(keyedLevel);
}
});
@@ -30,7 +30,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
/**
@@ -41,7 +41,7 @@ public class ServerApi
{
public static final ServerApi INSTANCE = new ServerApi();
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@@ -53,30 +53,6 @@ public class ServerApi
//=============//
// tick events //
//=============//
public void serverTickEvent()
{
try
{
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
if (serverWorld != null)
{
serverWorld.serverTick();
SharedApi.worldGenTick(serverWorld::worldGenTick);
}
}
catch (Exception e)
{
// try catch is necessary to prevent crashing the internal server when an exception is thrown
LOGGER.error("ServerTickEvent error: " + e.getMessage(), e);
}
}
//===============//
// server events //
//===============//
@@ -106,15 +82,15 @@ public class ServerApi
// level events //
//==============//
public void serverLevelLoadEvent(IServerLevelWrapper level)
public void serverLevelLoadEvent(IServerLevelWrapper levelWrapper)
{
LOGGER.debug("Server Level " + level + " loading");
LOGGER.debug("Server Level " + levelWrapper + " loading");
AbstractDhWorld serverWorld = SharedApi.getAbstractDhWorld();
if (serverWorld != null)
{
serverWorld.getOrLoadLevel(level);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level));
serverWorld.getOrLoadLevel(levelWrapper);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
}
}
public void serverLevelUnloadEvent(IServerLevelWrapper level)
@@ -125,7 +101,6 @@ public class ServerApi
if (serverWorld != null)
{
serverWorld.unloadLevel(level);
SharedApi.INSTANCE.clearQueuedChunkUpdates();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
}
}
@@ -136,8 +111,8 @@ public class ServerApi
// chunk modified events //
//=======================//
public void serverChunkLoadEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level, false, false); }
public void serverChunkSaveEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level, true, 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); }
@@ -152,7 +127,7 @@ public class ServerApi
return;
}
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
LOGGER.info("Player ["+player.getName()+"] joined.");
if (serverWorld != null)
{
@@ -166,7 +141,7 @@ public class ServerApi
return;
}
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
LOGGER.info("Player ["+player.getName()+"] disconnected.");
if (serverWorld != null)
{
@@ -180,7 +155,7 @@ public class ServerApi
return;
}
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
LOGGER.info("Player ["+player.getName()+"] changed level: ["+originLevel.getKeyedLevelDimensionName()+"] -> ["+destinationLevel.getKeyedLevelDimensionName()+"].");
if (serverWorld != null)
{
@@ -200,7 +175,7 @@ public class ServerApi
return;
}
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
if (serverWorld != null)
{
serverWorld.getServerPlayerStateManager().handlePluginMessage(player, message);
@@ -24,30 +24,26 @@ import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldUn
import com.seibel.distanthorizons.core.Initializer;
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateData;
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.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
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.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.util.LodUtil;
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.world.*;
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.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@@ -58,146 +54,142 @@ public class SharedApi
{
public static final SharedApi INSTANCE = new SharedApi();
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** will be null on the server-side */
@Nullable
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();
/**
* 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;
public static final WorldChunkUpdateManager WORLD_CHUNK_UPDATE_MANAGER = WorldChunkUpdateManager.INSTANCE; // local fariable for quick access
@Nullable
private static AbstractDhWorld currentWorld;
private static int lastWorldGenTickDelta = 0;
private static final Object worldLockObject = new Object();
//=============//
// constructor //
//=============//
//region
private SharedApi() { }
public static void init() { Initializer.init(); }
//endregion
//===============//
// world methods //
//===============//
//region
public static EWorldEnvironment getEnvironment() { return (currentWorld == null) ? null : currentWorld.environment; }
public static void setDhWorld(AbstractDhWorld newWorld)
{
AbstractDhWorld oldWorld = currentWorld;
if (oldWorld != null)
synchronized (worldLockObject)
{
oldWorld.close();
}
currentWorld = newWorld;
// starting and stopping the DataRenderTransformer is necessary to prevent attempting to
// access the MC level at inappropriate times, which can cause exceptions
if (currentWorld != null)
{
ThreadPoolUtil.setupThreadPools();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldLoadEvent.class, new DhApiWorldLoadEvent.EventParam());
}
else
{
ThreadPoolUtil.shutdownThreadPools();
DebugRenderer.clearRenderables();
if (MC_RENDER != null)
AbstractDhWorld oldWorld = currentWorld;
if (oldWorld != null)
{
MC_RENDER.clearTargetFrameBuffer();
oldWorld.close();
}
currentWorld = newWorld;
// shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed
AbstractDhRepo.closeAllConnections();
// needs to be closed on world shutdown to clear out un-processed chunks
CHUNK_UPDATE_QUEUE_MANAGER.clear();
// starting and stopping the DataRenderTransformer is necessary to prevent attempting to
// access the MC level at inappropriate times, which can cause exceptions
if (currentWorld != null)
{
ThreadPoolUtil.setupThreadPools();
// recommend that the garbage collector cleans up any objects from the old world and thread pools
System.gc();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldLoadEvent.class, new DhApiWorldLoadEvent.EventParam());
}
else
{
ThreadPoolUtil.shutdownThreadPools();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldUnloadEvent.class, new DhApiWorldUnloadEvent.EventParam());
// delayed get because SharedApi will be created before the singleton has been bound
AbstractDebugWireframeRenderer debugWireframeRenderer = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
debugWireframeRenderer.clearRenderables();
// fired after the unload event so API users can't change the read-only for any new worlds
DhApiWorldProxy.INSTANCE.setReadOnly(false, false);
}
}
if (MC_RENDER != null)
{
MC_RENDER.clearTargetFrameBuffer();
}
public static void worldGenTick(Runnable worldGenRunnable)
{
lastWorldGenTickDelta--;
if (lastWorldGenTickDelta <= 0)
{
worldGenRunnable.run();
lastWorldGenTickDelta = 20;
// shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed
AbstractDhRepo.closeAllConnections();
// needs to be closed on world shutdown to clear out un-processed chunks
WORLD_CHUNK_UPDATE_MANAGER.clear();
RenderThreadTaskHandler.INSTANCE.clearDebugStats();
// recommend that the garbage collector cleans up any objects from the old world and thread pools
System.gc();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiWorldUnloadEvent.class, new DhApiWorldUnloadEvent.EventParam());
// fired after the unload event so API users can't change the read-only for any new worlds
DhApiWorldProxy.INSTANCE.setReadOnly(false, false);
}
}
}
@Nullable
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientServerWorld} */
public static DhClientServerWorld getDhClientServerWorld() { return (currentWorld instanceof DhClientServerWorld) ? (DhClientServerWorld) currentWorld : null; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientWorld} or {@link DhClientServerWorld} */
public static IDhClientWorld getIDhClientWorld() { return (currentWorld instanceof IDhClientWorld) ? (IDhClientWorld) currentWorld : null; }
@Nullable
public static IDhClientWorld tryGetDhClientWorld() { return (currentWorld instanceof IDhClientWorld) ? (IDhClientWorld) currentWorld : null; }
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhServerWorld} or {@link DhClientServerWorld} */
public static IDhServerWorld getIDhServerWorld() { return (currentWorld instanceof IDhServerWorld) ? (IDhServerWorld) currentWorld : null; }
@Nullable
public static IDhServerWorld tryGetDhServerWorld() { return (currentWorld instanceof IDhServerWorld) ? (IDhServerWorld) currentWorld : null; }
//endregion
//==============//
// chunk update //
//==============//
//region
/**
* 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.
*/
public static boolean isChunkAtBlockPosAlreadyUpdating(int blockPosX, int blockPosZ)
{ return CHUNK_UPDATE_QUEUE_MANAGER.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ))); }
public static boolean isChunkAtChunkPosAlreadyUpdating(int chunkPosX, int chunkPosZ)
{ return CHUNK_UPDATE_QUEUE_MANAGER.contains(new DhChunkPos(chunkPosX, chunkPosZ)); }
/**
* This is often fired when unloading a level.
* This is done to prevent overloading the system when
* rapidly changing dimensions.
* (IE prevent DH from infinitely allocating memory
*/
public void clearQueuedChunkUpdates() { CHUNK_UPDATE_QUEUE_MANAGER.clear(); }
public int getQueuedChunkUpdateCount() { return CHUNK_UPDATE_QUEUE_MANAGER.getQueuedCount(); }
/** handles both block place and break events */
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) { this.applyChunkUpdate(chunkWrapper, level, canGetNeighboringChunks, false); }
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks, boolean newlyLoaded)
public static boolean isChunkAtBlockPosAlreadyUpdating(ILevelWrapper levelWrapper, int blockPosX, int blockPosZ)
{
//========================//
// world and level checks //
//========================//
ChunkUpdateQueueManager manager = WORLD_CHUNK_UPDATE_MANAGER.getByLevelWrapper(levelWrapper);
if (manager == null)
{
return true;
}
return manager.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ)));
}
public static boolean isChunkAtChunkPosAlreadyUpdating(ILevelWrapper levelWrapper, int chunkPosX, int chunkPosZ)
{
ChunkUpdateQueueManager manager = WORLD_CHUNK_UPDATE_MANAGER.getByLevelWrapper(levelWrapper);
if (manager == null)
{
return true;
}
return manager.contains(new DhChunkPos(chunkPosX, chunkPosZ));
}
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper levelWrapper)
{
//===================//
// validation checks //
//===================//
if (chunkWrapper == null)
{
@@ -208,11 +200,11 @@ public class SharedApi
AbstractDhWorld dhWorld = SharedApi.getAbstractDhWorld();
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.
// 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);
}
@@ -225,21 +217,21 @@ public class SharedApi
return;
}
// only continue if the level is loaded
IDhLevel dhLevel = dhWorld.getLevel(level);
IDhLevel dhLevel = dhWorld.getLevel(levelWrapper);
if (dhLevel == null)
{
if (level instanceof IClientLevelWrapper)
if (levelWrapper instanceof IClientLevelWrapper)
{
// 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);
}
return;
}
// ignore chunk updates if the network should handle them
if (dhLevel instanceof DhClientLevel)
{
if (!((DhClientLevel) dhLevel).shouldProcessChunkUpdate(chunkWrapper.getChunkPos()))
@@ -248,291 +240,67 @@ public class SharedApi
}
}
// shoudln't normally happen, but just in case
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
// ignore chunk updates for non-rendered levels
String dimName = dhLevel.getLevelWrapper().getDimensionName();
if (IgnoredDimensionCsvHandler.INSTANCE.dimensionNameShouldBeIgnored(dimName))
{
return;
}
ChunkUpdateQueueManager chunkManager = WORLD_CHUNK_UPDATE_MANAGER.getByLevelWrapper(levelWrapper);
// 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;
}
//===============================//
// update the necessary chunk(s) //
//===============================//
if (!canGetNeighboringChunks)
{
// only update the center chunk
queueChunkUpdate(chunkWrapper, null, dhLevel, false);
return;
}
ArrayList<IChunkWrapper> neighboringChunkList = getNeighborChunkListForChunk(chunkWrapper, dhLevel);
if (newlyLoaded)
{
// this means this chunkWrapper is a newly loaded chunk
// which may be missing some neighboring chunk data
// because it is bordering the render distance
// thus, only the chunks neighboring this chunkWrapper will get updated
// because those are more likely to have their full neighboring chunk data
//TODO this does not prevent those neighboring chunks from updating
// this newly loaded chunk that were just skipped
// leading to occasional lighting issues
for (IChunkWrapper neighboringChunk : neighboringChunkList)
{
if (neighboringChunk == chunkWrapper)
{
continue;
}
this.applyChunkUpdate(neighboringChunk, level, true, false);
}
}
else
{
// if not all neighboring chunk data is available, do not try to update
if (neighboringChunkList.size() < 9)
{
return;
}
// update the center with any existing neighbour chunks.
// this is done so lighting changes are propagated correctly
queueChunkUpdate(chunkWrapper, neighboringChunkList, dhLevel, true);
}
}
private static ArrayList<IChunkWrapper> getNeighborChunkListForChunk(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
{
// 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 = dhLevel.getLevelWrapper().tryGetChunk(neighborPos);
if (neighborChunk != null)
{
neighborChunkList.add(neighborChunk);
}
}
}
}
return neighborChunkList;
queueChunkUpdate(chunkManager, chunkWrapper, dhLevel);
}
private static void queueChunkUpdate(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighborChunkList, IDhLevel dhLevel, boolean canGetNeighboringChunks)
private static void queueChunkUpdate(ChunkUpdateQueueManager chunkManager, IChunkWrapper chunkWrapper, IDhLevel dhLevel)
{
// return if the chunk is already queued
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
if (chunkManager.contains(chunkWrapper.getChunkPos()))
{
return;
}
// add chunk update data to preUpdate queue
ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, neighborChunkList, dhLevel, canGetNeighboringChunks);
CHUNK_UPDATE_QUEUE_MANAGER.addItemToPreUpdateQueue(chunkWrapper.getChunkPos(), updateData);
ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, dhLevel);
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) //
//===============================//
// process preUpdate queue
processQueuedChunkPreUpdate();
// process update queue
processQueuedChunkUpdate();
// queue the next position if there are still positions to process
AbstractExecutorService executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor != null && !CHUNK_UPDATE_QUEUE_MANAGER.isEmpty())
if (executor != null)
{
try
{
executor.execute(SharedApi::processQueue);
executor.execute(WORLD_CHUNK_UPDATE_MANAGER::processEachQueue);
}
catch (RejectedExecutionException ignore)
{
// the executor was shut down, it should be back up shortly and able to accept new jobs
}
}
}
private static void processQueuedChunkPreUpdate()
{
ChunkUpdateData preUpdateData = CHUNK_UPDATE_QUEUE_MANAGER.preUpdateQueue.popClosest();
if (preUpdateData == null)
{
return;
}
IDhLevel dhLevel = preUpdateData.dhLevel;
IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper;
boolean canGetNeighboringChunks = preUpdateData.canGetNeighboringChunks;
ArrayList<IChunkWrapper> neighborChunkList = preUpdateData.neighborChunkList;
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;
}
// if this chunk will update and can get neighbors
// then queue neighboring chunks to update as well
// neighboring chunk will get added directly to the update queue
// so they won't queue further chunk updates
if (neighborChunkList != null
&& !neighborChunkList.isEmpty())
{
for (IChunkWrapper adjacentChunk : neighborChunkList)
{
// pulling a new chunkWrapper is necessary to prevent concurrent modification on the existing chunkWrappers
IChunkWrapper newCenterChunk = dhLevel.getLevelWrapper().tryGetChunk(adjacentChunk.getChunkPos());
if (newCenterChunk != null)
{
ChunkUpdateData newUpdateData;
if (canGetNeighboringChunks)
{
newUpdateData = new ChunkUpdateData(newCenterChunk, getNeighborChunkListForChunk(newCenterChunk, dhLevel), dhLevel, true);
}
else
{
newUpdateData = new ChunkUpdateData(newCenterChunk, null, dhLevel, false);
}
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(newCenterChunk.getChunkPos(), newUpdateData);
}
}
}
}
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()
{
//LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
ChunkUpdateData updateData = CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.popClosest();
if (updateData == null)
{
return;
}
IChunkWrapper chunkWrapper = updateData.chunkWrapper;
IDhLevel dhLevel = updateData.dhLevel;
// having a list of the nearby chunks is needed for lighting and beacon generation
@Nullable ArrayList<IChunkWrapper> nearbyChunkList = updateData.neighborChunkList;
// a non-null list is needed for the lighting engine
if (nearbyChunkList == null)
{
nearbyChunkList = new ArrayList<IChunkWrapper>();
nearbyChunkList.add(chunkWrapper);
}
try
{
// sky lighting is populated later at the data source level
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, dhLevel.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);
}
}
//endregion
//=========//
// F3 Menu //
//=========//
//region
public String 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());
public ArrayList<String> getDebugMenuString() { return WORLD_CHUNK_UPDATE_MANAGER.getDebugMenuString(); }
String maxUpdateCountStr = F3Screen.NUMBER_FORMAT.format(CHUNK_UPDATE_QUEUE_MANAGER.maxSize);
return "Queued chunk updates: "+"( "+preUpdatingCountStr+" + "+updatingCountStr+" ) [ "+queuedCountStr+" / "+maxUpdateCountStr+" ]";
}
//endregion
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import org.jetbrains.annotations.Nullable;
import java.util.Comparator;
import java.util.concurrent.ConcurrentHashMap;
@@ -105,6 +106,7 @@ public class ChunkPosQueue
this.furthestQueue.remove(closest);
return this.updateDataByChunkPos.remove(closest);
}
@Nullable
public ChunkUpdateData popFurthest()
{
if (this.furthestQueue.isEmpty())
@@ -9,18 +9,13 @@ import java.util.ArrayList;
public class ChunkUpdateData
{
public IChunkWrapper chunkWrapper;
@Nullable
public ArrayList<IChunkWrapper> neighborChunkList;
public IDhLevel dhLevel;
public boolean canGetNeighboringChunks;
public ChunkUpdateData(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighborChunkList, IDhLevel dhLevel, boolean canGetNeighborChunks)
public ChunkUpdateData(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
{
this.chunkWrapper = chunkWrapper;
this.neighborChunkList = neighborChunkList;
this.dhLevel = dhLevel;
this.canGetNeighboringChunks = canGetNeighborChunks;
}
}
@@ -1,30 +1,79 @@
package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
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.f3.F3Screen;
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 org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.logging.DhLogger;
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 java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.*;
/**
* @see WorldChunkUpdateManager
*/
public class ChunkUpdateQueueManager
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
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 preUpdateQueue;
public final ConcurrentMap<DhChunkPos, IChunkWrapper> queuedChunkWrapperByChunkPos = CacheBuilder.newBuilder()
.expireAfterWrite(20, TimeUnit.SECONDS)
.<DhChunkPos, IChunkWrapper>build()
.asMap();
/** dynamically changes based on the number of threads currently available */
public int maxSize = 500;
private static long lastOverloadedLogMessageMsTime = 0;
/** used to prevent flickering */
public long lastMsTimeShownActiveInF3Screen = System.currentTimeMillis();
//=============//
// constructor //
//=============//
//region
public ChunkUpdateQueueManager()
{
@@ -32,21 +81,31 @@ public class ChunkUpdateQueueManager
this.preUpdateQueue = new ChunkPosQueue();
}
//endregion
//==================//
// list/set methods //
//==================//
//region
public boolean contains(DhChunkPos pos) { return this.updateQueue.contains(pos) || this.preUpdateQueue.contains(pos); }
public boolean contains(DhChunkPos pos)
{
return this.updateQueue.contains(pos)
|| this.ignoredChunkPosSet.contains(pos)
|| this.preUpdateQueue.contains(pos);
}
public void clear()
{
this.updateQueue.clear();
this.preUpdateQueue.clear();
this.ignoredChunkPosSet.clear();
}
public int getQueuedCount() { return this.updateQueue.getQueuedCount() + this.preUpdateQueue.getQueuedCount(); }
public boolean isEmpty()
public boolean updateQueuesEmpty()
{
return this.updateQueue.isEmpty()
&& this.preUpdateQueue.isEmpty();
@@ -57,41 +116,27 @@ public class ChunkUpdateQueueManager
* If there are no more slots, replaces the item furthest from the center in the update queue.
*/
public void addItemToPreUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
{
int remainingSlots = this.maxSize - this.getQueuedCount();
// If no slots are left, get one by removing the item furthest from the center
if (remainingSlots <= 0)
{
if (!this.updateQueue.isEmpty())
{
this.updateQueue.popFurthest();
}
else
{
this.preUpdateQueue.popFurthest();
}
}
this.preUpdateQueue.addItem(pos, updateData);
remainingSlots = this.maxSize - this.getQueuedCount();
if (remainingSlots <= 0)
{
this.sendOverloadMessage();
}
}
{ this.addItemToQueue(pos, updateData, this.preUpdateQueue); }
public void addItemToUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
{ this.addItemToQueue(pos, updateData, this.updateQueue); }
private void addItemToQueue(DhChunkPos pos, ChunkUpdateData updateData, ChunkPosQueue queue)
{
int remainingSlots = this.maxSize - this.getQueuedCount();
// If no slots are left, get one by removing the item furthest from the center
if (remainingSlots <= 0)
{
this.updateQueue.popFurthest();
ChunkUpdateData removedData = queue.popFurthest();
if (removedData != null)
{
this.queuedChunkWrapperByChunkPos.remove(removedData.chunkWrapper.getChunkPos());
}
}
this.updateQueue.addItem(pos,updateData);
queue.addItem(pos,updateData);
this.queuedChunkWrapperByChunkPos.putIfAbsent(pos, updateData.chunkWrapper);
remainingSlots = this.maxSize - this.getQueuedCount();
if (remainingSlots <= 0)
@@ -100,18 +145,19 @@ public class ChunkUpdateQueueManager
}
}
private void sendOverloadMessage()
{
// limit how often an overloaded message can be sent
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();
String message = "\u00A76" + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + "\u00A7r" +
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. " +
"\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();
if (showWarningInChat)
@@ -129,11 +175,200 @@ public class ChunkUpdateQueueManager
}
}
/**
* Tries to return a cloned chunk wrapper from memory.
* Returns null if no chunk is available.
* <br><br>
* This is done instead of accessing the MC level since
* accessing the level often requires running on the render or server
* thread, which causes stuttering.
*/
@Nullable
public IChunkWrapper tryGetChunk(DhChunkPos pos)
{
IChunkWrapper existingWrapper = this.queuedChunkWrapperByChunkPos.get(pos);
if (existingWrapper == null)
{
return null;
}
return existingWrapper.copy();
}
//endregion
//=========//
// ignores //
//=========//
//region
public void addPosToIgnore(DhChunkPos chunkPos) { this.ignoredChunkPosSet.add(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 //
//==================//
//region
public void setCenter(DhChunkPos newCenter)
{
@@ -141,5 +376,33 @@ public class ChunkUpdateQueueManager
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
}
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.api.internal.rendering;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
/**
@@ -9,13 +10,36 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
*
* @see ClientApi
*/
public class RenderState
public class DhRenderState
{
public Mat4f mcModelViewMatrix = null;
public Mat4f 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;
/**
* 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 RenderState
errorReasons += "no Projection Matrix, ";
}
if (this.frameTime == -1)
if (this.partialTickTime == -1)
{
errorReasons += "no Frame Time, ";
}
@@ -51,13 +75,6 @@ public class RenderState
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
{
String errorReasons = this.unableToRenderBecause();
File diff suppressed because it is too large Load Diff
@@ -26,28 +26,27 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.config.ILangWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.logging.DhLogger;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.*;
/**
* Indexes and sets everything up for the file handling and gui.
* This should be init after singletons have been bound
* Sets up everything in {@link Config} for the file/GUI and keeps track of all
* entries therein. <br>
* This should be run after the singletons have been bound.
*
* @author coolGi
* @author Ran
* @version 2023-8-26
*
* @see Config
*/
public class ConfigBase
public class ConfigHandler
{
/** Our own config instance, don't modify unless you are the DH mod */
public static ConfigBase INSTANCE;
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
/**
* What the config works with
@@ -63,13 +62,13 @@ public class ConfigBase
* <br> {@link String}
* <br>
* <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> ArrayList<T>
* <br> Map<String, T>
* <br> HashMap<String, T>
*/
public static final List<Class<?>> ACCEPTABLE_INPUTS = new ArrayList<Class<?>>()
private static final List<Class<?>> ACCEPTABLE_INPUTS = new ArrayList<Class<?>>()
{{
this.add(Boolean.class);
this.add(Byte.class);
@@ -80,7 +79,7 @@ public class ConfigBase
this.add(Float.class);
this.add(String.class);
// TODO[CONFIG]: Check the type of these is valid
// partially implemented but not entirely
this.add(List.class);
this.add(ArrayList.class);
this.add(Map.class);
@@ -89,15 +88,17 @@ public class ConfigBase
public ConfigFileHandler configFileHandler;
public static final ConfigHandler INSTANCE = new ConfigHandler();
public final int configVersion;
public final ConfigFileHandler configFileHandler = new ConfigFileHandler(getConfigPath());
public final List<AbstractConfigBase<?>> configBaseList = new ArrayList<>();
public boolean isLoaded = false;
/** Disables the minimum and maximum of any variable */
public boolean disableMinMax = false; // Very fun to use, but should always be disabled by default
public final List<AbstractConfigType<?, ?>> entries = new ArrayList<>();
/**
* Disables the minimum and maximum validation. <Br>
* Fun to use, but should be disabled by default.
*/
public boolean runMinMaxValidation = true;
@@ -105,92 +106,104 @@ public class ConfigBase
// constructor //
//=============//
public static void RunFirstTimeSetup()
public static void tryRunFirstTimeSetup()
{
if (INSTANCE != null)
if (INSTANCE.isLoaded)
{
LOGGER.debug("ConfigBase setup already run, ignoring.");
LOGGER.debug("ConfigHandler setup already run, ignoring.");
return;
}
INSTANCE = new ConfigBase(Config.class, ModInfo.CONFIG_FILE_VERSION);
INSTANCE.runFirstTimeSetup();
}
private ConfigBase(Class<?> configClass, int configVersion)
private void runFirstTimeSetup()
{
LOGGER.info("Initialising config for [" + ModInfo.NAME + "]");
this.configVersion = configVersion;
this.initNestedClass(Config.class, ""); // Init root category
this.initNestedClass(configClass, ""); // Init root category
Path configPath = getConfigPath(ModInfo.NAME);
this.configFileHandler = new ConfigFileHandler(this, configPath);
this.configFileHandler.loadFromFile();
this.runMinMaxValidation = !Config.Client.Advanced.Debugging.allowUnsafeValues.get();
this.isLoaded = true;
LOGGER.info("Config for [" + ModInfo.NAME + "] initialised");
LOGGER.info("[" + ModInfo.NAME + "] Config initialised");
}
/** Gets the default config path given a mod name */
private static Path getConfigPath(String modName)
private static Path getConfigPath()
{
return SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class)
.getInstallationDirectory().toPath().resolve("config").resolve(modName + ".toml");
return MC_SHARED
.getInstallationDirectory().toPath()
.resolve("config")
.resolve(ModInfo.NAME + ".toml");
}
/** Put all the config entries into configEntryList */
private void initNestedClass(Class<?> configClass, String category)
{
// Put all the entries in entries
for (Field field : configClass.getFields())
Field[] fields = configClass.getFields();
for (Field field : fields)
{
if (AbstractConfigType.class.isAssignableFrom(field.getType()))
// ignore any non-config variables
if (!AbstractConfigBase.class.isAssignableFrom(field.getType()))
{
try
continue;
}
// add this config to the master list
try
{
this.configBaseList.add((AbstractConfigBase<?>) field.get(field.getType()));
}
catch (IllegalAccessException e)
{
LOGGER.warn("Unable to add config ["+field.getType().getName()+"], error: ["+e.getMessage()+"].", e);
continue;
}
// set any necessary variables in this config
AbstractConfigBase<?> configBase = this.configBaseList.get(this.configBaseList.size() - 1);
configBase.category = category;
configBase.name = field.getName();
// validate the config's input type
if (ConfigEntry.class.isAssignableFrom(field.getType()))
{
if (!isAcceptableType(configBase.getType()))
{
this.entries.add((AbstractConfigType<?, ?>) field.get(field.getType()));
LOGGER.error("Invalid variable type at [" + (category.isEmpty() ? "" : category + ".") + field.getName() + "].");
LOGGER.error("Type [" + configBase.getType() + "] is not one of these types [" + ACCEPTABLE_INPUTS.toString() + "]");
this.configBaseList.remove(this.configBaseList.size() - 1); // Delete the entry if it is invalid so the game can still run
}
catch (IllegalAccessException exception)
}
// recursively add deeper categories if present
if (ConfigCategory.class.isAssignableFrom(field.getType()))
{
ConfigCategory configCategory = (ConfigCategory) configBase;
if (configCategory.getDestination() == null)
{
LOGGER.warn(exception);
configCategory.destination = configBase.getNameAndCategory();
}
AbstractConfigType<?, ?> entry = this.entries.get(this.entries.size() - 1);
entry.category = category;
entry.name = field.getName();
entry.configBase = this;
if (ConfigEntry.class.isAssignableFrom(field.getType()))
// shouldn't happen, but just in case
if (configBase.get() != null)
{
// If item is type ConfigEntry
if (!isAcceptableType(entry.getType()))
{
LOGGER.error("Invalid variable type at [" + (category.isEmpty() ? "" : category + ".") + field.getName() + "].");
LOGGER.error("Type [" + entry.getType() + "] is not one of these types [" + ACCEPTABLE_INPUTS.toString() + "]");
this.entries.remove(this.entries.size() - 1); // Delete the entry if it is invalid so the game can still run
}
}
if (ConfigCategory.class.isAssignableFrom(field.getType()))
{ // If it's a category then init the stuff inside it and put it in the category list
assert entry instanceof ConfigCategory;
if (((ConfigCategory) entry).getDestination() == null)
((ConfigCategory) entry).destination = entry.getNameWCategory();
if (entry.get() != null)
{
this.initNestedClass(((ConfigCategory) entry).get(), ((ConfigCategory) entry).getDestination());
}
this.initNestedClass(configCategory.get(), configCategory.getDestination());
}
}
}
}
private static boolean isAcceptableType(Class<?> Clazz)
private static boolean isAcceptableType(Class<?> inputClass)
{
if (Clazz.isEnum())
if (inputClass.isEnum())
{
return true;
}
return ACCEPTABLE_INPUTS.contains(Clazz);
return ACCEPTABLE_INPUTS.contains(inputClass);
}
@@ -219,9 +232,9 @@ public class ConfigBase
String ending = "\",\n";
// config entries
for (AbstractConfigType<?, ?> entry : this.entries)
for (AbstractConfigBase<?> entry : this.configBaseList)
{
String entryPrefix = "distanthorizons.config." + entry.getNameWCategory();
String entryPrefix = "distanthorizons.config." + entry.getNameAndCategory();
if (checkEnums
&& entry.getType().isEnum()
@@ -248,7 +261,6 @@ public class ConfigBase
if (ConfigUIComment.class.isAssignableFrom(entry.getClass())
&& ((ConfigUIComment)entry).parentConfigPath != null)
{
// TODO this could potentially add the same item multiple times
entryPrefix = "distanthorizons.config." + ((ConfigUIComment)entry).parentConfigPath;
}
@@ -24,7 +24,7 @@ import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import java.util.HashMap;
import java.util.HashSet;
public class ConfigEntryWithPresetOptions<TQuickEnum, TConfig>
public class ConfigPresetOptions<TQuickEnum, TConfig>
{
public final ConfigEntry<TConfig> configEntry;
@@ -32,7 +32,11 @@ public class ConfigEntryWithPresetOptions<TQuickEnum, TConfig>
public ConfigEntryWithPresetOptions(ConfigEntry<TConfig> configEntry, HashMap<TQuickEnum, TConfig> configOptionByQualityOption)
//=============//
// constructor //
//=============//
public ConfigPresetOptions(ConfigEntry<TConfig> configEntry, HashMap<TQuickEnum, TConfig> configOptionByQualityOption)
{
this.configEntry = configEntry;
this.configOptionByQualityOption = configOptionByQualityOption;
@@ -40,6 +44,10 @@ public class ConfigEntryWithPresetOptions<TQuickEnum, TConfig>
//=========//
// methods //
//=========//
public void updateConfigEntry(TQuickEnum quickQuality)
{
TConfig newValue = this.configOptionByQualityOption.get(quickQuality);
@@ -17,12 +17,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.objects.config;
package com.seibel.distanthorizons.core.config.api;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConfigEntry;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConverter;
import com.seibel.distanthorizons.coreapi.util.converters.DefaultConverter;
import com.seibel.distanthorizons.core.config.api.converters.DefaultConverter;
import java.util.function.Consumer;
@@ -41,11 +41,17 @@ import java.util.function.Consumer;
*/
public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<apiType>
{
private final IConfigEntry<coreType> configEntry;
private final ConfigEntry<coreType> configBase;
private final IConverter<coreType, apiType> configConverter;
//==============//
// constructors //
//==============//
//region
/**
* This constructor should only be called internally. <br>
* There is no reason for API users to create this object. <br><br>
@@ -53,9 +59,9 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
* Uses the default object converter, this requires coreType and apiType to be the same.
*/
@SuppressWarnings("unchecked") // DefaultConverter's cast is safe
public DhApiConfigValue(IConfigEntry<coreType> newConfigEntry)
public DhApiConfigValue(ConfigEntry<coreType> configBase)
{
this.configEntry = newConfigEntry;
this.configBase = configBase;
this.configConverter = (IConverter<coreType, apiType>) new DefaultConverter<coreType>();
}
@@ -63,22 +69,40 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
* This constructor should only be called internally. <br>
* There is no reason for API users to create this object. <br><br>
*/
public DhApiConfigValue(IConfigEntry<coreType> newConfigEntry, IConverter<coreType, apiType> newConverter)
public DhApiConfigValue(ConfigEntry<coreType> configBase, IConverter<coreType, apiType> newConverter)
{
this.configEntry = newConfigEntry;
this.configBase = configBase;
this.configConverter = newConverter;
}
//endregion
public apiType getValue() { return this.configConverter.convertToApiType(this.configEntry.get()); }
public apiType getTrueValue() { return this.configConverter.convertToApiType(this.configEntry.getTrueValue()); }
public apiType getApiValue() { return this.configConverter.convertToApiType(this.configEntry.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)
{
if (this.configEntry.getAllowApiOverride())
if (this.configBase.getAllowApiOverride())
{
this.configEntry.setApiValue(this.configConverter.convertToCoreType(newValue));
this.configBase.setApiValue(this.configConverter.convertToCoreType(newValue));
return true;
}
else
@@ -87,13 +111,13 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
}
}
@Override
public boolean clearValue()
{
if (this.configEntry.getAllowApiOverride())
if (this.configBase.getAllowApiOverride())
{
// 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.configEntry.setApiValue(null);
this.configBase.setApiValue(null);
return true;
}
else
@@ -102,20 +126,24 @@ public class DhApiConfigValue<coreType, apiType> implements IDhApiConfigValue<ap
}
}
public boolean getCanBeOverrodeByApi() { return this.configEntry.getAllowApiOverride(); }
@Override
public boolean getCanBeOverrodeByApi() { return this.configBase.getAllowApiOverride(); }
public apiType getDefaultValue() { return this.configConverter.convertToApiType(this.configEntry.getDefaultValue()); }
public apiType getMaxValue() { return this.configConverter.convertToApiType(this.configEntry.getMax()); }
public apiType getMinValue() { return this.configConverter.convertToApiType(this.configEntry.getMin()); }
@Override public apiType getDefaultValue() { return this.configConverter.convertToApiType(this.configBase.getDefaultValue()); }
@Override public apiType getMaxValue() { return this.configConverter.convertToApiType(this.configBase.getMax()); }
@Override public apiType getMinValue() { return this.configConverter.convertToApiType(this.configBase.getMin()); }
@Override
public void addChangeListener(Consumer<apiType> onValueChangeFunc)
{
this.configEntry.addValueChangeListener((coreValue) ->
this.configBase.addValueChangeListener((coreValue) ->
{
apiType apiValue = this.configConverter.convertToApiType(coreValue);
onValueChangeFunc.accept(apiValue);
});
}
//endregion
}
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.coreapi.util.converters;
package com.seibel.distanthorizons.core.config.api.converters;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogDrawMode;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConverter;
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.coreapi.util.converters;
package com.seibel.distanthorizons.core.config.api.converters;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConverter;
@@ -17,34 +17,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.enums.worldGeneration;
package com.seibel.distanthorizons.core.config.api.converters;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConverter;
/**
* MULTI_THREADED, <br>
* SINGLE_THREADED, <br>
* SERVER_THREAD, <br>
* Used to support deprecated config options that may be identical
* in implementation but with the On/Off values flipped.
*
* @author James Seibel
* @version 7-25-2022
* @version 2025-12-22
*/
public enum EWorldGenThreadMode
public class InvertedBoolConverter implements IConverter<Boolean, Boolean>
{
/**
* This world generator can be run on an unlimited number
* of concurrent threads.
*/
MULTI_THREADED,
/**
* This world generator can only be run on one thread at
* a time, however that thread can run concurrently
* to Minecraft's server thread.
*/
SINGLE_THREADED,
@Override
public Boolean convertToCoreType(Boolean core)
{ return !core; }
@Override
public Boolean convertToApiType(Boolean api)
{ return !api; }
/**
* This world generator can only be run on Minecraft's
* server thread.
*/
SERVER_THREAD,
}
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.coreapi.util.converters;
package com.seibel.distanthorizons.core.config.api.converters;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConverter;
@@ -0,0 +1,125 @@
/*
* 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.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.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
public class IgnoredDimensionCsvHandler extends DhApiBeforeRenderEvent implements IConfigListener
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static IgnoredDimensionCsvHandler INSTANCE = new IgnoredDimensionCsvHandler();
private String[] dimensionNames = null;
//=============//
// constructor //
//=============//
/** private since we only ever need one handler at a time */
private IgnoredDimensionCsvHandler() { }
//=================//
// config handling //
//=================//
@Override
public void onConfigValueSet()
{
String ignoredDimensionCsvString = Config.Client.Advanced.Graphics.Experimental.ignoredDimensionCsv.get();
if (ignoredDimensionCsvString == null
|| ignoredDimensionCsvString.isEmpty())
{
LOGGER.info("Dimension ignoring disabled, DH will render all dimensions.");
this.dimensionNames = null;
}
else
{
try
{
this.dimensionNames = ignoredDimensionCsvString.split(",");
LOGGER.info("DH set to ignore dimensions: ["+ StringUtil.join(", ", this.dimensionNames)+"].");
}
catch (Exception e)
{
LOGGER.error("Failed to separate ignored dimensions from CSV string, error: ["+e.getMessage()+"].", e);
this.dimensionNames = null;
}
}
}
//===================//
// external handling //
//===================//
@Override
public void beforeRender(DhApiCancelableEventParam<DhApiRenderParam> event)
{
String dimName = event.value.clientLevelWrapper.getDimensionName();
if (IgnoredDimensionCsvHandler.INSTANCE.dimensionNameShouldBeIgnored(dimName))
{
event.cancelEvent();
Config.Client.Advanced.Graphics.Fog.enableVanillaFog.setApiValue(true);
Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.setApiValue(EDhApiMcRenderingFadeMode.NONE);
}
else
{
Config.Client.Advanced.Graphics.Fog.enableVanillaFog.setApiValue(null);
Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.setApiValue(null);
}
}
public boolean dimensionNameShouldBeIgnored(String dimName)
{
if (this.dimensionNames == null
|| this.dimensionNames.length == 0)
{
return false;
}
for (int i = 0; i < this.dimensionNames.length; i++)
{
String dimNameToIgnore = this.dimensionNames[i];
if (dimName.equalsIgnoreCase(dimNameToIgnore))
{
return true;
}
}
return false;
}
}
@@ -0,0 +1,69 @@
/*
* 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;
public class RenderBlockCacheCsvHandler implements IConfigListener
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static RenderBlockCacheCsvHandler INSTANCE = new RenderBlockCacheCsvHandler();
//=============//
// constructor //
//=============//
/** private since we only ever need one handler at a time */
private RenderBlockCacheCsvHandler() { }
//=================//
// config handling //
//=================//
@Override
public void onConfigValueSet()
{
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
if (wrapperFactory != null)
{
wrapperFactory.resetCachedIgnoredBlocksSets();
DhApi.Delayed.renderProxy.clearRenderDataCache();
}
}
}
@@ -20,23 +20,22 @@
package com.seibel.distanthorizons.core.config.eventHandlers;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.ConfigHandler;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
/**
* handles enabling/disabling config validation when the
* {@link Config.Client.Advanced.Debugging#allowUnsafeValues} option
* is changed.
*/
public class UnsafeValuesConfigListener implements IConfigListener
{
public static UnsafeValuesConfigListener INSTANCE = new UnsafeValuesConfigListener();
@Override
public void onConfigValueSet()
{
Config.Client.Advanced.Debugging.allowUnsafeValues.configBase.disableMinMax =
Config.Client.Advanced.Debugging.allowUnsafeValues.get();
}
{ ConfigHandler.INSTANCE.runMinMaxValidation = !Config.Client.Advanced.Debugging.allowUnsafeValues.get(); }
@Override
public void onUiModify()
{
}
}
@@ -35,7 +35,7 @@ public class WorldCurvatureConfigEventHandler implements IConfigListener
{
public static WorldCurvatureConfigEventHandler INSTANCE = new WorldCurvatureConfigEventHandler();
private static final int MIN_VALID_CURVE_VALUE = 50;
public static final int MIN_VALID_CURVE_VALUE = 50;
/** private since we only ever need one handler at a time */
@@ -52,6 +52,11 @@ public class WorldCurvatureConfigEventHandler implements IConfigListener
// shouldn't update the UI, otherwise we may end up fighting the user
Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.set(MIN_VALID_CURVE_VALUE);
}
else if (curveRatio < 0 && curveRatio > -MIN_VALID_CURVE_VALUE)
{
// same as above, but in the negative direction
Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.set(-MIN_VALID_CURVE_VALUE);
}
}
@@ -19,30 +19,30 @@
package com.seibel.distanthorizons.core.config.eventHandlers.presets;
import com.seibel.distanthorizons.core.config.ConfigBase;
import com.seibel.distanthorizons.core.config.ConfigEntryWithPresetOptions;
import com.seibel.distanthorizons.core.config.ConfigHandler;
import com.seibel.distanthorizons.core.config.ConfigPresetOptions;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.config.types.AbstractConfigBase;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.config.IConfigGui;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConfigEntry;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<?>> implements IConfigListener
{
private static final Logger LOGGER = LogManager.getLogger();
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final long MS_DELAY_BEFORE_APPLYING_PRESET = 3_000;
@Nullable
private static IConfigGui configGui = SingletonInjector.INSTANCE.get(IConfigGui.class);
private static boolean guiListenersAdded = false;
private static final IConfigGui CONFIG_GUI = SingletonInjector.INSTANCE.get(IConfigGui.class);
protected final ArrayList<ConfigEntryWithPresetOptions<TPresetEnum, ?>> configList = new ArrayList<>();
protected final ArrayList<ConfigPresetOptions<TPresetEnum, ?>> configList = new ArrayList<>();
/** this timer is used so each preset isn't applied while a user is clicking through the config options */
protected Timer applyPresetTimer = null;
/** the enum to apply after the timer expires or the UI screen changes. */
@@ -59,9 +59,9 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
public AbstractPresetConfigEventHandler()
{
// don't update the UI when running on a server
if (configGui != null)
if (CONFIG_GUI != null)
{
configGui.addOnScreenChangeListener(this::onConfigUiClosed);
CONFIG_GUI.addOnScreenChangeListener(this::onConfigUiClosed);
}
}
@@ -90,7 +90,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
public void onConfigValueSet()
{
// don't try modifying the config before it's been loaded from file
if (!ConfigBase.INSTANCE.isLoaded)
if (!ConfigHandler.INSTANCE.isLoaded)
{
return;
}
@@ -140,11 +140,11 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
LOGGER.info("changing preset to: " + newPresetEnum);
LOGGER.debug("changing preset to: [" + newPresetEnum + "].");
this.changingPreset = true;
// update the controlled config values
for (ConfigEntryWithPresetOptions<TPresetEnum, ?> configEntry : this.configList)
for (ConfigPresetOptions<TPresetEnum, ?> configEntry : this.configList)
{
configEntry.updateConfigEntry(newPresetEnum);
}
@@ -152,7 +152,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
this.setUiOnlyConfigValues();
this.changingPreset = false;
LOGGER.info("preset active: " + newPresetEnum);
LOGGER.debug("preset active: [" + newPresetEnum + "].");
}
/**
@@ -200,7 +200,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
// remove any quick options that aren't possible with the currently selected options
for (ConfigEntryWithPresetOptions<TPresetEnum, ?> configEntry : this.configList)
for (ConfigPresetOptions<TPresetEnum, ?> configEntry : this.configList)
{
HashSet<TPresetEnum> optionPresetSet = configEntry.getPossibleQualitiesFromCurrentOptionValue();
possiblePresetSet.retainAll(optionPresetSet);
@@ -230,7 +230,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
// abstract methods //
//==================//
protected abstract IConfigEntry<TPresetEnum> getPresetConfigEntry();
protected abstract AbstractConfigBase<TPresetEnum> getPresetConfigEntry();
protected abstract List<TPresetEnum> getPresetEnumList();
protected abstract TPresetEnum getCustomPresetEnum();
@@ -1,63 +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.eventHandlers.presets;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorProgressDisplayLocation;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
public class QuickShowWorldGenProgressConfigEventHandler
{
public static QuickShowWorldGenProgressConfigEventHandler INSTANCE = new QuickShowWorldGenProgressConfigEventHandler();
private final ConfigChangeListener<Boolean> quickChangeListener;
private final ConfigChangeListener<EDhApiDistantGeneratorProgressDisplayLocation> fullChangeListener;
/** private since we only ever need one handler at a time */
private QuickShowWorldGenProgressConfigEventHandler()
{
this.quickChangeListener = new ConfigChangeListener<>(Config.Client.quickShowWorldGenProgress,
(val) ->
{
Config.Common.WorldGenerator.showGenerationProgress.set(Config.Client.quickShowWorldGenProgress.get()
? Config.Common.WorldGenerator.showGenerationProgress.getDefaultValue()
: EDhApiDistantGeneratorProgressDisplayLocation.DISABLED);
});
this.fullChangeListener = new ConfigChangeListener<>(Config.Common.WorldGenerator.showGenerationProgress,
(val) ->
{
Config.Client.quickShowWorldGenProgress.set(Config.Common.WorldGenerator.showGenerationProgress.get() != EDhApiDistantGeneratorProgressDisplayLocation.DISABLED);
});
}
/**
* Set the UI only config based on what is set in the file. <br>
* This should only be called once.
*/
public void setUiOnlyConfigValues()
{
boolean showProgress = Config.Common.WorldGenerator.showGenerationProgress.get() != EDhApiDistantGeneratorProgressDisplayLocation.DISABLED;
Config.Client.quickShowWorldGenProgress.set(showProgress);
}
}
@@ -26,11 +26,11 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiVerticalQuality;
import com.seibel.distanthorizons.api.enums.config.quickOptions.EDhApiQualityPreset;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiTransparency;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.ConfigEntryWithPresetOptions;
import com.seibel.distanthorizons.core.config.ConfigPresetOptions;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConfigEntry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.config.types.AbstractConfigBase;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.*;
@@ -39,10 +39,10 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
{
public static final RenderQualityPresetConfigEventHandler INSTANCE = new RenderQualityPresetConfigEventHandler();
private static final Logger LOGGER = LogManager.getLogger();
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiMaxHorizontalResolution> drawResolution = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.maxHorizontalResolution,
private final ConfigPresetOptions<EDhApiQualityPreset, EDhApiMaxHorizontalResolution> drawResolution = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.maxHorizontalResolution,
new HashMap<EDhApiQualityPreset, EDhApiMaxHorizontalResolution>()
{{
this.put(EDhApiQualityPreset.MINIMUM, EDhApiMaxHorizontalResolution.TWO_BLOCKS);
@@ -51,7 +51,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, EDhApiMaxHorizontalResolution.BLOCK);
this.put(EDhApiQualityPreset.EXTREME, EDhApiMaxHorizontalResolution.BLOCK);
}});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiVerticalQuality> verticalQuality = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.verticalQuality,
private final ConfigPresetOptions<EDhApiQualityPreset, EDhApiVerticalQuality> verticalQuality = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.verticalQuality,
new HashMap<EDhApiQualityPreset, EDhApiVerticalQuality>()
{{
this.put(EDhApiQualityPreset.MINIMUM, EDhApiVerticalQuality.HEIGHT_MAP);
@@ -60,7 +60,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, EDhApiVerticalQuality.HIGH);
this.put(EDhApiQualityPreset.EXTREME, EDhApiVerticalQuality.EXTREME);
}});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiHorizontalQuality> horizontalQuality = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.horizontalQuality,
private final ConfigPresetOptions<EDhApiQualityPreset, EDhApiHorizontalQuality> horizontalQuality = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.horizontalQuality,
new HashMap<EDhApiQualityPreset, EDhApiHorizontalQuality>()
{{
this.put(EDhApiQualityPreset.MINIMUM, EDhApiHorizontalQuality.LOWEST);
@@ -69,7 +69,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, EDhApiHorizontalQuality.HIGH);
this.put(EDhApiQualityPreset.EXTREME, EDhApiHorizontalQuality.EXTREME);
}});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiTransparency> transparency = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.transparency,
private final ConfigPresetOptions<EDhApiQualityPreset, EDhApiTransparency> transparency = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.transparency,
new HashMap<EDhApiQualityPreset, EDhApiTransparency>()
{{
this.put(EDhApiQualityPreset.MINIMUM, EDhApiTransparency.DISABLED);
@@ -78,7 +78,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, EDhApiTransparency.COMPLETE);
this.put(EDhApiQualityPreset.EXTREME, EDhApiTransparency.COMPLETE);
}});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, Boolean> ssaoEnabled = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Ssao.enableSsao,
private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> ssaoEnabled = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.enableSsao,
new HashMap<EDhApiQualityPreset, Boolean>()
{{
this.put(EDhApiQualityPreset.MINIMUM, false);
@@ -87,7 +87,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, true);
this.put(EDhApiQualityPreset.EXTREME, true);
}});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiMcRenderingFadeMode> vanillaFade = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.vanillaFadeMode,
private final ConfigPresetOptions<EDhApiQualityPreset, EDhApiMcRenderingFadeMode> vanillaFade = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.vanillaFadeMode,
new HashMap<EDhApiQualityPreset, EDhApiMcRenderingFadeMode>()
{{
this.put(EDhApiQualityPreset.MINIMUM, EDhApiMcRenderingFadeMode.NONE);
@@ -96,7 +96,16 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, EDhApiMcRenderingFadeMode.DOUBLE_PASS);
this.put(EDhApiQualityPreset.EXTREME, EDhApiMcRenderingFadeMode.DOUBLE_PASS);
}});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, Boolean> dhDither = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.ditherDhFade,
private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> dhFadeFarClipPlane = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.dhFadeFarClipPlane,
new HashMap<EDhApiQualityPreset, Boolean>()
{{
this.put(EDhApiQualityPreset.MINIMUM, false);
this.put(EDhApiQualityPreset.LOW, false);
this.put(EDhApiQualityPreset.MEDIUM, true);
this.put(EDhApiQualityPreset.HIGH, true);
this.put(EDhApiQualityPreset.EXTREME, true);
}});
private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> dhDither = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.ditherDhFade,
new HashMap<EDhApiQualityPreset, Boolean>()
{{
this.put(EDhApiQualityPreset.MINIMUM, false);
@@ -105,7 +114,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, true);
this.put(EDhApiQualityPreset.EXTREME, true);
}});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, Boolean> caveCulling = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Culling.enableCaveCulling,
private final ConfigPresetOptions<EDhApiQualityPreset, Boolean> caveCulling = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Culling.enableCaveCulling,
new HashMap<EDhApiQualityPreset, Boolean>()
{{
this.put(EDhApiQualityPreset.MINIMUM, true);
@@ -114,7 +123,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, false);
this.put(EDhApiQualityPreset.EXTREME, false);
}});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, Integer> biomeBlending = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.lodBiomeBlending,
private final ConfigPresetOptions<EDhApiQualityPreset, Integer> biomeBlending = new ConfigPresetOptions<>(Config.Client.Advanced.Graphics.Quality.lodBiomeBlending,
new HashMap<EDhApiQualityPreset, Integer>()
{{
this.put(EDhApiQualityPreset.MINIMUM, 0);
@@ -139,13 +148,14 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.configList.add(this.horizontalQuality);
this.configList.add(this.transparency);
this.configList.add(this.ssaoEnabled);
this.configList.add(this.dhFadeFarClipPlane);
this.configList.add(this.vanillaFade);
this.configList.add(this.dhDither);
this.configList.add(this.caveCulling);
this.configList.add(this.biomeBlending);
for (ConfigEntryWithPresetOptions<EDhApiQualityPreset, ?> config : this.configList)
for (ConfigPresetOptions<EDhApiQualityPreset, ?> config : this.configList)
{
// ignore try-using, the listener should only ever be added once and should never be removed
new ConfigChangeListener<>(config.configEntry, (val) -> { this.onConfigValueChanged(); });
@@ -159,7 +169,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
//==============//
@Override
protected IConfigEntry<EDhApiQualityPreset> getPresetConfigEntry() { return Config.Client.qualityPresetSetting; }
protected AbstractConfigBase<EDhApiQualityPreset> getPresetConfigEntry() { return Config.Client.qualityPresetSetting; }
@Override
protected List<EDhApiQualityPreset> getPresetEnumList() { return Arrays.asList(EDhApiQualityPreset.values()); }
@@ -22,11 +22,12 @@ package com.seibel.distanthorizons.core.config.eventHandlers.presets;
import com.seibel.distanthorizons.api.enums.config.quickOptions.EDhApiThreadPreset;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.ConfigEntryWithPresetOptions;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConfigEntry;
import com.seibel.distanthorizons.core.config.ConfigPresetOptions;
import com.seibel.distanthorizons.core.config.types.AbstractConfigBase;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.Arrays;
import java.util.HashMap;
@@ -37,11 +38,11 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
{
public static final ThreadPresetConfigEventHandler INSTANCE = new ThreadPresetConfigEventHandler();
private static final Logger LOGGER = LogManager.getLogger();
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static int getDefaultThreadCount() { return getThreadCountByPercent(0.5); }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> threadCount = new ConfigEntryWithPresetOptions<>(Config.Common.MultiThreading.numberOfThreads,
private final ConfigPresetOptions<EDhApiThreadPreset, Integer> threadCount = new ConfigPresetOptions<>(Config.Common.MultiThreading.numberOfThreads,
new HashMap<EDhApiThreadPreset, Integer>()
{{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, getThreadCountByPercent(0.1));
@@ -51,7 +52,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
}});
public static double getDefaultRunTimeRatio() { return 1.0; }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> threadRunTime = new ConfigEntryWithPresetOptions<>(Config.Common.MultiThreading.threadRunTimeRatio,
private final ConfigPresetOptions<EDhApiThreadPreset, Double> threadRunTime = new ConfigPresetOptions<>(Config.Common.MultiThreading.threadRunTimeRatio,
new HashMap<EDhApiThreadPreset, Double>()
{{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.5);
@@ -74,7 +75,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
this.configList.add(this.threadCount);
this.configList.add(this.threadRunTime);
for (ConfigEntryWithPresetOptions<EDhApiThreadPreset, ?> config : this.configList)
for (ConfigPresetOptions<EDhApiThreadPreset, ?> config : this.configList)
{
// ignore try-using, the listeners should only ever be added once and should never be removed
new ConfigChangeListener<>(config.configEntry, (val) -> { this.onConfigValueChanged(); });
@@ -119,7 +120,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
//==============//
@Override
protected IConfigEntry<EDhApiThreadPreset> getPresetConfigEntry() { return Config.Client.threadPresetSetting; }
protected AbstractConfigBase<EDhApiThreadPreset> getPresetConfigEntry() { return Config.Client.threadPresetSetting; }
@Override
protected List<EDhApiThreadPreset> getPresetEnumList() { return Arrays.asList(EDhApiThreadPreset.values()); }
@@ -20,17 +20,18 @@
package com.seibel.distanthorizons.core.config.file;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.seibel.distanthorizons.core.config.ConfigHandler;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.config.ConfigBase;
import com.seibel.distanthorizons.core.config.types.AbstractConfigType;
import com.seibel.distanthorizons.core.config.types.AbstractConfigBase;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.jar.EPlatform;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.logging.DhLogger;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -44,14 +45,11 @@ import java.util.concurrent.locks.ReentrantLock;
*/
public class ConfigFileHandler
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public final ConfigBase configBase;
public final Path configPath;
private final Logger logger;
/** This is the object for night-config */
private final CommentedFileConfig nightConfig;
@@ -64,10 +62,8 @@ public class ConfigFileHandler
// constructor //
//=============//
public ConfigFileHandler(ConfigBase configBase, Path configPath)
public ConfigFileHandler(Path configPath)
{
this.logger = LogManager.getLogger(this.getClass().getSimpleName() + ", " + ModInfo.ID);
this.configBase = configBase;
this.configPath = configPath;
this.nightConfig = CommentedFileConfig
@@ -104,7 +100,7 @@ public class ConfigFileHandler
this.loadNightConfig(nightConfig);
for (AbstractConfigType<?, ?> entry : this.configBase.entries)
for (AbstractConfigBase<?> entry : ConfigHandler.INSTANCE.configBaseList)
{
if (ConfigEntry.class.isAssignableFrom(entry.getClass()))
{
@@ -142,7 +138,7 @@ public class ConfigFileHandler
{
this.readWriteLock.lock();
int currentCfgVersion = this.configBase.configVersion;
int currentCfgVersion = ModInfo.CONFIG_FILE_VERSION;
try
{
// Dont load the real `this.nightConfig`, instead create a tempoary one
@@ -154,29 +150,29 @@ public class ConfigFileHandler
}
catch (Exception ignored) { }
if (currentCfgVersion == this.configBase.configVersion)
if (currentCfgVersion == ModInfo.CONFIG_FILE_VERSION)
{
// handle normally
}
else if (currentCfgVersion > this.configBase.configVersion)
else if (currentCfgVersion > ModInfo.CONFIG_FILE_VERSION)
{
this.logger.warn("Found config version [" + currentCfgVersion + "] which is newer than current mods config version of [" + this.configBase.configVersion + "]. You may have downgraded the mod and items may have been moved, you have been warned");
LOGGER.warn("Found config version [" + currentCfgVersion + "] which is newer than current mods config version of [" + ModInfo.CONFIG_FILE_VERSION + "]. You may have downgraded the mod and items may have been moved, you have been warned");
}
else // if (currentCfgVersion < configBase.configVersion)
{
this.logger.warn(ModInfo.NAME + " config is of an older version, currently there is no config updater... so resetting config");
LOGGER.warn(ModInfo.NAME + " config is of an older version, currently there is no config updater... so resetting config");
try
{
Files.delete(this.configPath);
}
catch (Exception e)
{
this.logger.error(e);
LOGGER.error("Unable to delete outdated config file at: ["+this.configPath+"], error: ["+e.getMessage()+"].", e);
}
}
this.loadFromFile(this.nightConfig);
this.nightConfig.set("_version", this.configBase.configVersion);
this.nightConfig.set("_version", ModInfo.CONFIG_FILE_VERSION);
}
finally
{
@@ -202,7 +198,7 @@ public class ConfigFileHandler
// Load all the entries
for (AbstractConfigType<?, ?> entry : this.configBase.entries)
for (AbstractConfigBase<?> entry : ConfigHandler.INSTANCE.configBaseList)
{
if (ConfigEntry.class.isAssignableFrom(entry.getClass())
&& entry.getAppearance().showInFile)
@@ -245,11 +241,11 @@ public class ConfigFileHandler
}
else if (entry.getTrueValue() == null)
{
// TODO when can this happen?
throw new IllegalArgumentException("Entry [" + entry.getNameWCategory() + "] is null, this may be a problem with [" + ModInfo.NAME + "]. Please contact the authors.");
// shouldn't happen, but just in case
throw new IllegalArgumentException("ConfigEntry [" + entry.getNameAndCategory() + "] is null, how did this happen?");
}
workConfig.set(entry.getNameWCategory(), ConfigTypeConverters.attemptToConvertToString(entry.getType(), entry.getTrueValue()));
workConfig.set(entry.getNameAndCategory(), ConfigTypeConverters.attemptToConvertToString(entry.getType(), entry.getTrueValue()));
}
/** Loads an entry when only given the entry */
@@ -259,9 +255,11 @@ public class ConfigFileHandler
public <T> void loadEntry(ConfigEntry<T> entry, CommentedFileConfig nightConfig)
{
if (!entry.getAppearance().showInFile)
{
return;
}
if (!nightConfig.contains(entry.getNameWCategory()))
if (!nightConfig.contains(entry.getNameAndCategory()))
{
this.saveEntry(entry, nightConfig);
return;
@@ -272,34 +270,33 @@ public class ConfigFileHandler
{
if (entry.getType().isEnum())
{
entry.pureSet((T) (nightConfig.getEnum(entry.getNameWCategory(), (Class<? extends Enum>) entry.getType())));
entry.setWithoutFiringEvents((T) (nightConfig.getEnum(entry.getNameAndCategory(), (Class<? extends Enum>) entry.getType())));
return;
}
// try converting the value if necessary
Class<?> expectedValueClass = entry.getType();
Object value = nightConfig.get(entry.getNameWCategory());
Object value = nightConfig.get(entry.getNameAndCategory());
Object convertedValue = ConfigTypeConverters.attemptToConvertFromString(expectedValueClass, value);
if (!convertedValue.getClass().equals(expectedValueClass))
{
this.logger.error("Unable to convert config value ["+value+"] from ["+(value != null ? value.getClass() : "NULL")+"] to ["+expectedValueClass+"] for config ["+entry.name+"], " +
LOGGER.error("Unable to convert config value ["+value+"] from ["+(value != null ? value.getClass() : "NULL")+"] to ["+expectedValueClass+"] for config ["+entry.name+"], " +
"the default config value will be used instead ["+entry.getDefaultValue()+"]. " +
"Make sure a converter is defined in ["+ConfigTypeConverters.class.getSimpleName()+"].");
convertedValue = entry.getDefaultValue();
}
entry.pureSet((T) convertedValue);
entry.setWithoutFiringEvents((T) convertedValue);
if (entry.getTrueValue() == null)
{
this.logger.warn("Entry [" + entry.getNameWCategory() + "] returned as null from the config. Using default value.");
entry.pureSet(entry.getDefaultValue());
LOGGER.warn("BlockBiomeWrapperPair [" + entry.getNameAndCategory() + "] returned as null from the config. Using default value.");
entry.setWithoutFiringEvents(entry.getDefaultValue());
}
}
catch (Exception e)
{
// e.printStackTrace();
this.logger.warn("Entry [" + entry.getNameWCategory() + "] had an invalid value when loading the config. Using default value.");
entry.pureSet(entry.getDefaultValue());
LOGGER.warn("BlockBiomeWrapperPair [" + entry.getNameAndCategory() + "] had an invalid value when loading the config. Using default value.");
entry.setWithoutFiringEvents(entry.getDefaultValue());
}
}
@@ -320,7 +317,7 @@ public class ConfigFileHandler
// the new line makes it easier to read and separate configs
// the space makes sure the first word of a comment isn't directly in line with the "#"
comment = "\n " + comment;
nightConfig.setComment(entry.getNameWCategory(), comment);
nightConfig.setComment(entry.getNameAndCategory(), comment);
}
@@ -352,18 +349,21 @@ public class ConfigFileHandler
}
catch (Exception e)
{
this.logger.warn("Loading file failed because of this expectation:\n" + e);
LOGGER.warn("Loading file failed because of this expectation:\n" + e);
reCreateFile(this.configPath);
nightConfig.load();
}
}
catch (Exception ex)
catch (Exception e)
{
System.out.println("Creating file failed");
this.logger.error(ex);
SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).crashMinecraft("Loading file and resetting config file failed at path [" + this.configPath + "]. Please check the file is ok and you have the permissions", ex);
LOGGER.error("File creation failed at ["+this.configPath+"], error: ["+e.getMessage()+"].", e);
// delayed MC getter since this object may be created before
// the singleton has been bound
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);
}
}
@@ -23,7 +23,7 @@ import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.core.io.ParsingMode;
import com.electronwill.nightconfig.json.JsonFormat;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.HashMap;
import java.util.Map;
@@ -36,7 +36,7 @@ import java.util.Map;
*/
public class ConfigTypeConverters
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
// Once you've made a converter add it to here where the first value is the type you want to convert and the 2nd value is the converter
public static final Map<Class<?>, ConverterBase> convertObjects = new HashMap<Class<?>, ConverterBase>()
@@ -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));
}
}
@@ -0,0 +1,10 @@
package com.seibel.distanthorizons.core.config.gui;
/**
* Points to a Common object that holds the GUI state.
* Having this interface allows for cleaner casting.
*/
public interface IConfigGuiInfo
{
}
@@ -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);
}
}
@@ -19,25 +19,27 @@
package com.seibel.distanthorizons.core.config.types;
import com.seibel.distanthorizons.core.config.ConfigBase;
import com.seibel.distanthorizons.core.config.gui.IConfigGuiInfo;
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance;
/**
* The class where all config options should extend
* The class all config options should extend
*
* @author coolGi
*/
// Note for devs: The "S" is the class that is extending this
public abstract class AbstractConfigType<T, S>
public abstract class AbstractConfigBase<T>
{
public String category = ""; // This should only be set once in the init
public String name; // This should only be set once in the init
protected final T defaultValue;
protected final boolean isFloatingPointNumber;
protected T value;
public ConfigBase configBase;
public Object guiValue; // This is a storage variable something like the gui can use
/**
* This stores information related to the GUI state.
* This is set during config UI setup.
*/
public IConfigGuiInfo guiValue;
protected EConfigEntryAppearance appearance;
@@ -47,14 +49,20 @@ public abstract class AbstractConfigType<T, S>
// constructor //
//=============//
protected AbstractConfigType(EConfigEntryAppearance appearance, T defaultValue)
protected AbstractConfigBase(EConfigEntryAppearance appearance, T defaultValue)
{
this.defaultValue = defaultValue;
if (this.defaultValue == null)
{
throw new IllegalArgumentException("defaultValue cannot be null");
}
this.value = defaultValue;
this.appearance = appearance;
Class<?> defaultValueClass = defaultValue.getClass();
this.isFloatingPointNumber = (defaultValueClass == Double.class || defaultValueClass == Float.class);
}
@@ -74,7 +82,7 @@ public abstract class AbstractConfigType<T, S>
public String getCategory() { return this.category; }
public String getName() { return this.name; }
public String getNameWCategory() { return (this.category.isEmpty() ? "" : this.category + ".") + this.name; }
public String getNameAndCategory() { return (this.category.isEmpty() ? "" : this.category + ".") + this.name; }
/** Gets the class of T */
@@ -27,10 +27,19 @@ import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance
*
* @author coolGi
*/
public class ConfigCategory extends AbstractConfigType<Class<?>, ConfigCategory>
public class ConfigCategory extends AbstractConfigBase<Class<?>>
{
/** This should not be set by anything other than the config system itself */
public String destination; // Where the category goes to
/**
* Defines where this category points to. <br>
* May be defined during config setup.
*/
public String destination;
//=============//
// constructor //
//=============//
private ConfigCategory(EConfigEntryAppearance appearance, Class<?> value, String destination)
{
@@ -38,20 +47,26 @@ public class ConfigCategory extends AbstractConfigType<Class<?>, ConfigCategory>
this.destination = destination;
}
public String getDestination()
{
return this.destination;
}
//==================//
// property getters //
//==================//
public String getDestination() { return this.destination; }
/** Use get() instead for category */
@Override
@Deprecated
public Class<?> getType()
{
return value;
}
public Class<?> getType() { return this.value; }
public static class Builder extends AbstractConfigType.Builder<Class<?>, Builder>
//=========//
// builder //
//=========//
public static class Builder extends AbstractConfigBase.Builder<Class<?>, Builder>
{
private String tmpDestination = null;
@@ -20,13 +20,13 @@
package com.seibel.distanthorizons.core.config.types;
import com.seibel.distanthorizons.core.config.NumberUtil;
import com.seibel.distanthorizons.core.config.ConfigHandler;
import com.seibel.distanthorizons.core.util.NumberUtil;
import com.seibel.distanthorizons.core.config.file.ConfigFileHandler;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance;
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryPerformance;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConfigEntry;
import com.seibel.distanthorizons.core.config.types.enums.EConfigValidity;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
@@ -34,40 +34,39 @@ import java.util.Arrays;
import java.util.function.Consumer;
/**
* Use for making the config variables
* for types that are not supported by it look in ConfigBase
* This config type allows for entering text, number, or enum values.
*
* @author coolGi
* @version 2023-7-16
*/
public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implements IConfigEntry<T>
public class ConfigEntry<T> extends AbstractConfigBase<T>
{
private String comment;
private final String comment;
private T min;
private T max;
private final ArrayList<IConfigListener> listenerList;
private final String chatCommandName;
private final EConfigEntryPerformance performance;
// API control //
/**
* If true this config can be controlled by the API <br>
* and any get() method calls will return the apiValue if it is set.
*/
public final boolean allowApiOverride;
private final boolean allowApiOverride;
/** Will be null if un-set */
@Nullable
private T apiValue;
/** Creates the entry */
//=============//
// constructor //
//=============//
//region
private ConfigEntry(
EConfigEntryAppearance appearance,
T value, String comment, T min, T max,
String chatCommandName, boolean allowApiOverride,
EConfigEntryPerformance performance,
String comment, String chatCommandName,
T value, T min, T max,
boolean allowApiOverride,
ArrayList<IConfigListener> listenerList)
{
super(appearance, value);
@@ -77,44 +76,74 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
this.max = max;
this.chatCommandName = chatCommandName;
this.allowApiOverride = allowApiOverride;
this.performance = performance;
this.listenerList = listenerList;
}
//endregion
/** Gets the default value of the option */
@Override
public T getDefaultValue() { return super.defaultValue; }
@Override
//==========================//
// property getters/setters //
//==========================//
//region
/** the string used when entering the config into the command line or chat */
public String getChatCommandName() { return this.chatCommandName; }
public String getComment() { return this.comment; }
/**
* If true this config can be controlled by the API <br>
* and any get() method calls will return the apiValue if it is set.
*/
public boolean getAllowApiOverride() { return this.allowApiOverride; }
public T getMin() { return this.min; }
public void setMin(T newMin) { this.min = newMin; }
public T getMax() { return this.max; }
public void setMax(T newMax) { this.max = newMax; }
//endregion
//===============//
// value setters //
//===============//
//region
public void setApiValue(T newApiValue)
{
this.apiValue = newApiValue;
this.listenerList.forEach(IConfigListener::onConfigValueSet);
synchronized (this.listenerList)
{
this.listenerList.forEach(IConfigListener::onConfigValueSet);
}
}
public boolean apiIsOverriding()
{
return this.allowApiOverride
&& this.apiValue != null;
}
@Override
public T getApiValue() { return this.apiValue; }
@Override
public boolean apiIsOverriding() { return this.allowApiOverride && this.apiValue != null; }
@Override
public boolean getAllowApiOverride() { return this.allowApiOverride; }
/**
* DONT USE THIS IN YOUR CODE <br>
* Sets the value without informing the rest of the code (ie, doesnt call listeners, or saves the value). <br>
* Should only be used when loading the config from the file (in places like the {@link ConfigFileHandler} or {@link com.seibel.distanthorizons.core.config.ConfigBase})
* 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).
* @see ConfigFileHandler
*/
public void pureSet(T newValue) {
super.set(newValue);
}
public void setWithoutFiringEvents(T newValue) { super.set(newValue); }
/** Sets the value without saving */
@Override
public void setWithoutSaving(T newValue)
{
super.set(newValue);
this.listenerList.forEach(IConfigListener::onConfigValueSet);
synchronized (this.listenerList)
{
this.listenerList.forEach(IConfigListener::onConfigValueSet);
}
}
@Override
public void set(T newValue)
@@ -126,83 +155,58 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
public void uiSetWithoutSaving(T newValue)
{
this.setWithoutSaving(newValue);
this.listenerList.forEach(IConfigListener::onUiModify);
synchronized (this.listenerList)
{
this.listenerList.forEach(IConfigListener::onUiModify);
}
}
public void uiSet(T newValue)
{
this.set(newValue);
this.listenerList.forEach(IConfigListener::onUiModify);
synchronized (this.listenerList)
{
this.listenerList.forEach(IConfigListener::onUiModify);
}
}
//endregion
//===============//
// value getters //
//===============//
//region
@Override
public T get()
{
if (this.allowApiOverride && this.apiValue != null)
if (this.allowApiOverride
&& this.apiValue != null)
{
return this.apiValue;
}
return super.get();
}
@Override
public T getTrueValue()
{
return super.get();
}
/** Ignores the API value if set. */
public T getTrueValue() { return super.get(); }
public T getDefaultValue() { return super.defaultValue; }
@Nullable
public T getApiValue() { return this.apiValue; }
//endregion
/** Gets the min value */
@Override
public T getMin() { return this.min; }
/** Sets the min value */
@Override
public void setMin(T newMin) { this.min = newMin; }
/** Gets the max value */
@Override
public T getMax() { return this.max; }
/** Sets the max value */
@Override
public void setMax(T newMax) { this.max = newMax; }
/** Sets the min and max within a single setter */
@Override
public void setMinMax(T newMin, T newMax)
{
this.setMin(newMin);
this.setMax(newMax);
}
/**
* Clamps the value within the set range
*
* @apiNote This does not save the value
*/
public void clampWithinRange() { this.clampWithinRange(this.min, this.max); }
/**
* Clamps the value within a set range
*
* @param min The minimum that the value can be
* @param max The maximum that the value can be
* @apiNote This does not save the value
*/
@SuppressWarnings("unchecked") // Suppress due to its always safe
public void clampWithinRange(T min, T max)
{
byte validness = this.isValid(min, max);
if (validness == -1) this.value = (T) NumberUtil.getMinimum(this.value.getClass());
if (validness == 1) this.value = (T) NumberUtil.getMaximum(this.value.getClass());
}
// TODO is this for command line use?
public String getChatCommandName() { return this.chatCommandName; }
@Override
public String getComment() { return this.comment; }
@Override
public void setComment(String newComment) { this.comment = newComment; }
/** Gets the performance impact of an option */
public EConfigEntryPerformance getPerformance() { return this.performance; }
//===========//
// listeners //
//===========//
//region
/** Fired whenever the config value changes to a new value. */
public void addValueChangeListener(Consumer<T> onValueChangeFunc)
@@ -211,132 +215,154 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
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). */
public void addListener(IConfigListener newListener) { this.listenerList.add(newListener); }
//public void removeValueChangeListener(Consumer<T> onValueChangeFunc) { } // not currently implemented
public void removeListener(IConfigListener oldListener) { this.listenerList.remove(oldListener); }
public void clearListeners() { this.listenerList.clear(); }
public ArrayList<IConfigListener> getListeners() { return this.listenerList; }
/** Replaces the listener list */
public void setListeners(ArrayList<IConfigListener> newListeners)
public void addListener(IConfigListener newListener)
{
this.listenerList.clear();
this.listenerList.addAll(newListeners);
}
public void setListeners(IConfigListener... newListeners) { this.listenerList.addAll(Arrays.asList(newListeners)); }
/**
* Checks if the option is valid
*
* @return 0 == valid
* <p> 2 == invalid
* <p> 1 == number too high
* <p> -1 == number too low
*/
@Override
public byte isValid() { return isValid(this.value, this.min, this.max); }
/**
* Checks if a new value is valid
*
* @param value Value that is being checked whether valid
* @return 0 == valid
* <p> 2 == invalid
* <p> 1 == number too high
* <p> -1 == number too low
*/
@Override
public byte isValid(T value) { return this.isValid(value, this.min, this.max); }
/**
* Checks if a new value is valid
*
* @param min The minimum that the value can be
* @param max The maximum that the value can be
* @return 0 == valid
* <p> 2 == invalid
* <p> 1 == number too high
* <p> -1 == number too low
*/
public byte isValid(T min, T max) { return this.isValid(this.value, min, max); }
/**
* Checks if a new value is valid
*
* @param value Value that is being checked whether valid
* @param min The minimum that the value can be
* @param max The maximum that the value can be
* @return 0 == valid
* <p> 2 == invalid
* <p> 1 == number too high
* <p> -1 == number too low
*/
public byte isValid(T value, T min, T max)
{
if (this.configBase.disableMinMax)
synchronized (this.listenerList)
{
return 0;
this.listenerList.add(newListener);
}
else if (min == null && max == null)
}
public void removeListener(IConfigListener oldListener)
{
synchronized (this.listenerList)
{
this.listenerList.remove(oldListener);
}
}
public void clearListeners()
{
synchronized (this.listenerList)
{
this.listenerList.clear();
}
}
//endregion
//====================//
// min/max validation //
//====================//
//region
/** Checks if this config's current value is valid */
public EConfigValidity getValidity() { return this.getValidity(this.value, this.min, this.max); }
/** Checks if the given value is valid */
public EConfigValidity getValidity(@Nullable T value) { return this.getValidity(value, this.min, this.max); }
/** Checks if the given value is valid */
public EConfigValidity getValidity(@Nullable T value, @Nullable T min, @Nullable T max)
{
if (!ConfigHandler.INSTANCE.runMinMaxValidation)
{
return EConfigValidity.VALID;
}
else if (min == null
&& max == null)
{
// no validation is needed for this field
return 0;
return EConfigValidity.VALID;
}
else if (value == null || this.value == null
else if (value == null
|| this.value == null
|| value.getClass() != this.value.getClass())
{
// If the 2 variables aren't the same type then it will be invalid
return 2;
// If the 2 variables aren't the same type
// or the input is missing
// then it will be invalid
return EConfigValidity.INVALID;
}
else if (Number.class.isAssignableFrom(value.getClass()))
else if (value instanceof Number)
{
// Only check min max if it is a number
if (max != null && NumberUtil.greaterThan((Number) value, (Number) max))
// Only check min/max if this config's type is a number
if (max != null
&& NumberUtil.greaterThan((Number) value, (Number) max))
{
return 1;
}
if (min != null && NumberUtil.lessThan((Number) value, (Number) min))
{
return -1;
return EConfigValidity.NUMBER_TOO_HIGH;
}
return 0;
if (min != null
&& NumberUtil.lessThan((Number) value, (Number) min))
{
return EConfigValidity.NUMBER_TOO_LOW;
}
return EConfigValidity.VALID;
}
else
{
return 0;
return EConfigValidity.VALID;
}
}
/** This should normally not be called since set() automatically calls this */
public void save() { configBase.configFileHandler.saveEntry(this); }
/** This should normally not be called except for special circumstances */
public void load() { configBase.configFileHandler.loadEntry(this); }
//endregion
//===============//
// file handling //
//===============//
//region
/** This should normally not be called since set() automatically calls this */
public void save() { ConfigHandler.INSTANCE.configFileHandler.saveEntry(this); }
/** This should normally not be called except for special circumstances */
public void load() { ConfigHandler.INSTANCE.configFileHandler.loadEntry(this); }
//endregion
//================//
// base overrides //
//================//
//region
@Override
public boolean equals(IConfigEntry<?> obj) { return obj.getClass() == ConfigEntry.class && equals((ConfigEntry<?>) obj); }
public String toString() { return this.name + ": [" + this.get() + "]"; }
public boolean equals(AbstractConfigBase<?> obj)
{
return obj.getClass() == ConfigEntry.class
&& this.equals((ConfigEntry<?>) obj);
}
/** Is the value of this equal to another */
public boolean equals(ConfigEntry<?> obj)
{
// Can all of this just be "return this.value.equals(obj.value)"?
if (Number.class.isAssignableFrom(this.value.getClass()))
{
return this.value == obj.value;
}
else
{
return this.value.equals(obj.value);
}
}
//endregion
public static class Builder<T> extends AbstractConfigType.Builder<T, Builder<T>>
//=========//
// builder //
//=========//
//region
public static class Builder<T> extends AbstractConfigBase.Builder<T, Builder<T>>
{
private String tmpComment = null;
private T tmpMin = null;
private T tmpMax = null;
protected String tmpChatCommandName = null;
private boolean tmpUseApiOverwrite = true;
private EConfigEntryPerformance tmpPerformance = EConfigEntryPerformance.DONT_SHOW;
protected ArrayList<IConfigListener> tmpIConfigListener = new ArrayList<>();
public Builder<T> comment(String newComment)
{
this.tmpComment = newComment;
@@ -382,12 +408,6 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
return this;
}
public Builder<T> setPerformance(EConfigEntryPerformance newPerformance)
{
this.tmpPerformance = newPerformance;
return this;
}
public Builder<T> replaceListeners(ArrayList<IConfigListener> newConfigListener)
@@ -416,15 +436,21 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
// build //
public ConfigEntry<T> build()
{
return new ConfigEntry<>(
this.tmpAppearance,
this.tmpValue, this.tmpComment, this.tmpMin, this.tmpMax,
this.tmpChatCommandName, this.tmpUseApiOverwrite,
this.tmpPerformance, this.tmpIConfigListener);
this.tmpComment, this.tmpChatCommandName, this.tmpValue, this.tmpMin, this.tmpMax,
this.tmpUseApiOverwrite,
this.tmpIConfigListener);
}
}
//endregion
}
@@ -21,29 +21,42 @@ package com.seibel.distanthorizons.core.config.types;
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance;
public class ConfigUIButton extends AbstractConfigType<Runnable, ConfigUIButton>
public class ConfigUIButton extends AbstractConfigBase<Runnable>
{
public ConfigUIButton(Runnable runnable)
{
super(EConfigEntryAppearance.ONLY_IN_GUI, runnable);
}
//=============//
// constructor //
//=============//
/** Runs the action of the button. NOTE: Will run on the main thread (so can halt the main process if not offloaded to a different thread) */
public ConfigUIButton(Runnable runnable)
{ super(EConfigEntryAppearance.ONLY_IN_GUI, runnable); }
//=========//
// actions //
//=========//
/**
* Runs the action of the button.
* NOTE: This will run on the render thread
* (so it can halt the main process if it takes too long and isn't offloaded to another thread)
*/
public void runAction() { this.value.run(); }
public static class Builder extends AbstractConfigType.Builder<Runnable, Builder>
//=========//
// builder //
//=========//
public static class Builder extends AbstractConfigBase.Builder<Runnable, Builder>
{
/** Appearance shouldn't be changed */
@Override
public Builder setAppearance(EConfigEntryAppearance newAppearance)
{
return this;
}
public Builder setAppearance(EConfigEntryAppearance newAppearance) { return this; }
public ConfigUIButton build()
{
return new ConfigUIButton(this.tmpValue);
}
{ return new ConfigUIButton(this.tmpValue); }
}
@@ -23,7 +23,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.types.enums.EConfigCommentTextPosition;
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.Logger;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -32,9 +32,9 @@ import org.jetbrains.annotations.Nullable;
*
* @author coolGi
*/
public class ConfigUIComment extends AbstractConfigType<String, ConfigUIComment>
public class ConfigUIComment extends AbstractConfigBase<String>
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public String parentConfigPath = null;
@@ -43,8 +43,11 @@ public class ConfigUIComment extends AbstractConfigType<String, ConfigUIComment>
public ConfigUIComment() { this(null, null); }
public ConfigUIComment(String parentConfigPath, EConfigCommentTextPosition textPosition)
//=============//
// constructor //
//=============//
public ConfigUIComment(String parentConfigPath, @Nullable EConfigCommentTextPosition textPosition)
{
super(EConfigEntryAppearance.ONLY_IN_GUI, "");
this.parentConfigPath = parentConfigPath;
@@ -53,6 +56,10 @@ public class ConfigUIComment extends AbstractConfigType<String, ConfigUIComment>
//=========//
// setters //
//=========//
/** Appearance shouldn't be changed */
@Override
public void setAppearance(EConfigEntryAppearance newAppearance) { }
@@ -63,9 +70,14 @@ public class ConfigUIComment extends AbstractConfigType<String, ConfigUIComment>
public static class Builder extends AbstractConfigType.Builder<String, Builder>
//=========//
// builder //
//=========//
public static class Builder extends AbstractConfigBase.Builder<String, Builder>
{
public String tempParentConfigPath = null;
@Nullable
public EConfigCommentTextPosition tempTextPosition = null;
@@ -155,9 +167,13 @@ public class ConfigUIComment extends AbstractConfigType<String, ConfigUIComment>
// build //
public ConfigUIComment build()
{ return new ConfigUIComment(this.tempParentConfigPath, this.tempTextPosition); }
}
}
@@ -25,13 +25,21 @@ import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance
* Adds empty space the height of a button.
* Useful for separating different categories.
*/
public class ConfigUISpacer extends AbstractConfigType<String, ConfigUISpacer>
public class ConfigUISpacer extends AbstractConfigBase<String>
{
//=============//
// constructor //
//=============//
public ConfigUISpacer()
{ super(EConfigEntryAppearance.ONLY_IN_GUI, ""); }
//=========//
// setters //
//=========//
/** Appearance shouldn't be changed */
@Override
public void setAppearance(EConfigEntryAppearance newAppearance) { }
@@ -42,7 +50,11 @@ public class ConfigUISpacer extends AbstractConfigType<String, ConfigUISpacer>
public static class Builder extends AbstractConfigType.Builder<String, Builder>
//=========//
// builder //
//=========//
public static class Builder extends AbstractConfigBase.Builder<String, Builder>
{
/** Appearance shouldn't be changed */
@Override
@@ -23,16 +23,24 @@ import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance
/**
* Creates a UI element that copies everything from another element.
* This only effects the UI
* This element is only visible in the GUI.
*
* @author coolGi
*/
public class ConfigUiLinkedEntry extends AbstractConfigType<AbstractConfigType<?, ?>, ConfigUiLinkedEntry>
public class ConfigUiLinkedEntry extends AbstractConfigBase<AbstractConfigBase<?>>
{
public ConfigUiLinkedEntry(AbstractConfigType<?, ?> value)
{
super(EConfigEntryAppearance.ONLY_IN_GUI, value);
}
//=============//
// constructor //
//=============//
public ConfigUiLinkedEntry(AbstractConfigBase<?> value)
{ super(EConfigEntryAppearance.ONLY_IN_GUI, value); }
//=========//
// setters //
//=========//
/** Appearance shouldn't be changed */
@Override
@@ -40,10 +48,15 @@ public class ConfigUiLinkedEntry extends AbstractConfigType<AbstractConfigType<?
/** Value shouldn't be changed after creation */
@Override
public void set(AbstractConfigType<?, ?> newValue) { }
public void set(AbstractConfigBase<?> newValue) { }
public static class Builder extends AbstractConfigType.Builder<AbstractConfigType<?, ?>, Builder>
//=========//
// builder //
//=========//
public static class Builder extends AbstractConfigBase.Builder<AbstractConfigBase<?>, Builder>
{
/** Appearance shouldn't be changed */
@Override
@@ -59,4 +72,6 @@ public class ConfigUiLinkedEntry extends AbstractConfigType<AbstractConfigType<?
}
}
@@ -0,0 +1,15 @@
package com.seibel.distanthorizons.core.config.types.enums;
/**
* VALID
* INVALID
* NUMBER_TOO_HIGH
* NUMBER_TOO_LOW
*/
public enum EConfigValidity
{
VALID,
INVALID,
NUMBER_TOO_HIGH,
NUMBER_TOO_LOW,
}

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