Compare commits

...

225 Commits

Author SHA1 Message Date
James Seibel 894a2dbe7d Fix incorrect DhSectionPos method var order 2024-05-16 22:14:34 -05:00
James Seibel 172e7560fd Replace DhSectionPos with long primitives to reduce GC 2024-05-16 22:07:57 -05:00
James Seibel cd5ff8ce35 Start adding new DhSectionPos and unit tests 2024-05-15 18:48:25 -05:00
James Seibel 6e717a383d Remove unused OldSectionPos methods and add serialize methods to V1 repo 2024-05-15 18:47:51 -05:00
James Seibel bef873b875 Add comments to EDhDirection 2024-05-15 18:47:09 -05:00
James Seibel 331d75a3bc Rename DhSectionPos -> OldDhSectionPos before rewrite 2024-05-15 07:50:52 -04:00
James Seibel cec6438602 Replace QuadTree iterator linked list with ArrayDeque
Thanks JustALittleWolf!
2024-05-15 07:36:32 -04:00
James Seibel f5e0c112e3 Revert max world gen tasks 2 -> 20
I was hoping it would fix LODs not appearing, although it appears there was a different problem which has since been fixed.
2024-05-11 16:23:20 -05:00
James Seibel 899c4aca91 Add Quad Tree Render Status debug wireframe 2024-05-11 16:21:53 -05:00
James Seibel a4ac483e5b Potentially fix LODs not loading in 2024-05-11 16:19:10 -05:00
James Seibel 723f67ea0c Attempt to prevent thread starvation due to world gen
Hopefully this should help prevent issues on low end machines not loading in LODs
2024-05-10 22:27:22 -05:00
James Seibel 4575701bd4 disable sql timeout 2024-05-09 23:21:35 -05:00
James Seibel 7cfcfb0695 Handle missing/corrupted block/biome ID's in the full data 2024-05-09 19:45:54 -05:00
James Seibel f33bfa1d69 Fix monoliths due to duplicate IDs
This would specifically happen if moving from one MC version that has more blockstate attributes to one with fewer. If that was done then the both blockstates would act like they were the same, screwing up the ID map.
2024-05-09 19:44:33 -05:00
James Seibel a3c1f1563d minor FullDataToRenderDataTransformer reformat 2024-05-09 19:35:43 -05:00
James Seibel 361d197c5e Add DataCorruptedException(message, exception) constructor 2024-05-09 19:04:07 -05:00
James Seibel 04379691bc Potential fix for NaN multiverse similarity 2024-05-09 07:34:47 -05:00
James Seibel e1ca398b8f Fix updating chunk count not clearing on world close 2024-05-04 15:35:29 -05:00
James Seibel f34e67e6bb Fix F3 levels not closing with multiverse 2024-05-04 15:12:26 -05:00
James Seibel aad095ca1a Fix #670 Remove outdated world gen options from tooltip 2024-05-04 09:48:35 -05:00
James Seibel 950c951c2d minor ConfigBasedLogger cleanup 2024-05-02 17:31:53 -05:00
cola98765 37eaa2656a Update file FullDataToRenderDataTransformer.java 2024-05-01 10:31:43 +00:00
James Seibel 945853d014 Improve nightly build warning message 2024-04-30 21:53:00 -05:00
James Seibel 6bb38ad500 Improve migration queue messages 2024-04-30 21:52:13 -05:00
James Seibel 980086c533 Fix debug wireframes rendering on top of LODs 2024-04-30 21:23:54 -05:00
James Seibel c8f1154831 Remove ZStd compression option
Any ZStd data will be automatically deleted and re-generated
2024-04-30 21:17:26 -05:00
James Seibel 9196480e50 Remove references to FastUtil 8.5.13 2024-04-30 20:28:34 -05:00
James Seibel f0506d28e5 Fix incorrect refernce to fastutil in LzmaArrayCache 2024-04-30 20:26:34 -05:00
James Seibel 8ecd5dd9cb Fix optifine 1.16 support 2024-04-30 18:57:07 -05:00
James Seibel c83140a2d0 add IClientLevelWrapper.getPlainsBiomeWrapper() 2024-04-28 17:31:08 -05:00
James Seibel 3b600ce800 Add corrupt data read handling 2024-04-28 15:52:08 -05:00
James Seibel 7f874b4dc5 Revert a613540b 2024-04-27 12:56:08 -05:00
James Seibel 23e857a20d Fix some lib shading issues 2024-04-27 11:35:08 -05:00
James Seibel 2298ef0e0d up the version number 2.0.3 -> 2.0.4 2024-04-26 07:33:42 -05:00
James Seibel 7470455e50 Fix issues with compressors not appearing at runtime 2024-04-26 07:33:14 -05:00
James Seibel 614884c29e Merge Data_source_rewrite into main 2024-04-26 07:21:48 -05:00
James Seibel b41e54e6a6 shade in apache.logging for the standalone jar 2024-04-25 22:09:51 -05:00
James Seibel 2cc6c8d840 Change Initializer compressor test class 2024-04-25 21:52:44 -05:00
James Seibel a613540b6a Move most libraries from the main script to core 2024-04-25 21:51:48 -05:00
Cutiepie c89fcb094a Add core to gradle 2024-04-22 18:53:31 +10:00
James Seibel 10014d3729 Change default grass side rendering to fade_to_dirt 2024-04-20 19:00:33 -05:00
James Seibel 88ff9e7cde Drop high and extreme horizontal quality enums 2024-04-20 12:36:19 -05:00
James Seibel 459bc5fc0c Ops, forgot to remove this unused override in DataSourceProvider 2024-04-20 12:35:48 -05:00
James Seibel 3826c83d89 Remove "new" from AbstractDataSourceHandler's name 2024-04-20 11:41:02 -05:00
James Seibel 382917033a Fix a few warnings 2024-04-20 11:38:52 -05:00
James Seibel 37381896bc Fix warnings about updating empty maps 2024-04-20 11:38:31 -05:00
James Seibel 22f4757aae Fix rendering holes 2024-04-20 11:02:52 -05:00
James Seibel 57cd143009 Fix LodRenderSections not stopping load futures when closed 2024-04-20 08:49:44 -05:00
James Seibel 031742a951 speed up initial LOD loading 2024-04-18 21:06:25 -05:00
James Seibel a2e2559b26 Fix transparent blocks glowing (thanks IMS) 2024-04-18 07:47:42 -05:00
James Seibel 6e0071a046 Fix migration chat message not appearing 2024-04-13 17:30:39 -05:00
James Seibel 352a59838d Fix double sending migration messages 2024-04-13 17:02:31 -05:00
James Seibel 63371e8111 improve migration deletion logic and add F3 logging 2024-04-13 16:58:52 -05:00
James Seibel 383ab1121d Fix migration getter SQL 2024-04-13 16:58:33 -05:00
James Seibel c9a7527bcb Improve migration message 2024-04-13 16:52:09 -05:00
James Seibel 7d4d899226 log migration status in F3 menu and chat 2024-04-13 14:25:28 -05:00
James Seibel 169429fe48 improve client level load logging 2024-04-13 13:35:04 -05:00
James Seibel 94304eb055 speed up initial loading when DB migration is necessary 2024-04-13 12:12:50 -05:00
James Seibel 4a40e19b34 suppress unchecked warning in LodRenderSection 2024-04-13 09:45:49 -05:00
James Seibel 22422321ab Remove unhelpful data source pooling in LodRenderSection 2024-04-13 08:22:36 -05:00
James Seibel eafaf2b4cd Fix compiler error in FullDataSourceV2 and minor reformat 2024-04-13 07:42:32 -05:00
James Seibel 281c85c283 Merge branch 'Data_Source_Rewrite' of https://gitlab.com/jeseibel/distant-horizons-core into Data_Source_Rewrite 2024-04-13 07:40:17 -05:00
James Seibel 8cecdebc05 Disable cave culling for any detail level above 0 2024-04-13 07:40:15 -05:00
James Seibel cba55aa643 Improve LOD load order 2024-04-12 22:40:08 -05:00
James Seibel f7c5b5725f Fix LodRenderSection adjacent source indicies 2024-04-12 22:39:32 -05:00
James Seibel 07b495d48c Increase file handler thread pool default runtime ratio 2024-04-12 22:19:44 -05:00
James Seibel b5d938475a Optimize LodRenderSection loading and data caching 2024-04-12 22:19:28 -05:00
cola98765 f8b127a85d I may not be smart 2024-04-11 09:45:30 +00:00
cola98765 a216ba364d hopefully make old file conversion a bit faster. 2024-04-11 09:43:05 +00:00
James Seibel 6d0ec33316 fix minor line spacing 2024-04-10 18:51:14 -05:00
James Seibel 35681fe9a5 Fix Z-fighting at very high heights 2024-04-10 07:11:23 -05:00
James Seibel e560ddf3dc Fix sqlite database files not being unlocked 2024-04-09 21:29:12 -05:00
James Seibel 77fffb1a9b add a TODO to FullDataSourceProviderV2 2024-04-09 19:31:20 -05:00
James Seibel cf74d17e1b Fix world gen tasks being incorrectly removed from the queue at long distances 2024-04-09 19:27:57 -05:00
James Seibel a0e7bb94c6 Add a ApplyToParent DB index to improve file handling speed 2024-04-09 07:48:31 -05:00
James Seibel e10617ac19 increase update populator thread default run time ratio 2024-04-09 07:13:01 -05:00
James Seibel cd2b5fd668 Speed up world gen queue initial loading 2024-04-09 07:11:59 -05:00
James Seibel 336e8b99ec Reduce update thread pool default counts 2024-04-08 07:43:21 -05:00
James Seibel d8bac9df8c Remove deprecated DhApiScreenResizeEvent 2024-04-08 07:17:17 -05:00
James Seibel 1ff4a56e2d Improve grass side rendering and add a config 2024-04-07 21:42:31 -05:00
James Seibel 7bdafa28cc Fix debug detail render mode 2024-04-07 20:38:59 -05:00
James Seibel 03e11fbe3e Improve migration update timeout message and increase the time 2024-04-07 14:19:55 -05:00
James Seibel 74d193d294 Add a couple full data source null checks 2024-04-07 14:00:03 -05:00
James Seibel 46e3b98b62 Remove near fog option 2024-04-07 13:50:05 -05:00
James Seibel 2d323cef39 remove missed unlimited horizontal quality reference 2024-04-06 18:18:57 -05:00
James Seibel 8846ca5b5e Improve shutdown thread pool handling 2024-04-06 18:18:41 -05:00
James Seibel f310f1b316 Remove Unlimited horizontal quality setting
The setting was un-maintainable and would cause major issues for anything but the smallest render distances.
2024-04-06 18:03:54 -05:00
James Seibel 10d9282df7 Fix a couple FullDataPointUtilV2 references 2024-04-06 12:37:31 -05:00
James Seibel a751507f19 Have grass fade to dirt for walls 2024-04-06 12:37:18 -05:00
James Seibel b4b7738aa6 Fix API enums missing "DhApi" prefix and remove unused Enums/code 2024-04-06 10:06:03 -05:00
James Seibel 8fd37d72c7 Add Sqlite Write Ahead Log to Sqlite 2024-04-06 09:53:28 -05:00
James Seibel f09818e564 Update ELodShading names and descriptions 2024-04-06 08:42:29 -05:00
James Seibel bdd0fbe5ae Merge branch 'change_shading_lang' into Data_Source_Rewrite 2024-04-06 08:30:22 -05:00
James Seibel ddbad36d8a Remove FullDataPointUtilV2
Not sure what happened to make me think we needed to change the data point format, but luckily we don't have to deal with it any more and can just stick with the single file.
2024-04-06 08:17:09 -05:00
James Seibel ae829cbe3e Fix legacy migration conversion column order 2024-04-06 07:54:47 -05:00
James Seibel 5ef6cb2e0d Fix wrong data source Version ref in SubDim Matcher 2024-04-06 07:53:52 -05:00
James Seibel a1a45f50bf Change Sqlite Journaling to WAL to potentially improve concurrent performance 2024-04-04 07:50:02 -05:00
James Seibel 42675abef1 Comment out LodRenderSection neighbor collection 2024-04-04 07:09:17 -05:00
James Seibel 08f0f8ee17 Add a constant for LodRenderSection cache expiration 2024-04-04 07:08:51 -05:00
James Seibel 4e1155f8a7 Improve initial LOD loading speed 2024-04-03 22:07:25 -05:00
James Seibel 4bc7cf1e86 Fix LodRenderSections not disabling rendering 2024-04-03 21:26:05 -05:00
James Seibel 1491487328 Remove unneeded V1 -> V2 data migration validation 2024-04-03 07:28:59 -05:00
James Seibel 750d8b7bc3 Fix crash in 1.16 due to buffer upload happening before rendering is setup 2024-04-03 07:19:34 -05:00
James Seibel b59d505725 Add IP only server folder mode 2024-04-02 07:17:31 -05:00
James Seibel 2cce71cf28 Add non-magic max pooled data source number 2024-04-01 20:26:13 -05:00
James Seibel 964f5feb4b Remove unused data source properties 2024-04-01 20:21:56 -05:00
James Seibel 9835af0845 remove unused ColumnRenderSource.worldGenStep 2024-04-01 20:19:31 -05:00
James Seibel 81f921623e rename IDataSource getSectionPos -> getPos 2024-04-01 20:18:44 -05:00
James Seibel dc5968b0b5 pool columnRenderSources 2024-04-01 20:16:07 -05:00
James Seibel 43392ca0e4 Have LodRenderSection cancel loading on render distance change 2024-03-31 18:57:23 -05:00
James Seibel 88d6092153 Clean up LodRenderSection and fix massive memory use at long render distances 2024-03-31 18:42:46 -05:00
James Seibel 0ddf1dd640 Remove ColumnRenderSourceLoader 2024-03-30 22:07:08 -05:00
James Seibel ddd475d537 Remove AbstractRenderBuffer 2024-03-30 08:07:53 -05:00
James Seibel 543c3ffc54 Fix compiler bugs 2024-03-28 17:14:08 -05:00
cola98765 cf97c08aaf @James check if it doesn't explote; This should help with rendering, and fix the issue with stacking water with 2block resolution 2024-03-28 21:27:01 +00:00
cola98765 ea0a62b6a3 panic fix 2024-03-28 20:14:19 +00:00
cola98765 57f3d44326 Update file FullDataToRenderDataTransformer.java 2024-03-28 19:01:42 +00:00
James Seibel e7eb2ff9cc Remove the DhRenderData DB table 2024-03-28 07:47:07 -05:00
James Seibel 6a3261394f Add compression mode config documentation and missing LodDataBuilder lines 2024-03-28 07:42:53 -05:00
James Seibel d41af88494 Add ColumnWorldCompressionMode to the database 2024-03-28 07:36:02 -05:00
James Seibel e83864fd02 Add world compression config 2024-03-27 19:09:09 -05:00
James Seibel 5722a92dc1 Add broken lossy chunk compression 2024-03-27 15:54:24 -06:00
James Seibel 5249377199 Add data caching to Lzma data compression 2024-03-24 15:11:42 -05:00
James Seibel b3a20fb938 Use native bindings for LZ4 output compression to reduce GC load 2024-03-24 12:17:48 -05:00
James Seibel 74627fdf90 Use automatica Zstd buffer pooling 2024-03-24 12:00:51 -05:00
James Seibel bbe5ae9b7c Fix potential issues when java.awt.headless is true 2024-03-24 11:52:32 -05:00
James Seibel 63b6365128 Add swing headless setup to Initializer 2024-03-24 11:50:45 -05:00
James Seibel ed9cf526cd rename example unit tests to prevent naming issues 2024-03-24 11:38:30 -05:00
coolGi cb0c294df6 Removed test code being included in main repo code 2024-03-24 12:26:10 +10:30
James Seibel 661f286b77 Fix accidentally marking DTOs to update past the max parent detail level 2024-03-23 18:08:35 -05:00
James Seibel 8c5cd7f11f Remove render data file handling and related code 2024-03-23 17:55:59 -05:00
James Seibel b64df318ce Improve error handling in DebugRenderer 2024-03-23 17:18:05 -05:00
James Seibel a1c85d91fc Comment out trace logs
These logs aren't printed and will just increase GC pressure for strings
2024-03-23 16:18:21 -05:00
James Seibel ce95dfb3d1 Reduce some GC pressure in FullDataSourceV2.mergeInputTwoByTwoDataColumn 2024-03-23 16:14:34 -05:00
James Seibel cbfd4f9de3 Pool FullDataPointIdMap Entries and improve hash logic 2024-03-23 15:49:40 -05:00
James Seibel 5d50775932 re-implement data source pooling 2024-03-21 22:07:35 -05:00
James Seibel 18ad241025 Replace datasource v2 long[][] with LongArrayList[] and fix previous implementation 2024-03-21 21:53:15 -05:00
James Seibel 4b98882b41 Revert "Add FullDataSourceV2 pooling and replace long[][] arrays with LongArrayList[]"
This reverts commit f11e9a142f.
2024-03-21 20:12:47 -05:00
James Seibel 3ec9bfca1b comment out potenially unused world gen logic 2024-03-21 20:11:14 -05:00
James Seibel 6f7c46e086 Remove unnecessary async render source updates 2024-03-21 20:09:10 -05:00
James Seibel 7726335413 Fix FullDataSourceV2Repo.getColumnGenerationStepForPos ignoring compression 2024-03-21 19:59:12 -05:00
James Seibel f3d8a749fd Improve DhLodPos.getWidthAtDetail assertion 2024-03-21 18:12:37 -05:00
James Seibel f11e9a142f Add FullDataSourceV2 pooling and replace long[][] arrays with LongArrayList[] 2024-03-21 17:25:37 -05:00
James Seibel 72c601b2db change en_us "Horizontal Quality" -> "LOD Dropoff Distance" 2024-03-21 17:11:04 -05:00
James Seibel 4cdc6c9632 Compress the column gen step in the database 2024-03-20 07:25:38 -05:00
James Seibel 3b9962d7dc Fix transactionScript auto update variable flipped 2024-03-19 21:10:27 -05:00
James Seibel d926d11d3c Reduce string concatenations in assertions 2024-03-19 20:53:07 -05:00
James Seibel 9fb5182b78 Change the SQLite journal mode DELETE -> TRUNCATE 2024-03-19 20:07:21 -05:00
James Seibel a1950ebccc prevent world gen queue until half of the update tasks have finished 2024-03-19 07:28:14 -05:00
James Seibel 8f9caa5d9a Remove vacuum call in createFullDataSourceV2Tables SQL script 2024-03-19 07:27:57 -05:00
cola98765 c537084d07 potential fix to bright blocks when they reach build height limit 2024-03-19 12:27:05 +00:00
James Seibel 9f195231db up the version number 2.0.2 -> 2.0.3 2024-03-19 06:48:26 -05:00
James Seibel 805429722f Fix an issue where downsampling would sometimes corrupt the data souce 2024-03-18 21:14:35 -05:00
James Seibel 5b67f60e6f Fix black block lighting for super flat worlds 2024-03-18 20:05:12 -05:00
James Seibel 6557a1a7bd rename FullDataSourceV2.createFromCompleteDataSource 2024-03-18 20:01:39 -05:00
James Seibel a62ee8b3cf Merge !50 2024-03-17 19:09:01 -05:00
cola98765 7714569251 Merge branch 'blending_change' into 'main'
Update LodRenderer.java

See merge request jeseibel/distant-horizons-core!53
2024-03-17 23:47:26 +00:00
cola98765 8b9e48d4d3 Update LodRenderer.java 2024-03-17 23:47:25 +00:00
James Seibel 94e3426779 Remove ISourceProvider 2024-03-17 17:36:28 -05:00
James Seibel 6b13e9141c rename FullDataFileHandler -> FullDataSourceProvider and Remove IFullDataSourceProvider
IFullDataSourceProvider was removed so legacy providers don't have to implement methods they don't need
2024-03-17 17:32:41 -05:00
James Seibel bf00a23499 Rename IWorldGenerationQueue -> IFullDataSourceRetrievalQueue 2024-03-17 16:46:56 -05:00
James Seibel 569a4b16b4 Improve the nightly build warning text 2024-03-17 16:29:49 -05:00
James Seibel 8ec22189fd remove unneeded casting in RenderSourceFileHandler 2024-03-17 16:24:46 -05:00
James Seibel 9d539c4766 Update several todo comments 2024-03-17 16:15:06 -05:00
James Seibel fada27257c Write FullDataSourceV2 column length as a short instead of a int 2024-03-17 16:14:24 -05:00
James Seibel c55880bcb9 Fix empty data sources not updating and being regenerated 2024-03-16 22:43:22 -05:00
James Seibel cbadfab62a disable cave culling in the end 2024-03-16 22:43:07 -05:00
James Seibel c435b55576 Fix updating empty LODs (IE in the end) 2024-03-16 22:27:11 -05:00
James Seibel 69e3e2bca3 Add a config to render overlapping quad errors 2024-03-16 22:23:20 -05:00
James Seibel b20cbab012 Remove Seamless Overdraw Test
The result wasn't very good due to rendering issues with entities
2024-03-16 21:03:15 -05:00
James Seibel ce4c5db5df Fix incorrect FullDataSourceV2 downsampling for worldGenStep 2024-03-16 20:54:12 -05:00
James Seibel 6d2912e320 Add created and last modified unix time to FullDataSourceV2 2024-03-16 20:32:01 -05:00
James Seibel e14e122b6c Refactor FullDataSourceV2 variables 2024-03-16 20:19:24 -05:00
James Seibel dcd16cb84b rename NewFullDataSource -> FullDataSourceV2 and supporting objects 2024-03-16 19:53:01 -05:00
James Seibel 98183a4e75 Rename CompleteFullDataSource -> FullDataSourceV1 2024-03-16 19:40:36 -05:00
James Seibel 4ec1dea1ba merge CompleteFullDataSource and FullDataArrayAccessor 2024-03-16 19:34:36 -05:00
James Seibel 031bf754e8 Remove SingleColumnFullDataAccessor and IFullDataAccessor 2024-03-16 19:16:50 -05:00
James Seibel c081b6c57c Remove unusused AbstractDhRepo.get(TDTO) 2024-03-16 17:47:06 -05:00
James Seibel 6413e17e4b Add multiple compression options and unit tests 2024-03-16 17:25:15 -05:00
cola98765 01c1dbb146 Update en_us.json 2024-03-15 09:13:24 +00:00
cola98765 9e0046ba83 Update en_us.json 2024-03-15 09:07:24 +00:00
cola98765 150d929a45 Update EVerticalQuality.java 2024-03-15 08:58:26 +00:00
James Seibel d5074feda2 Add aggregate getters to NewFullDataSourceRepo for unit testing 2024-03-14 21:40:36 -05:00
James Seibel 4741e25349 Add NewFullDataSourceDTO.createUnitTestDataSource() 2024-03-14 21:35:52 -05:00
James Seibel c3f99835db Multithread full data migration 2024-03-12 22:00:36 -05:00
James Seibel 996621887c Prevent world gen while migration is running 2024-03-12 21:43:26 -05:00
James Seibel 6f931c66bf Add migration fail capture 2024-03-12 21:42:53 -05:00
James Seibel 024176f97c Fix migrated downsampling light looking weird 2024-03-12 21:15:15 -05:00
James Seibel 968bc9addc Fix incorrect parent updating lock logic 2024-03-12 21:05:45 -05:00
James Seibel e83f7bd62e Add FullDataPointUtilV1 for use with CompleteFullDataSource 2024-03-12 21:00:50 -05:00
James Seibel c5787d0ff2 Add Legacy data source migration 2024-03-12 20:24:57 -05:00
James Seibel 30076f1b60 Minor comments and refactoring 2024-03-12 07:21:56 -05:00
James Seibel 34fdae1c78 remove debug code in NewFullDataSource 2024-03-10 21:50:13 -05:00
James Seibel dd7d7733f0 Rename and remove debug wireframe configs 2024-03-10 21:47:27 -05:00
James Seibel fd1944eb26 Remove unnecessary legacy debug rendering 2024-03-10 21:46:52 -05:00
James Seibel 64fce77a0a Fix repo castring for AbstractNewDataSourceHandler 2024-03-10 21:38:41 -05:00
James Seibel d569ae4052 Deprecate EHorizontalQuality.UNLIMITED 2024-03-10 21:30:59 -05:00
James Seibel 5f8eceee8d Remove unused override in RenderSourceFileHandler 2024-03-10 21:16:03 -05:00
James Seibel b49988af9e Remove an unused method added by Iris 2024-03-10 21:15:45 -05:00
James Seibel 46ba2630f1 Fix LodQuadTree clear render cache not working at extreme distances 2024-03-10 21:15:37 -05:00
James Seibel 02a1445732 Fix render data not regenerating on config change 2024-03-10 21:14:59 -05:00
James Seibel 8bdc4fe779 Add missing changed for 28de6f93 2024-03-10 21:05:13 -05:00
James Seibel 28de6f93af Fix world gen for extreme render distances 2024-03-10 20:44:12 -05:00
James Seibel 20394068b2 Add additional F3 lines for file updating 2024-03-09 17:06:17 -06:00
James Seibel 599340c4e8 Reduce data source update locking and add some data update batching 2024-03-09 17:05:54 -06:00
James Seibel e6c985a189 Add hash code and equals to NewFullDataSource 2024-03-09 09:06:39 -06:00
James Seibel 244d960ec0 minor render data bugfix 2024-03-04 21:34:00 -06:00
James Seibel eea2155e20 Temp fix for 0020 auto update script 2024-03-04 21:25:02 -06:00
James Seibel 8f6ff8c943 Fix data sources not saving correctly 2024-03-04 20:56:48 -06:00
James Seibel 97554ccae6 Increase Vertical Quality settings to hopefully reduce issues with floating islands 2024-03-04 07:13:43 -06:00
James Seibel c866fbfbfd Add update propagator thread pool 2024-03-04 07:10:20 -06:00
James Seibel 0c5c4f3a74 Fix multithreaded save calls to AbstractDhRepo 2024-03-04 07:08:32 -06:00
James Seibel 0f04453134 Make down sampling average values instead of grabbing the value closest to -inf 2024-03-03 19:17:24 -06:00
James Seibel a734bb6a69 Increase AbstractNewDataSourceHandler update lock count 2x -> 4x
Trying to reduce lock collisions.
2024-03-03 19:08:23 -06:00
James Seibel dc687f70ae Add LodUtil light constants 2024-03-03 14:53:10 -06:00
James Seibel e15ffe10c9 Improve FullDataPointUtil parameter names 2024-03-02 21:36:54 -06:00
James Seibel 3d999a1749 Change FullDataPointUtil to get/set sky and block lighting separately
The binary format is identical the only difference is the getter/setter methods
2024-03-02 21:31:21 -06:00
James Seibel 47391028d8 Prevent re-saving unmodified full data 2024-03-02 16:21:32 -06:00
James Seibel b8e03a2144 Deprecate DhApiScreenResizeEvent, replace with DhApiColorDepthTextureCreatedEvent 2024-03-02 15:21:22 -06:00
James Seibel 2a39a4cdc4 Add several TODO comments and minor reformatting 2024-03-02 15:20:51 -06:00
James Seibel 89012711ce rename ThreadPools -> ThreadPoolUtil 2024-03-02 14:02:54 -06:00
James Seibel 9b93125936 Add first draft of the new full data system 2024-03-02 11:44:50 -06:00
James Seibel 113c0f227f Improve debugging for FullDataPointUtil.remap() 2024-03-02 08:05:54 -06:00
James Seibel 28e230a2db Fix DhApiScreenResizeEvent incorrect description 2024-02-25 08:14:25 -06:00
James Seibel bb5dd248d8 Add compound key repo support 2024-02-24 09:04:46 -06:00
213 changed files with 9726 additions and 8617 deletions
+3
View File
@@ -27,3 +27,6 @@ forge*changelog.txt
# Sqlite databases
*.sqlite
*.sqlite-journal
*.sqlite-shm
*.sqlite-wal
@@ -1,56 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.enums.config;
/**
* CONSTANT <br>
* FREQUENT <br>
* NORMAL <br>
* RARE <br> <br>
*
* Determines how fast the buffers should be regenerated
*
* @author Leonardo Amato
* @version 9-25-2021
*/
@Deprecated // not currently in use, if the config this enum represents is re-implemented, the deprecated flag can be removed
public enum EBufferRebuildTimes
{
CONSTANT(0, 0, 0, 1),
FREQUENT(1000, 500, 2500, 1),
NORMAL(2000, 1000, 5000, 4),
RARE(5000, 2000, 10000, 16);
public final int playerMoveTimeout;
public final int renderedChunkTimeout;
public final int chunkChangeTimeout;
public final int playerMoveDistance;
EBufferRebuildTimes(int playerMoveTimeout, int renderedChunkTimeout, int chunkChangeTimeout, int playerMoveDistance)
{
this.playerMoveTimeout = playerMoveTimeout;
this.renderedChunkTimeout = renderedChunkTimeout;
this.chunkChangeTimeout = chunkChangeTimeout;
this.playerMoveDistance = playerMoveDistance;
}
}
@@ -23,9 +23,10 @@ package com.seibel.distanthorizons.api.enums.config;
* NONE, <br>
* NON_COLLIDING, <br>
*
* @since API 1.0.0
* @since API 1.1.0
* @version 2024-4-6
*/
public enum EBlocksToAvoid
public enum EDhApiBlocksToAvoid
{
// Reminder:
// when adding items up the API minor version
@@ -36,6 +37,6 @@ public enum EBlocksToAvoid
public final boolean noCollision;
EBlocksToAvoid(boolean noCollision) { this.noCollision = noCollision; }
EDhApiBlocksToAvoid(boolean noCollision) { this.noCollision = noCollision; }
}
@@ -0,0 +1,102 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.enums.config;
/**
* UNCOMPRESSED <br>
* LZ4 <br>
* ZSTD <br>
* XZ <br><br>
*
* Note: speed and compression ratios are examples
* and should only be used for estimated comparisons.
*
* @version 2024-3-16
* @since API 1.1.0
*/
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>
*
* Read Speed: 1.64 MS / DTO <br>
* Write Speed: 12.44 MS / DTO <br>
* Compression ratio: 1.0 <br>
*/
@DisallowSelectingViaConfigGui
UNCOMPRESSED(0),
/**
* Extremely fast (often faster than uncompressed), but generally poor compression. <br><br>
*
* Read Speed: 1.85 MS / DTO <br>
* Write Speed: 9.46 MS / DTO <br>
* Compression ratio: 0.3638 <br>
*/
LZ4(1),
/*
* Decent speed and good compression. <br><br>
*
* Read Speed: 11.78 MS / DTO <br>
* Write Speed: 16.76 MS / DTO <br>
* Compression ratio: 0.2199 <br>
*/
//@Deprecated
//Z_STD(2),
/**
* Extremely slow, but very good compression. <br><br>
*
* Read Speed: 12.25 MS / DTO <br>
* Write Speed: 490.07 MS / DTO <br>
* Compression ratio: 0.1242 <br>
*/
LZMA2(3);
/** More stable than using the ordinal of the enum */
public final byte value;
EDhApiDataCompressionMode(int value) { this.value = (byte) value; }
/** @throws IllegalArgumentException if the value doesn't map to a value */
public static EDhApiDataCompressionMode getFromValue(byte value) throws IllegalArgumentException
{
EDhApiDataCompressionMode[] enumList = EDhApiDataCompressionMode.values();
for (int i = 0; i < enumList.length; i++)
{
if (enumList[i].value == value)
{
return enumList[i];
}
}
throw new IllegalArgumentException("No compression mode with the value ["+value+"]");
}
}
@@ -20,9 +20,10 @@
package com.seibel.distanthorizons.api.enums.config;
/**
* @since API 1.0.0
* @since API 1.1.0
* @version 2024-4-6
*/
public enum EGLErrorHandlingMode
public enum EDhApiGLErrorHandlingMode
{
IGNORE,
LOG,
@@ -20,9 +20,10 @@
package com.seibel.distanthorizons.api.enums.config;
/**
* @since API 1.0.0
* @since API 1.1.0
* @version 2024-4-6
*/
public enum EGlProfileMode
public enum EDhApiGlProfileMode
{
CORE,
COMPAT,
@@ -28,10 +28,10 @@ package com.seibel.distanthorizons.api.enums.config;
*
* @author Leetom
* @author James Seibel
* @version 2022-7-2
* @since API 1.0.0
* @version 2024-4-6
* @since API 1.1.0
*/
public enum EGpuUploadMethod
public enum EDhApiGpuUploadMethod
{
/** Picks the best option based on the GPU the user has. */
AUTO(false, false),
@@ -60,7 +60,7 @@ public enum EGpuUploadMethod
public final boolean useEarlyMapping;
public final boolean useBufferStorage;
EGpuUploadMethod(boolean useEarlyMapping, boolean useBufferStorage)
EDhApiGpuUploadMethod(boolean useEarlyMapping, boolean useBufferStorage)
{
this.useEarlyMapping = useEarlyMapping;
this.useBufferStorage = useBufferStorage;
@@ -20,21 +20,21 @@
package com.seibel.distanthorizons.api.enums.config;
/**
* DISTANT_HORIZONS, <br>
* MINECRAFT,
* AS_GRASS <br>
* FADE_TO_DIRT <br>
* AS_DIRT <br>
*
* @author Leetom
* @version 2023-6-7
* @since API 1.0.0
* @since API 1.1.0
* @version 2024-4-7
*/
public enum ELightGenerationMode
public enum EDhApiGrassSideRendering
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
DISTANT_HORIZONS,
MINECRAFT
AS_GRASS,
FADE_TO_DIRT,
AS_DIRT;
}
@@ -26,9 +26,10 @@ package com.seibel.distanthorizons.api.enums.config;
* HIGH <br>
* UNLIMITED <br>
*
* @since API 1.0.0
* @since API 1.1.0
* @version 2024-4-6
*/
public enum EHorizontalQuality
public enum EDhApiHorizontalQuality
{
// Reminder:
// when adding items up the API minor version
@@ -42,17 +43,16 @@ public enum EHorizontalQuality
LOWEST(2.0f, 4),
LOW(2.0f, 8),
MEDIUM(2.0f, 12),
HIGH(2.2f, 24),
EXTREME(2.4f, 64),
UNLIMITED(-1, -1);
HIGH(2.2f, 16),
EXTREME(2.4f, 32),
;
public final double quadraticBase;
public final int distanceUnitInBlocks;
EHorizontalQuality(double quadraticBase, int distanceUnitInBlocks)
EDhApiHorizontalQuality(double quadraticBase, int distanceUnitInBlocks)
{
this.quadraticBase = quadraticBase;
this.distanceUnitInBlocks = distanceUnitInBlocks;
@@ -20,27 +20,33 @@
package com.seibel.distanthorizons.api.enums.config;
/**
* MINECRAFT <br>
* OLD_LIGHTING <br>
* NONE <br>
* AUTO <br>
* ENABLED <br>
* DISABLED <br>
*
* @since API 1.0.0
* @since API 1.1.0
* @version 2024-4-6
*/
public enum ELodShading
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 */
MINECRAFT,
/**
* Simulates Minecraft's shading.
* Uses Minecraft's shading for LODs. <Br>
* This means if Minecraft's shading is disabled DH's shading will be as well.
*/
AUTO,
/**
* Simulates Minecraft's shading. <Br>
* This is most useful for shaders that disable Minecraft's shading
* but still require shading on LODs.
*/
OLD_LIGHTING,
ENABLED,
/** LODs will have no shading */
NONE;
DISABLED;
}
@@ -22,9 +22,10 @@ package com.seibel.distanthorizons.api.enums.config;
import org.apache.logging.log4j.Level;
/**
* @since API 1.0.0
* @since API 1.1.0
* @version 2024-4-6
*/
public enum ELoggerMode
public enum EDhApiLoggerMode
{
DISABLED(Level.OFF, Level.OFF),
LOG_ALL_TO_FILE(Level.ALL, Level.OFF),
@@ -40,11 +41,14 @@ public enum ELoggerMode
LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.WARN),
LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.ERROR),
;
public final Level levelForFile;
public final Level levelForChat;
ELoggerMode(Level levelForFile, Level levelForChat)
EDhApiLoggerMode(Level levelForFile, Level levelForChat)
{
this.levelForFile = levelForFile;
this.levelForChat = levelForChat;
}
}
@@ -33,10 +33,10 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
*
* @author James Seibel
* @author Leonardo Amato
* @version 2023-6-14
* @since API 1.0.0
* @since API 1.1.0
* @version 2024-4-6
*/
public enum EMaxHorizontalResolution
public enum EDhApiMaxHorizontalResolution
{
/** render 256 LODs for each chunk */
BLOCK(16, 0),
@@ -84,12 +84,12 @@ public enum EMaxHorizontalResolution
* 2nd dimension: An array of all LodDetails that are less than or <br>
* equal to that detailLevel
*/
private static EMaxHorizontalResolution[][] lowerDetailArrays;
private static EDhApiMaxHorizontalResolution[][] lowerDetailArrays;
EMaxHorizontalResolution(int newLengthCount, int newDetailLevel)
EDhApiMaxHorizontalResolution(int newLengthCount, int newDetailLevel)
{
this.detailLevel = (byte) newDetailLevel;
this.dataPointLengthCount = newLengthCount;
@@ -129,20 +129,20 @@ public enum EMaxHorizontalResolution
* Returns an array of all LodDetails that have a detail level
* that is less than or equal to the given LodDetail
*/
public static EMaxHorizontalResolution[] getSelfAndLowerDetails(EMaxHorizontalResolution detail)
public static EDhApiMaxHorizontalResolution[] getSelfAndLowerDetails(EDhApiMaxHorizontalResolution detail)
{
if (lowerDetailArrays == null)
{
// run first time setup
lowerDetailArrays = new EMaxHorizontalResolution[EMaxHorizontalResolution.values().length][];
lowerDetailArrays = new EDhApiMaxHorizontalResolution[EDhApiMaxHorizontalResolution.values().length][];
// go through each LodDetail
for (EMaxHorizontalResolution currentDetail : EMaxHorizontalResolution.values())
for (EDhApiMaxHorizontalResolution currentDetail : EDhApiMaxHorizontalResolution.values())
{
ArrayList<EMaxHorizontalResolution> lowerDetails = new ArrayList<>();
ArrayList<EDhApiMaxHorizontalResolution> lowerDetails = new ArrayList<>();
// find the details lower than currentDetail
for (EMaxHorizontalResolution compareDetail : EMaxHorizontalResolution.values())
for (EDhApiMaxHorizontalResolution compareDetail : EDhApiMaxHorizontalResolution.values())
{
if (currentDetail.detailLevel <= compareDetail.detailLevel)
{
@@ -154,7 +154,7 @@ public enum EMaxHorizontalResolution
Collections.sort(lowerDetails);
Collections.reverse(lowerDetails);
lowerDetailArrays[currentDetail.detailLevel] = lowerDetails.toArray(new EMaxHorizontalResolution[lowerDetails.size()]);
lowerDetailArrays[currentDetail.detailLevel] = lowerDetails.toArray(new EDhApiMaxHorizontalResolution[lowerDetails.size()]);
}
}
@@ -162,9 +162,9 @@ public enum EMaxHorizontalResolution
}
/** Returns what detail level should be used at a given distance and maxDistance. */
public static EMaxHorizontalResolution getDetailForDistance(EMaxHorizontalResolution maxDetailLevel, int distance, int maxDistance)
public static EDhApiMaxHorizontalResolution getDetailForDistance(EDhApiMaxHorizontalResolution maxDetailLevel, int distance, int maxDistance)
{
EMaxHorizontalResolution[] lowerDetails = getSelfAndLowerDetails(maxDetailLevel);
EDhApiMaxHorizontalResolution[] lowerDetails = getSelfAndLowerDetails(maxDetailLevel);
int distanceBetweenDetails = maxDistance / lowerDetails.length;
int index = MathUtil.clamp(0, distance / distanceBetweenDetails, lowerDetails.length - 1);
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.api.enums.config;
/**
* NAME_ONLY, <br>
* IP_ONLY, <br>
* NAME_IP, <br>
* NAME_IP_PORT, <br>
* NAME_IP_PORT_MC_VERSION, <br> <br>
@@ -29,10 +30,10 @@ package com.seibel.distanthorizons.api.enums.config;
* Determines how the multiplayer folders should be named.
*
* @author James Seibel
* @version 2022-7-1
* @since API 1.0.0
* @since API 1.1.0
* @version 2024-4-6
*/
public enum EServerFolderNameMode
public enum EDhApiServerFolderNameMode
{
// Reminder:
// when adding items up the API minor version
@@ -42,6 +43,9 @@ public enum EServerFolderNameMode
/** Only use the server name */
NAME_ONLY,
/** Only use the server IP */
IP_ONLY,
/**
* {SERVER_NAME} IP {IP} <br>
* Example: Minecraft Server IP 192.168.1.40
@@ -0,0 +1,14 @@
package com.seibel.distanthorizons.api.enums.config;
/**
* STABLE, <br>
* NIGHTLY, <br><br>
*
* @since API 1.1.0
* @version 2024-4-6
*/
public enum EDhApiUpdateBranch
{
STABLE,
NIGHTLY
}
@@ -28,10 +28,11 @@ package com.seibel.distanthorizons.api.enums.config;
* the vanilla Minecraft terrain.
*
* @author James Seibel
* @version 2022-6-30
* @since API 1.1.0
* @version 2024-4-6
*/
@Deprecated // not currently in use, if the config this enum represents is re-implemented, the deprecated flag can be removed
public enum EVanillaOverdraw
public enum EDhApiVanillaOverdraw
{
// Reminder:
// when adding items up the API minor version
@@ -29,23 +29,25 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
* EXTREME <br>
*
* @author Leonardo Amato
* @version 2023-2-5
* @since API 1.0.0
* @version 2024-4-6
* @since API 1.1.0
*/
public enum EVerticalQuality
public enum EDhApiVerticalQuality
{
HEIGHT_MAP(new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}),
LOW(new int[]{4, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1}),
MEDIUM(new int[]{6, 4, 3, 2, 2, 1, 1, 1, 1, 1, 1}),
HIGH(new int[]{8, 6, 4, 2, 2, 2, 2, 1, 1, 1, 1}),
EXTREME(new int[]{16, 8, 4, 2, 2, 2, 2, 1, 1, 1, 1});
HEIGHT_MAP( new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}),
LOW( new int[]{4, 3, 3, 2, 2, 1, 1, 1, 1, 1, 1}),
MEDIUM( new int[]{6, 4, 3, 3, 3, 3, 3, 2, 2, 2, 1}),
HIGH( new int[]{16, 8, 4, 3, 3, 3, 3, 3, 3, 3, 1}),
VERY_HIGH( new int[]{32, 16, 8, 4, 4, 3, 3, 3, 3, 3, 1}),
EXTREME( new int[]{64, 32, 8, 4, 4, 3, 3, 3, 3, 3, 1}),
PIXEL_ART( new int[]{512, 64, 16, 8, 4, 3, 3, 3, 3, 3, 1});
/** represents how many LODs can be rendered in a single vertical slice */
public final int[] maxVerticalData;
EVerticalQuality(int[] maxVerticalData) { this.maxVerticalData = maxVerticalData; }
EDhApiVerticalQuality(int[] maxVerticalData) { this.maxVerticalData = maxVerticalData; }
@@ -0,0 +1,70 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.enums.config;
/**
* MERGE_SAME_BLOCKS <br>
* VISUALLY_EQUAL <br><br>
*
* @version 2024-3-27
* @since API 1.1.0
*/
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.
*/
MERGE_SAME_BLOCKS(0),
/**
* Only visible block/biome changes are recorded in the database. <Br>
* Hidden blocks (IE ores) are ignored.
*/
VISUALLY_EQUAL(1);
/** More stable than using the ordinal of the enum */
public final byte value;
EDhApiWorldCompressionMode(int value) { this.value = (byte) value; }
public static EDhApiWorldCompressionMode getFromValue(byte value)
{
EDhApiWorldCompressionMode[] enumList = EDhApiWorldCompressionMode.values();
for (int i = 0; i < enumList.length; i++)
{
if (enumList[i].value == value)
{
return enumList[i];
}
}
throw new IllegalArgumentException("No lossy compression mode with the value ["+value+"]");
}
}
@@ -1,48 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.enums.config;
/**
* AUTO <br>
* Near_First <br>
* Far_First <br> <br>
*
* Determines which LODs should have priority when generating
* outside the normal view distance.
*
* @author Leonardo Amato
* @version 12-1-2021
*/
@Deprecated // not currently in use, if the config this enum represents is re-implemented, the deprecated flag can be removed
public enum EGenerationPriority
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** NEAR_FIRST when connected to servers and BALANCED when on single player */
AUTO,
NEAR_FIRST,
BALANCED,
FAR_FIRST
}
@@ -1,51 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.enums.config;
/**
* NONE <br>
* LIGHT <br>
* MEDIUM <br>
* HEAVY <br>
*
* CUSTOM <br>
*
* @since API 1.0.0
* @deprecated will be removed when DH updates to MC 1.21 <br>
* After removal a float value will be used to control overdraw instead.
*/
@Deprecated
public enum EOverdrawPrevention
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
NONE,
LIGHT,
MEDIUM,
HEAVY,
/**
* Should not be passed in. <br>
* Is returned if the overdraw value doesn't match any of the enums defined here.
*/
CUSTOM;
}
@@ -1,7 +0,0 @@
package com.seibel.distanthorizons.api.enums.config;
public enum EUpdateBranch
{
STABLE,
NIGHTLY
}
@@ -30,9 +30,10 @@ import com.seibel.distanthorizons.api.enums.config.DisallowSelectingViaConfigGui
* HIGH, <br>
* EXTREME, <br>
*
* @since API 1.0.0
* @since API 1.1.0
* @version 2024-4-6
*/
public enum EQualityPreset
public enum EDhApiQualityPreset
{
// Reminder:
// when adding items up the API minor version
@@ -29,9 +29,10 @@ import com.seibel.distanthorizons.api.enums.config.DisallowSelectingViaConfigGui
* BALANCED, <br>
* AGGRESSIVE, <br>
*
* @since API 1.0.0
* @since API 1.1.0
* @version 2024-4-6
*/
public enum EThreadPreset
public enum EDhApiThreadPreset
{
// Reminder:
// when adding items up the API minor version
@@ -44,8 +45,6 @@ public enum EThreadPreset
LOW_IMPACT,
BALANCED,
AGGRESSIVE,
// temporarily removed due to stability concerns
I_PAID_FOR_THE_WHOLE_CPU,
}
@@ -28,10 +28,10 @@ package com.seibel.distanthorizons.api.enums.rendering;
*
* @author Leetom
* @author James Seibel
* @version 2023-6-7
* @since API 1.0.0
* @version 2024-4-6
* @since API 1.1.0
*/
public enum EDebugRendering
public enum EDhApiDebugRendering
{
// Reminder:
// when adding items up the API minor version
@@ -44,10 +44,6 @@ public enum EDebugRendering
/** LOD colors are based on their detail */
SHOW_DETAIL,
@Deprecated
/** LOD colors are based on their gen mode. */
SHOW_GENMODE,
/** Block Materials are often used by Iris shaders to determine how LODs should be rendered */
SHOW_BLOCK_MATERIAL,
@@ -58,7 +54,7 @@ public enum EDebugRendering
SHOW_RENDER_SOURCE_FLAG;
public static EDebugRendering next(EDebugRendering type)
public static EDhApiDebugRendering next(EDhApiDebugRendering type)
{
switch (type)
{
@@ -75,7 +71,7 @@ public enum EDebugRendering
}
}
public static EDebugRendering previous(EDebugRendering type)
public static EDhApiDebugRendering previous(EDhApiDebugRendering type)
{
switch (type)
{
@@ -84,8 +80,6 @@ public enum EDebugRendering
case SHOW_RENDER_SOURCE_FLAG:
return SHOW_OVERLAPPING_QUADS;
case SHOW_OVERLAPPING_QUADS:
return SHOW_GENMODE;
case SHOW_GENMODE:
return SHOW_DETAIL;
default:
return OFF;
@@ -24,10 +24,10 @@ package com.seibel.distanthorizons.api.enums.rendering;
* USE_SKY_COLOR, <br>
*
* @author James Seibel
* @version 2022-6-9
* @since API 1.0.0
* @version 2024-4-6
* @since API 1.1.0
*/
public enum EFogColorMode
public enum EDhApiFogColorMode
{
// Reminder:
// when adding items: up the API minor version
@@ -44,4 +44,5 @@ public enum EFogColorMode
* https://www.curseforge.com/minecraft/mc-mods/clear-skies-forge-port
*/
USE_SKY_COLOR,
}
@@ -25,10 +25,10 @@ package com.seibel.distanthorizons.api.enums.rendering;
* FOG_DISABLED <br>
*
* @author James Seibel
* @since API 1.1.0
* @version 2022-6-2
* @since API 1.0.0
*/
public enum EFogDrawMode
public enum EDhApiFogDrawMode
{
// Reminder:
// when adding items up the API minor version
@@ -26,9 +26,9 @@ package com.seibel.distanthorizons.api.enums.rendering;
*
* @author Leetom
* @version 2022-6-30
* @since API 1.0.0
* @since API 1.1.0
*/
public enum EFogFalloff
public enum EDhApiFogFalloff
{
// Reminder:
// when adding items up the API minor version
@@ -38,5 +38,5 @@ public enum EFogFalloff
LINEAR,
EXPONENTIAL,
EXPONENTIAL_SQUARED,
// TEXTURE_BASED, // TODO: Impl this
}
@@ -32,10 +32,10 @@ package com.seibel.distanthorizons.api.enums.rendering;
* AVERAGE <br>
*
* @author Leetom
* @version 2022-4-14
* @since API 1.0.0
* @version 2024-4-6
* @since API 1.1.0
*/
public enum EHeightFogMixMode
public enum EDhApiHeightFogMixMode
{
BASIC,
IGNORE_HEIGHT,
@@ -28,10 +28,10 @@ package com.seibel.distanthorizons.api.enums.rendering;
* ABOVE_AND_BELOW_SET_HEIGHT, <br>
*
* @author Leetom
* @version 6-30-2022
* @since API 1.0.0
* @version 2024-4-6
* @since API 1.1.0
*/
public enum EHeightFogMode
public enum EDhApiHeightFogMode
{
// Reminder:
// when adding items up the API minor version
@@ -49,7 +49,7 @@ public enum EHeightFogMode
public final boolean above;
public final boolean below;
EHeightFogMode(boolean basedOnCamera, boolean above, boolean below)
EDhApiHeightFogMode(boolean basedOnCamera, boolean above, boolean below)
{
this.basedOnCamera = basedOnCamera;
this.above = above;
@@ -24,10 +24,10 @@ package com.seibel.distanthorizons.api.enums.rendering;
* Debug <br>
* Disabled <br>
*
* @version 2022-6-2
* @since API 1.0.0
* @since API 1.1.0
* @version 2024-4-6
*/
public enum ERendererMode
public enum EDhApiRendererMode
{
// Reminder:
// when adding items up the API minor version
@@ -40,7 +40,7 @@ public enum ERendererMode
/** Used by the config GUI to cycle through the available rendering options */
public static ERendererMode next(ERendererMode type)
public static EDhApiRendererMode next(EDhApiRendererMode type)
{
switch (type)
{
@@ -54,7 +54,7 @@ public enum ERendererMode
}
/** Used by the config GUI to cycle through the available rendering options */
public static ERendererMode previous(ERendererMode type)
public static EDhApiRendererMode previous(EDhApiRendererMode type)
{
switch (type)
{
@@ -24,9 +24,10 @@ package com.seibel.distanthorizons.api.enums.rendering;
* FAKE, <br>
* COMPLETE, <br>
*
* @since API 1.0.0
* @since API 1.1.0
* @version 2024-4-6
*/
public enum ETransparency
public enum EDhApiTransparency
{
// Reminder:
// when adding items up the API minor version
@@ -40,7 +41,7 @@ public enum ETransparency
public final boolean transparencyEnabled;
public final boolean fakeTransparencyEnabled;
ETransparency(boolean transparencyEnabled, boolean fakeTransparencyEnabled)
EDhApiTransparency(boolean transparencyEnabled, boolean fakeTransparencyEnabled)
{
this.transparencyEnabled = transparencyEnabled;
this.fakeTransparencyEnabled = fakeTransparencyEnabled;
@@ -1,40 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.enums.rendering;
/**
* NEAR, <br>
* FAR, <br>
* NEAR_AND_FAR <br>
*
* @author James Seibel
* @version 2022-6-2
* @since API 1.0.0
*/
public enum EFogDistance
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
NEAR,
FAR,
NEAR_AND_FAR
}
@@ -19,7 +19,6 @@
package com.seibel.distanthorizons.api.interfaces.config.both;
import com.seibel.distanthorizons.api.enums.config.ELightGenerationMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
@@ -19,9 +19,6 @@
package com.seibel.distanthorizons.api.interfaces.config.client;
import com.seibel.distanthorizons.api.enums.rendering.EFogColorMode;
import com.seibel.distanthorizons.api.enums.rendering.EFogDistance;
import com.seibel.distanthorizons.api.enums.rendering.EFogDrawMode;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
@@ -19,7 +19,7 @@
package com.seibel.distanthorizons.api.interfaces.config.client;
import com.seibel.distanthorizons.api.enums.rendering.EDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
@@ -33,7 +33,7 @@ import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
public interface IDhApiDebuggingConfig extends IDhApiConfigGroup
{
/** Can be used to debug the standard fake chunk rendering. */
IDhApiConfigValue<EDebugRendering> debugRendering();
IDhApiConfigValue<EDhApiDebugRendering> debugRendering();
/** If enabled debug keybindings can be used. */
IDhApiConfigValue<Boolean> debugKeybindings();
@@ -19,8 +19,7 @@
package com.seibel.distanthorizons.api.interfaces.config.client;
import com.seibel.distanthorizons.api.enums.rendering.EFogFalloff;
import com.seibel.distanthorizons.api.enums.rendering.*;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogFalloff;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
@@ -64,7 +63,7 @@ public interface IDhApiFarFogConfig extends IDhApiConfigGroup
IDhApiConfigValue<Double> farFogMaxThickness();
/** Defines how the fog changes in thickness. */
IDhApiConfigValue<EFogFalloff> farFogFalloff();
IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff();
/** Defines the fog density. */
IDhApiConfigValue<Double> farFogDensity();
@@ -19,10 +19,8 @@
package com.seibel.distanthorizons.api.interfaces.config.client;
import com.seibel.distanthorizons.api.enums.rendering.EFogColorMode;
import com.seibel.distanthorizons.api.enums.rendering.EFogDistance;
import com.seibel.distanthorizons.api.enums.rendering.EFogDrawMode;
import com.seibel.distanthorizons.api.enums.rendering.*;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogColorMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogDrawMode;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
@@ -58,14 +56,11 @@ public interface IDhApiFogConfig extends IDhApiConfigGroup
// basic fog settings //
//====================//
/** Defines at what distance fog is rendered on fake chunks. */
IDhApiConfigValue<EFogDistance> distance();
/** Should be used to enable/disable fog rendering. */
IDhApiConfigValue<EFogDrawMode> drawMode();
IDhApiConfigValue<EDhApiFogDrawMode> drawMode();
/** Can be used to enable support with mods that change vanilla MC's fog color. */
IDhApiConfigValue<EFogColorMode> color();
IDhApiConfigValue<EDhApiFogColorMode> color();
/**
* If enabled attempts to disable vanilla MC's fog on real chunks. <br>
@@ -19,7 +19,7 @@
package com.seibel.distanthorizons.api.interfaces.config.client;
import com.seibel.distanthorizons.api.enums.config.EGpuUploadMethod;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
@@ -34,7 +34,7 @@ public interface IDhApiGpuBuffersConfig extends IDhApiConfigGroup
{
/** Defines how geometry data is uploaded to the GPU. */
IDhApiConfigValue<EGpuUploadMethod> gpuUploadMethod();
IDhApiConfigValue<EDhApiGpuUploadMethod> gpuUploadMethod();
/**
* Defines how long we should wait after uploading one
@@ -20,9 +20,8 @@
package com.seibel.distanthorizons.api.interfaces.config.client;
import com.seibel.distanthorizons.api.enums.config.*;
import com.seibel.distanthorizons.api.enums.config.*;
import com.seibel.distanthorizons.api.enums.rendering.ERendererMode;
import com.seibel.distanthorizons.api.enums.rendering.ETransparency;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiTransparency;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
@@ -68,7 +67,7 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
*
* Changing this config also changes {@link IDhApiGraphicsConfig#renderingEnabled()}'s value.
*/
IDhApiConfigValue<ERendererMode> renderingMode();
IDhApiConfigValue<EDhApiRendererMode> renderingMode();
@@ -77,18 +76,18 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
//==================//
/** Defines how detailed fake chunks are in the horizontal direction */
IDhApiConfigValue<EMaxHorizontalResolution> maxHorizontalResolution();
IDhApiConfigValue<EDhApiMaxHorizontalResolution> maxHorizontalResolution();
/** Defines how detailed fake chunks are in the vertical direction */
IDhApiConfigValue<EVerticalQuality> verticalQuality();
IDhApiConfigValue<EDhApiVerticalQuality> verticalQuality();
/** Modifies the quadratic function fake chunks use for horizontal quality drop-off. */
IDhApiConfigValue<EHorizontalQuality> horizontalQuality();
IDhApiConfigValue<EDhApiHorizontalQuality> horizontalQuality();
IDhApiConfigValue<ETransparency> transparency();
IDhApiConfigValue<EDhApiTransparency> transparency();
/** Defines what blocks won't be rendered as LODs. */
IDhApiConfigValue<EBlocksToAvoid> blocksToAvoid();
IDhApiConfigValue<EDhApiBlocksToAvoid> blocksToAvoid();
/**
* Defines if the color of avoided blocks will color the block below them. <Br>
@@ -112,17 +111,6 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
// advanced graphic settings //
//===========================//
/**
* Sets the distance used by the near clip plane to reduce
* overdraw. <br>
* Disabling this reduces holes in the world due to the near clip plane
* being too close to the camera and the terrain not being covered by vanilla terrain.
*
* @deprecated Use {@link IDhApiGraphicsConfig#overdrawPreventionRadius()} instead.
*/
@Deprecated
IDhApiConfigValue<EOverdrawPrevention> overdrawPrevention();
/**
* Sets the radius used by the near clip shader to reduce
* overdraw. <br>
@@ -172,7 +160,7 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
*
* @since API 1.1.0
*/
IDhApiConfigValue<ELodShading> lodShading();
IDhApiConfigValue<EDhApiLodShading> lodShading();
/**
* Sets whether LODs outside the view frustum culling will
@@ -19,9 +19,9 @@
package com.seibel.distanthorizons.api.interfaces.config.client;
import com.seibel.distanthorizons.api.enums.rendering.EFogFalloff;
import com.seibel.distanthorizons.api.enums.rendering.EHeightFogMixMode;
import com.seibel.distanthorizons.api.enums.rendering.EHeightFogMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogFalloff;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMixMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMode;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
@@ -40,10 +40,10 @@ public interface IDhApiHeightFogConfig extends IDhApiConfigGroup
{
/** Defines how the height fog mixes. */
IDhApiConfigValue<EHeightFogMixMode> heightFogMixMode();
IDhApiConfigValue<EDhApiHeightFogMixMode> heightFogMixMode();
/** Defines how the height fog is drawn relative to the camera or world. */
IDhApiConfigValue<EHeightFogMode> heightFogMode();
IDhApiConfigValue<EDhApiHeightFogMode> heightFogMode();
/**
* Defines the height fog's base height if {@link IDhApiHeightFogConfig#heightFogMode()}
@@ -64,7 +64,7 @@ public interface IDhApiHeightFogConfig extends IDhApiConfigGroup
IDhApiConfigValue<Double> heightFogMaxThickness();
/** Defines how the height fog changes in thickness. */
IDhApiConfigValue<EFogFalloff> heightFogFalloff();
IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff();
/** Defines the height fog's density. */
IDhApiConfigValue<Double> heightFogDensity();
@@ -20,7 +20,7 @@
package com.seibel.distanthorizons.api.interfaces.config.client;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.enums.config.EServerFolderNameMode;
import com.seibel.distanthorizons.api.enums.config.EDhApiServerFolderNameMode;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
/**
@@ -37,7 +37,7 @@ public interface IDhApiMultiplayerConfig extends IDhApiConfigGroup
* Defines how multiplayer server folders are named. <br>
* Note: Changing this while connected to a multiplayer world will cause undefined behavior!
*/
IDhApiConfigValue<EServerFolderNameMode> folderSavingMode();
IDhApiConfigValue<EDhApiServerFolderNameMode> folderSavingMode();
/**
* Defines the necessary similarity (as a percent) that two potential levels
@@ -23,16 +23,16 @@ import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
/**
* Called before Distant Horizons starts rendering a buffer. <br>
* This event cannot be cancelled, use {@link DhApiBeforeRenderEvent} if you want to cancel rendering.
* Called whenever Distant Horizons (re)creates
* the color and depth textures it renders to. <br>
*
* @author James Seibel
* @version 2023-1-23
* @version 2024-3-2
* @since API 1.1.0
*/
public abstract class DhApiScreenResizeEvent implements IDhApiEvent<DhApiScreenResizeEvent.EventParam>
public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiColorDepthTextureCreatedEvent.EventParam>
{
/** Fired immediately before Distant Horizons renders any transparent buffers. */
/** Fired before Distant Horizons creates. */
public abstract void onResize(DhApiEventParam<EventParam> event);
@@ -40,7 +40,8 @@ public class DhApiTerrainDataPoint
*/
public final byte detailLevel;
public final int lightLevel;
public final int blockLightLevel;
public final int skyLightLevel;
public final int topYBlockPos;
public final int bottomYBlockPos;
@@ -49,11 +50,12 @@ public class DhApiTerrainDataPoint
public DhApiTerrainDataPoint(byte detailLevel, int lightLevel, int topYBlockPos, int bottomYBlockPos, IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper)
public DhApiTerrainDataPoint(byte detailLevel, int blockLightLevel, int skyLightLevel, int topYBlockPos, int bottomYBlockPos, IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper)
{
this.detailLevel = detailLevel;
this.lightLevel = lightLevel;
this.blockLightLevel = blockLightLevel;
this.skyLightLevel = skyLightLevel;
this.topYBlockPos = topYBlockPos;
this.bottomYBlockPos = bottomYBlockPos;
@@ -34,7 +34,7 @@ public final class ModInfo
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.0.2-a-dev";
public static final String VERSION = "2.0.4-a-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */
public static boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
@@ -19,7 +19,7 @@
package com.seibel.distanthorizons.coreapi.util.converters;
import com.seibel.distanthorizons.api.enums.rendering.ERendererMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConverter;
/**
@@ -28,17 +28,17 @@ import com.seibel.distanthorizons.coreapi.interfaces.config.IConverter;
* @author James Seibel
* @version 2022-6-30
*/
public class RenderModeEnabledConverter implements IConverter<ERendererMode, Boolean>
public class RenderModeEnabledConverter implements IConverter<EDhApiRendererMode, Boolean>
{
@Override public ERendererMode convertToCoreType(Boolean renderingEnabled)
@Override public EDhApiRendererMode convertToCoreType(Boolean renderingEnabled)
{
return renderingEnabled ? ERendererMode.DEFAULT : ERendererMode.DISABLED;
return renderingEnabled ? EDhApiRendererMode.DEFAULT : EDhApiRendererMode.DISABLED;
}
@Override public Boolean convertToApiType(ERendererMode renderingMode)
@Override public Boolean convertToApiType(EDhApiRendererMode renderingMode)
{
return renderingMode == ERendererMode.DEFAULT;
return renderingMode == EDhApiRendererMode.DEFAULT;
}
}
@@ -28,7 +28,7 @@ import org.junit.Test;
* @author James Seibel
* @version 2022-9-5
*/
public class ExampleTest
public class ExampleApiTest
{
@Test
+63
View File
@@ -0,0 +1,63 @@
plugins {
id "java"
id "com.github.johnrengelman.shadow" version '7.1.2' apply false // Set this to true if you're using the standalone Core project
}
apply plugin: "application"
application {
mainClass.set("com.seibel.distanthorizons.core.jar.JarMain")
}
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
}
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
// 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
// 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"
// 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
// 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")
}
artifacts {
shadowedArtifact shadowJar // Setup the configuration shadowedArtifact to be the shadowJar
}
shadowJar {
def librariesLocation = "distanthorizons.libraries"
// relocate "it.unimi.dsi.fastutil", "${librariesLocation}.unimi.dsi.fastutil"
mergeServiceFiles()
}
@@ -26,17 +26,14 @@ 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.core.dataObjects.fullData.loader.CompleteFullDataSourceLoader;
import com.seibel.distanthorizons.core.dataObjects.fullData.loader.HighDetailIncompleteFullDataSourceLoader;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.core.dataObjects.fullData.loader.LowDetailIncompleteFullDataSourceLoader;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
//import io.netty.buffer.ByteBuf;
import net.jpountz.lz4.LZ4Compressor;
import net.jpountz.lz4.LZ4FrameOutputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.tukaani.xz.XZOutputStream;
import java.io.InputStream;
import java.awt.*;
/** Handles first time Core setup. */
public class Initializer
@@ -50,13 +47,17 @@ public class Initializer
{
// if any library isn't present in the jar its class
// will throw an error (not an exception)
Class<?> compressor = LZ4Compressor.class;
Class<?> fastCompressor = LZ4FrameOutputStream.class;
Class<?> smallCompressor = XZOutputStream.class;
//Class<?> networking = ByteBuf.class;
Class<?> toml = com.electronwill.nightconfig.core.Config.class;
Class<?> config = com.electronwill.nightconfig.core.Config.class;
Class<?> oldFastUtil = it.unimi.dsi.fastutil.longs.LongArrayList.class; // available in 8.2.1
//Class<?> newFastUtil = it.unimi.dsi.fastutil.ints.IntUnaryOperator.class; // available in 8.5.13
}
catch (NoClassDefFoundError e)
catch (Throwable e)
{
LOGGER.fatal("Critical programmer error: One or more libraries aren't present. Error: [" + e.getMessage() + "].");
// throwing here should crash the game, notifying the developer that something is wrong
throw e;
}
@@ -76,10 +77,16 @@ public class Initializer
}
CompleteFullDataSourceLoader unused2 = new CompleteFullDataSourceLoader(); // Auto register into the loader system
HighDetailIncompleteFullDataSourceLoader unused3 = new HighDetailIncompleteFullDataSourceLoader(); // Auto register
LowDetailIncompleteFullDataSourceLoader unused4 = new LowDetailIncompleteFullDataSourceLoader(); // Auto register
// attempt to setup Swing so we can display dialogs (popup windows)
System.setProperty("java.awt.headless", "false");
if (GraphicsEnvironment.isHeadless())
{
LOGGER.warn("Java.awt.headless is false. This means Distant Horizons can't display error and info dialog windows.");
}
else
{
LOGGER.info("Java.awt.headless set to true. Distant Horizons can correctly display error and info dialog windows.");
}
// link Core's config to the API
DhApi.Delayed.configs = DhApiConfig.INSTANCE;
@@ -23,7 +23,7 @@ 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.Config;
import com.seibel.distanthorizons.api.enums.rendering.EDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
public class DhApiDebuggingConfig implements IDhApiDebuggingConfig
{
@@ -33,8 +33,8 @@ public class DhApiDebuggingConfig implements IDhApiDebuggingConfig
public IDhApiConfigValue<EDebugRendering> debugRendering()
{ return new DhApiConfigValue<EDebugRendering, EDebugRendering>(Config.Client.Advanced.Debugging.debugRendering); }
public IDhApiConfigValue<EDhApiDebugRendering> debugRendering()
{ return new DhApiConfigValue<EDhApiDebugRendering, EDhApiDebugRendering>(Config.Client.Advanced.Debugging.debugRendering); }
public IDhApiConfigValue<Boolean> debugKeybindings()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.enableDebugKeybindings); }
@@ -19,7 +19,7 @@
package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.enums.rendering.EFogFalloff;
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;
@@ -50,8 +50,8 @@ public class DhApiFarFogConfig implements IDhApiFarFogConfig
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogMax); }
@Override
public IDhApiConfigValue<EFogFalloff> farFogFalloff()
{ return new DhApiConfigValue<EFogFalloff, EFogFalloff>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogFalloff); }
public IDhApiConfigValue<EDhApiFogFalloff> farFogFalloff()
{ return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.farFogFalloff); }
@Override
public IDhApiConfigValue<Double> farFogDensity()
@@ -19,9 +19,8 @@
package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.enums.rendering.EFogColorMode;
import com.seibel.distanthorizons.api.enums.rendering.EFogDistance;
import com.seibel.distanthorizons.api.enums.rendering.EFogDrawMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogColorMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogDrawMode;
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;
@@ -53,15 +52,11 @@ public class DhApiFogConfig implements IDhApiFogConfig
//====================//
@Override
public IDhApiConfigValue<EFogDistance> distance()
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.distance); }
@Override
public IDhApiConfigValue<EFogDrawMode> drawMode()
public IDhApiConfigValue<EDhApiFogDrawMode> drawMode()
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.drawMode); }
@Override
public IDhApiConfigValue<EFogColorMode> color()
public IDhApiConfigValue<EDhApiFogColorMode> color()
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.colorMode); }
@Override
@@ -23,7 +23,7 @@ import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiGpuBuffersConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.api.enums.config.EGpuUploadMethod;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
public class DhApiGpuBuffersConfig implements IDhApiGpuBuffersConfig
{
@@ -33,7 +33,7 @@ public class DhApiGpuBuffersConfig implements IDhApiGpuBuffersConfig
public IDhApiConfigValue<EGpuUploadMethod> gpuUploadMethod()
public IDhApiConfigValue<EDhApiGpuUploadMethod> gpuUploadMethod()
{ return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadMethod); }
public IDhApiConfigValue<Integer> gpuUploadPerMegabyteInMilliseconds()
@@ -20,14 +20,14 @@
package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.enums.config.*;
import com.seibel.distanthorizons.api.enums.rendering.ETransparency;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiTransparency;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiAmbientOcclusionConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiFogConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiGraphicsConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiNoiseTextureConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.api.enums.rendering.ERendererMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.config.Config;
public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
@@ -61,8 +61,8 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.quickEnableRendering); }
@Override
public IDhApiConfigValue<ERendererMode> renderingMode()
{ return new DhApiConfigValue<ERendererMode, ERendererMode>(Config.Client.Advanced.Debugging.rendererMode); }
public IDhApiConfigValue<EDhApiRendererMode> renderingMode()
{ return new DhApiConfigValue<EDhApiRendererMode, EDhApiRendererMode>(Config.Client.Advanced.Debugging.rendererMode); }
@@ -71,24 +71,24 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
//==================//
@Override
public IDhApiConfigValue<EMaxHorizontalResolution> maxHorizontalResolution()
{ return new DhApiConfigValue<EMaxHorizontalResolution, EMaxHorizontalResolution>(Config.Client.Advanced.Graphics.Quality.maxHorizontalResolution); }
public IDhApiConfigValue<EDhApiMaxHorizontalResolution> maxHorizontalResolution()
{ return new DhApiConfigValue<EDhApiMaxHorizontalResolution, EDhApiMaxHorizontalResolution>(Config.Client.Advanced.Graphics.Quality.maxHorizontalResolution); }
@Override
public IDhApiConfigValue<EVerticalQuality> verticalQuality()
{ return new DhApiConfigValue<EVerticalQuality, EVerticalQuality>(Config.Client.Advanced.Graphics.Quality.verticalQuality); }
public IDhApiConfigValue<EDhApiVerticalQuality> verticalQuality()
{ return new DhApiConfigValue<EDhApiVerticalQuality, EDhApiVerticalQuality>(Config.Client.Advanced.Graphics.Quality.verticalQuality); }
@Override
public IDhApiConfigValue<EHorizontalQuality> horizontalQuality()
{ return new DhApiConfigValue<EHorizontalQuality, EHorizontalQuality>(Config.Client.Advanced.Graphics.Quality.horizontalQuality); }
public IDhApiConfigValue<EDhApiHorizontalQuality> horizontalQuality()
{ return new DhApiConfigValue<EDhApiHorizontalQuality, EDhApiHorizontalQuality>(Config.Client.Advanced.Graphics.Quality.horizontalQuality); }
@Override
public IDhApiConfigValue<ETransparency> transparency()
{ return new DhApiConfigValue<ETransparency, ETransparency>(Config.Client.Advanced.Graphics.Quality.transparency); }
public IDhApiConfigValue<EDhApiTransparency> transparency()
{ return new DhApiConfigValue<EDhApiTransparency, EDhApiTransparency>(Config.Client.Advanced.Graphics.Quality.transparency); }
@Override
public IDhApiConfigValue<EBlocksToAvoid> blocksToAvoid()
{ return new DhApiConfigValue<EBlocksToAvoid, EBlocksToAvoid>(Config.Client.Advanced.Graphics.Quality.blocksToIgnore); }
public IDhApiConfigValue<EDhApiBlocksToAvoid> blocksToAvoid()
{ return new DhApiConfigValue<EDhApiBlocksToAvoid, EDhApiBlocksToAvoid>(Config.Client.Advanced.Graphics.Quality.blocksToIgnore); }
@Override
public IDhApiConfigValue<Boolean> tintWithAvoidedBlocks()
@@ -105,11 +105,6 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
// advanced graphic settings //
//===========================//
@Deprecated
@Override
public IDhApiConfigValue<EOverdrawPrevention> overdrawPrevention()
{ return new DhApiConfigValue<EOverdrawPrevention, EOverdrawPrevention>(Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPreventionPreset); }
@Override
public IDhApiConfigValue<Double> overdrawPreventionRadius()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention); }
@@ -143,8 +138,8 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.AdvancedGraphics.lodBias); }
@Override
public IDhApiConfigValue<ELodShading> lodShading()
{ return new DhApiConfigValue<ELodShading, ELodShading>(Config.Client.Advanced.Graphics.AdvancedGraphics.lodShading); }
public IDhApiConfigValue<EDhApiLodShading> lodShading()
{ return new DhApiConfigValue<EDhApiLodShading, EDhApiLodShading>(Config.Client.Advanced.Graphics.AdvancedGraphics.lodShading); }
@Override
public IDhApiConfigValue<Boolean> disableFrustumCulling()
@@ -19,9 +19,9 @@
package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.enums.rendering.EFogFalloff;
import com.seibel.distanthorizons.api.enums.rendering.EHeightFogMixMode;
import com.seibel.distanthorizons.api.enums.rendering.EHeightFogMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogFalloff;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMixMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMode;
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;
@@ -36,12 +36,12 @@ public class DhApiHeightFogConfig implements IDhApiHeightFogConfig
@Override
public IDhApiConfigValue<EHeightFogMixMode> heightFogMixMode()
{ return new DhApiConfigValue<EHeightFogMixMode, EHeightFogMixMode>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogMixMode); }
public IDhApiConfigValue<EDhApiHeightFogMixMode> heightFogMixMode()
{ return new DhApiConfigValue<EDhApiHeightFogMixMode, EDhApiHeightFogMixMode>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogMixMode); }
@Override
public IDhApiConfigValue<EHeightFogMode> heightFogMode()
{ return new DhApiConfigValue<EHeightFogMode, EHeightFogMode>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogMode); }
public IDhApiConfigValue<EDhApiHeightFogMode> heightFogMode()
{ return new DhApiConfigValue<EDhApiHeightFogMode, EDhApiHeightFogMode>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogMode); }
@Override
public IDhApiConfigValue<Double> heightFogBaseHeight()
@@ -64,8 +64,8 @@ public class DhApiHeightFogConfig implements IDhApiHeightFogConfig
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogMax); }
@Override
public IDhApiConfigValue<EFogFalloff> heightFogFalloff()
{ return new DhApiConfigValue<EFogFalloff, EFogFalloff>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogFalloff); }
public IDhApiConfigValue<EDhApiFogFalloff> heightFogFalloff()
{ return new DhApiConfigValue<EDhApiFogFalloff, EDhApiFogFalloff>(Config.Client.Advanced.Graphics.Fog.AdvancedFog.HeightFog.heightFogFalloff); }
@Override
public IDhApiConfigValue<Double> heightFogDensity()
@@ -23,7 +23,7 @@ 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.Config;
import com.seibel.distanthorizons.api.enums.config.EServerFolderNameMode;
import com.seibel.distanthorizons.api.enums.config.EDhApiServerFolderNameMode;
public class DhApiMultiplayerConfig implements IDhApiMultiplayerConfig
{
@@ -33,8 +33,8 @@ public class DhApiMultiplayerConfig implements IDhApiMultiplayerConfig
public IDhApiConfigValue<EServerFolderNameMode> folderSavingMode()
{ return new DhApiConfigValue<EServerFolderNameMode, EServerFolderNameMode>(Config.Client.Advanced.Multiplayer.serverFolderNameMode); }
public IDhApiConfigValue<EDhApiServerFolderNameMode> folderSavingMode()
{ return new DhApiConfigValue<EDhApiServerFolderNameMode, EDhApiServerFolderNameMode>(Config.Client.Advanced.Multiplayer.serverFolderNameMode); }
public IDhApiConfigValue<Double> multiverseSimilarityRequirement()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Multiplayer.multiverseSimilarityRequiredPercent); }
@@ -19,7 +19,6 @@
package com.seibel.distanthorizons.core.api.external.methods.config.common;
import com.seibel.distanthorizons.api.enums.config.ELightGenerationMode;
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;
@@ -27,8 +27,7 @@ import com.seibel.distanthorizons.api.interfaces.data.IDhApiTerrainDataRepo;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3i;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.SingleColumnFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
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;
@@ -47,6 +46,7 @@ import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.coreapi.util.math.Vec3d;
import com.seibel.distanthorizons.coreapi.util.math.Vec3f;
import com.seibel.distanthorizons.coreapi.util.math.Vec3i;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -205,26 +205,26 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
byte sectionDetailLevel = (byte) (requestedDetailLevel + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
// get the positions for this request
DhSectionPos sectionPos = requestedColumnPos.getSectionPosWithSectionDetailLevel(sectionDetailLevel);
long sectionPos = requestedColumnPos.getSectionPosWithSectionDetailLevel(sectionDetailLevel);
DhLodPos relativePos = requestedColumnPos.getDhSectionRelativePositionForDetailLevel();
try
{
// attempt to get/generate the data source for this section
IFullDataSource dataSource = level.getFileHandler().getAsync(sectionPos).get();
FullDataSourceV2 dataSource = level.getFullDataProvider().getAsync(sectionPos).get();
if (dataSource == null)
{
return DhApiResult.createFail("Unable to find/generate any data at the " + DhSectionPos.class.getSimpleName() + " [" + sectionPos + "].");
return DhApiResult.createFail("Unable to find/generate any data at the " + DhSectionPos.class.getSimpleName() + " [" + DhSectionPos.toString(sectionPos) + "].");
}
else
{
// attempt to get the LOD data from the data source
FullDataPointIdMap mapping = dataSource.getMapping();
SingleColumnFullDataAccessor dataColumn = dataSource.tryGet(relativePos.x, relativePos.z);
FullDataPointIdMap mapping = dataSource.mapping;
LongArrayList dataColumn = dataSource.get(relativePos.x, relativePos.z);
if (dataColumn != null)
{
int dataColumnIndexCount = dataColumn.getSingleLength();
int dataColumnIndexCount = dataColumn.size();
DhApiTerrainDataPoint[] returnArray = new DhApiTerrainDataPoint[dataColumnIndexCount];
long dataPoint;
@@ -235,7 +235,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
// search for a datapoint that contains the block y position
for (int i = 0; i < dataColumnIndexCount; i++)
{
dataPoint = dataColumn.getSingle(i);
dataPoint = dataColumn.getLong(i);
if (!getSpecificYCoordinate)
{
@@ -290,7 +290,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
int topY = bottomY + height;
return new DhApiTerrainDataPoint(detailLevel,
FullDataPointUtil.getLight(dataPoint), topY, bottomY,
FullDataPointUtil.getBlockLight(dataPoint), FullDataPointUtil.getSkyLight(dataPoint),
topY, bottomY,
blockState, biomeWrapper);
}
@@ -460,7 +461,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);
dhLevel.updateChunkAsync(chunk);
SharedApi.INSTANCE.applyChunkUpdate(chunk, dhLevel.getLevelWrapper(), true);
return DhApiResult.createSuccess();
@@ -21,7 +21,6 @@ package com.seibel.distanthorizons.core.api.internal;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiFramebuffer;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager;
@@ -32,10 +31,9 @@ import com.seibel.distanthorizons.core.world.*;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.api.enums.rendering.EDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.ERendererMode;
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.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
@@ -54,10 +52,11 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL32;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
@@ -81,6 +80,9 @@ public class ClientApi
public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
private boolean configOverrideReminderPrinted = false;
private final Queue<String> chatMessageQueueForNextFrame = new LinkedBlockingQueue<>();
public boolean rendererDisabledBecauseOfExceptions = false;
private long lastFlushNanoTime = 0;
@@ -179,7 +181,7 @@ public class ClientApi
// can happen on certain multiverse servers
return;
}
LOGGER.info("Unloading client level [" + level + "].");
LOGGER.info("Unloading client level [" + level + "]-["+level.getDimensionType().getDimensionName()+"].");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
@@ -218,7 +220,7 @@ public class ClientApi
LOGGER.info("Loading " + (isServerCommunication ? "Multiverse" : "") + " client level [" + level + "].");
LOGGER.info("Loading " + (isServerCommunication ? "Multiverse" : "") + " client level [" + level + "]-["+level.getDimensionType().getDimensionName()+"].");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
@@ -473,14 +475,28 @@ public class ClientApi
{
// logging //
// dev build
if (ModInfo.IS_DEV_BUILD && !this.configOverrideReminderPrinted && MC.playerExists())
{
this.configOverrideReminderPrinted = true;
// remind the user that this is a development build
MC.sendChatMessage(ModInfo.READABLE_NAME + " experimental build " + ModInfo.VERSION);
MC.sendChatMessage("You are running an unsupported version of Distant Horizons!");
MC.sendChatMessage("Distant Horizons nightly/unstable build, version: [" + ModInfo.VERSION+"].");
MC.sendChatMessage("Issues may occur with this version.");
MC.sendChatMessage("Here be dragons!");
MC.sendChatMessage("");
}
// generic messages
while (!this.chatMessageQueueForNextFrame.isEmpty())
{
String message = this.chatMessageQueueForNextFrame.poll();
if (message == null)
{
// done to prevent potential null pointers
message = "";
}
MC.sendChatMessage(message);
}
IProfilerWrapper profiler = MC.getProfiler();
@@ -547,7 +563,7 @@ public class ClientApi
if (!renderingDeferredLayer)
{
if (Config.Client.Advanced.Debugging.rendererMode.get() == ERendererMode.DEFAULT)
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
{
this.renderingCancelledForThisFrame = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, renderEventParam);
if (!this.renderingCancelledForThisFrame)
@@ -560,7 +576,7 @@ public class ClientApi
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, renderEventParam);
}
}
else if (Config.Client.Advanced.Debugging.rendererMode.get() == ERendererMode.DEBUG)
else if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEBUG)
{
profiler.push("Render Debug");
ClientApi.testRenderer.render();
@@ -617,12 +633,12 @@ public class ClientApi
if (glfwKey == GLFW.GLFW_KEY_F8)
{
Config.Client.Advanced.Debugging.debugRendering.set(EDebugRendering.next(Config.Client.Advanced.Debugging.debugRendering.get()));
Config.Client.Advanced.Debugging.debugRendering.set(EDhApiDebugRendering.next(Config.Client.Advanced.Debugging.debugRendering.get()));
MC.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRendering.get());
}
else if (glfwKey == GLFW.GLFW_KEY_F6)
{
Config.Client.Advanced.Debugging.rendererMode.set(ERendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get()));
Config.Client.Advanced.Debugging.rendererMode.set(EDhApiRendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get()));
MC.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererMode.get());
}
else if (glfwKey == GLFW.GLFW_KEY_P)
@@ -632,5 +648,10 @@ public class ClientApi
}
}
/**
* Queues the given message to appear in chat the next valid frame.
* Useful for queueing up messages that may be triggered before the user has loaded into the world.
*/
public void showChatMessageNextFrame(String chatMessage) { this.chatMessageQueueForNextFrame.add(chatMessage); }
}
@@ -38,11 +38,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
/**
* This holds the methods that should be called by the host mod loader (Fabric,
* Forge, etc.). Specifically server events.
@@ -32,7 +32,7 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.threading.ThreadPools;
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.IMinecraftRenderWrapper;
@@ -46,6 +46,7 @@ import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
/** Contains code and variables used by both {@link ClientApi} and {@link ServerApi} */
@@ -102,13 +103,15 @@ public class SharedApi
// access the MC level at inappropriate times, which can cause exceptions
if (currentWorld != null)
{
ThreadPools.setupThreadPools();
ThreadPoolUtil.setupThreadPools();
}
else
{
ThreadPools.shutdownThreadPools();
ThreadPoolUtil.shutdownThreadPools();
DebugRenderer.clearRenderables();
MC_RENDER.clearTargetFrameBuffer();
// needs to be closed on world shutdown to clear out un-processed chunks
UPDATING_CHUNK_POS_SET.clear();
// recommend that the garbage collector cleans up any objects from the old world and thread pools
System.gc();
@@ -285,77 +288,81 @@ public class SharedApi
private static void bakeChunkLightingAndSendToLevelAsync(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighbourChunkList, IDhLevel dhLevel)
{
// lighting the chunk needs to be done on a separate thread to prevent lagging any of the event threads
ThreadPoolExecutor executor = ThreadPools.getLightPopulatorExecutor();
ThreadPoolExecutor executor = ThreadPoolUtil.getLightPopulatorExecutor();
if (executor == null)
{
return;
}
executor.execute(() ->
try
{
LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
try
executor.execute(() ->
{
// Save or populate the chunk wrapper's lighting
// this is done so we don't have to worry about MC unloading the lighting data for this chunk
boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get();
if (!onlyUseDhLighting && chunkWrapper.isLightCorrect())
//LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
try
{
try
// Save or populate the chunk wrapper's lighting
// this is done so we don't have to worry about MC unloading the lighting data for this chunk
boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get();
if (!onlyUseDhLighting && chunkWrapper.isLightCorrect())
{
// If MC's lighting engine isn't thread safe this may cause the server thread to lag
chunkWrapper.bakeDhLightingUsingMcLightingEngine();
}
catch (IllegalStateException e)
{
LOGGER.warn("Chunk light baking error: " + e.getMessage(), e);
}
}
else
{
// generate the chunk's lighting, using neighboring chunks if present
ArrayList<IChunkWrapper> nearbyChunkList;
if (neighbourChunkList != null)
{
nearbyChunkList = neighbourChunkList;
try
{
// If MC's lighting engine isn't thread safe this may cause the server thread to lag
chunkWrapper.bakeDhLightingUsingMcLightingEngine();
}
catch (IllegalStateException e)
{
LOGGER.warn("Chunk light baking error: " + e.getMessage(), e);
}
}
else
{
nearbyChunkList = new ArrayList<>(1);
nearbyChunkList.add(chunkWrapper);
// generate the chunk's lighting, using neighboring chunks if present
ArrayList<IChunkWrapper> nearbyChunkList;
if (neighbourChunkList != null)
{
nearbyChunkList = neighbourChunkList;
}
else
{
nearbyChunkList = new ArrayList<>(1);
nearbyChunkList.add(chunkWrapper);
}
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? 15 : 0);
}
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? 15 : 0);
dhLevel.updateChunkAsync(chunkWrapper);
}
dhLevel.updateChunkAsync(chunkWrapper);
}
catch (Exception e)
{
LOGGER.error("Unexpected error when updating chunk at pos: ["+chunkWrapper.getChunkPos()+"]", e);
}
finally
{
// the LOD chunk has finished being updated
int updateTimeoutInSec = Config.Client.Advanced.LodBuilding.minTimeBetweenChunkUpdatesInSeconds.get();
if (updateTimeoutInSec != 0)
catch (Exception e)
{
// prevent updating this chunk again until the timeout finishes
CHUNK_UPDATE_TIMER.schedule(new TimerTask()
LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
}
finally
{
// the LOD chunk has finished being updated
int updateTimeoutInSec = Config.Client.Advanced.LodBuilding.minTimeBetweenChunkUpdatesInSeconds.get();
if (updateTimeoutInSec != 0)
{
@Override
public void run() { UPDATING_CHUNK_POS_SET.remove(chunkWrapper.getChunkPos()); }
}, updateTimeoutInSec * 1000L);
// prevent updating this chunk again until the timeout finishes
CHUNK_UPDATE_TIMER.schedule(new TimerTask()
{
@Override
public void run() { UPDATING_CHUNK_POS_SET.remove(chunkWrapper.getChunkPos()); }
}, updateTimeoutInSec * 1000L);
}
else
{
// instantly allow this chunk to be updated again
UPDATING_CHUNK_POS_SET.remove(chunkWrapper.getChunkPos());
}
}
else
{
// instantly allow this chunk to be updated again
UPDATING_CHUNK_POS_SET.remove(chunkWrapper.getChunkPos());
}
}
});
});
}
catch (RejectedExecutionException ignore) { /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
}
@@ -37,7 +37,9 @@ import com.seibel.distanthorizons.coreapi.util.StringUtil;
import org.apache.logging.log4j.Logger;
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.util.List;
/**
@@ -72,8 +74,8 @@ public class Config
public static ConfigLinkedEntry quickLodChunkRenderDistance = new ConfigLinkedEntry(Advanced.Graphics.Quality.lodChunkRenderDistanceRadius);
public static ConfigEntry<EQualityPreset> qualityPresetSetting = new ConfigEntry.Builder<EQualityPreset>()
.set(EQualityPreset.MEDIUM) // the default value is set via the listener when accessed
public static ConfigEntry<EDhApiQualityPreset> qualityPresetSetting = new ConfigEntry.Builder<EDhApiQualityPreset>()
.set(EDhApiQualityPreset.MEDIUM) // the default value is set via the listener when accessed
.comment(""
+ "Changing this setting will modify a number of different settings that will change the \n"
+ "visual fidelity of the rendered LODs.\n"
@@ -84,8 +86,8 @@ public class Config
.addListener(RenderQualityPresetConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<EThreadPreset> threadPresetSetting = new ConfigEntry.Builder<EThreadPreset>()
.set(EThreadPreset.LOW_IMPACT) // the default value is set via the listener when accessed
public static ConfigEntry<EDhApiThreadPreset> threadPresetSetting = new ConfigEntry.Builder<EDhApiThreadPreset>()
.set(EDhApiThreadPreset.LOW_IMPACT) // the default value is set via the listener when accessed
.comment(""
+ "Changing this setting will modify a number of different settings that will change \n"
+ "the load that Distant Horizons is allowed to put on your CPU. \n"
@@ -138,20 +140,20 @@ public class Config
public static class Quality
{
public static ConfigEntry<EMaxHorizontalResolution> maxHorizontalResolution = new ConfigEntry.Builder<EMaxHorizontalResolution>()
.set(EMaxHorizontalResolution.BLOCK)
public static ConfigEntry<EDhApiMaxHorizontalResolution> maxHorizontalResolution = new ConfigEntry.Builder<EDhApiMaxHorizontalResolution>()
.set(EDhApiMaxHorizontalResolution.BLOCK)
.comment(""
+ "What is the maximum detail LODs should be drawn at? \n"
+ "Higher settings will increase memory and GPU usage. \n"
+ "\n"
+ EMaxHorizontalResolution.CHUNK + ": render 1 LOD for each Chunk. \n"
+ EMaxHorizontalResolution.HALF_CHUNK + ": render 4 LODs for each Chunk. \n"
+ EMaxHorizontalResolution.FOUR_BLOCKS + ": render 16 LODs for each Chunk. \n"
+ EMaxHorizontalResolution.TWO_BLOCKS + ": render 64 LODs for each Chunk. \n"
+ EMaxHorizontalResolution.BLOCK + ": render 256 LODs for each Chunk (width of one block). \n"
+ EDhApiMaxHorizontalResolution.CHUNK + ": render 1 LOD for each Chunk. \n"
+ EDhApiMaxHorizontalResolution.HALF_CHUNK + ": render 4 LODs for each Chunk. \n"
+ EDhApiMaxHorizontalResolution.FOUR_BLOCKS + ": render 16 LODs for each Chunk. \n"
+ EDhApiMaxHorizontalResolution.TWO_BLOCKS + ": render 64 LODs for each Chunk. \n"
+ EDhApiMaxHorizontalResolution.BLOCK + ": render 256 LODs for each Chunk (width of one block). \n"
+ "\n"
+ "Lowest Quality: " + EMaxHorizontalResolution.CHUNK + "\n"
+ "Highest Quality: " + EMaxHorizontalResolution.BLOCK)
+ "Lowest Quality: " + EDhApiMaxHorizontalResolution.CHUNK + "\n"
+ "Highest Quality: " + EDhApiMaxHorizontalResolution.BLOCK)
.setPerformance(EConfigEntryPerformance.MEDIUM)
.build();
@@ -161,21 +163,21 @@ public class Config
.setPerformance(EConfigEntryPerformance.HIGH)
.build();
public static ConfigEntry<EVerticalQuality> verticalQuality = new ConfigEntry.Builder<EVerticalQuality>()
.set(EVerticalQuality.MEDIUM)
public static ConfigEntry<EDhApiVerticalQuality> verticalQuality = new ConfigEntry.Builder<EDhApiVerticalQuality>()
.set(EDhApiVerticalQuality.MEDIUM)
.comment(""
+ "This indicates how well LODs will represent \n"
+ "overhangs, caves, floating islands, etc. \n"
+ "Higher options will make the world more accurate, but"
+ "will increase memory and GPU usage. \n"
+ "\n"
+ "Lowest Quality: " + EVerticalQuality.HEIGHT_MAP + "\n"
+ "Highest Quality: " + EVerticalQuality.EXTREME)
+ "Lowest Quality: " + EDhApiVerticalQuality.HEIGHT_MAP + "\n"
+ "Highest Quality: " + EDhApiVerticalQuality.EXTREME)
.setPerformance(EConfigEntryPerformance.VERY_HIGH)
.build();
public static ConfigEntry<EHorizontalQuality> horizontalQuality = new ConfigEntry.Builder<EHorizontalQuality>()
.set(EHorizontalQuality.MEDIUM)
public static ConfigEntry<EDhApiHorizontalQuality> horizontalQuality = new ConfigEntry.Builder<EDhApiHorizontalQuality>()
.set(EDhApiHorizontalQuality.MEDIUM)
.comment(""
+ "This indicates how quickly LODs decrease in quality the further away they are. \n"
+ "Higher settings will render higher quality fake chunks farther away, \n"
@@ -183,25 +185,25 @@ public class Config
.setPerformance(EConfigEntryPerformance.MEDIUM)
.build();
public static ConfigEntry<ETransparency> transparency = new ConfigEntry.Builder<ETransparency>()
.set(ETransparency.COMPLETE)
public static ConfigEntry<EDhApiTransparency> transparency = new ConfigEntry.Builder<EDhApiTransparency>()
.set(EDhApiTransparency.COMPLETE)
.comment(""
+ "How should LOD transparency be handled. \n"
+ "\n"
+ ETransparency.COMPLETE + ": LODs will render transparent. \n"
+ ETransparency.FAKE + ": LODs will be opaque, but shaded to match the blocks underneath. \n"
+ ETransparency.DISABLED + ": LODs will be opaque. \n"
+ EDhApiTransparency.COMPLETE + ": LODs will render transparent. \n"
+ EDhApiTransparency.FAKE + ": LODs will be opaque, but shaded to match the blocks underneath. \n"
+ EDhApiTransparency.DISABLED + ": LODs will be opaque. \n"
+ "")
.setPerformance(EConfigEntryPerformance.MEDIUM)
.build();
public static ConfigEntry<EBlocksToAvoid> blocksToIgnore = new ConfigEntry.Builder<EBlocksToAvoid>()
.set(EBlocksToAvoid.NON_COLLIDING)
public static ConfigEntry<EDhApiBlocksToAvoid> blocksToIgnore = new ConfigEntry.Builder<EDhApiBlocksToAvoid>()
.set(EDhApiBlocksToAvoid.NON_COLLIDING)
.comment(""
+ "What blocks shouldn't be rendered as LODs? \n"
+ "\n"
+ EBlocksToAvoid.NONE + ": Represent all blocks in the LODs \n"
+ EBlocksToAvoid.NON_COLLIDING + ": Only represent solid blocks in the LODs (tall grass, torches, etc. won't count for a LOD's height) \n"
+ EDhApiBlocksToAvoid.NONE + ": Represent all blocks in the LODs \n"
+ EDhApiBlocksToAvoid.NON_COLLIDING + ": Only represent solid blocks in the LODs (tall grass, torches, etc. won't count for a LOD's height) \n"
+ "")
.setPerformance(EConfigEntryPerformance.NONE)
.build();
@@ -234,33 +236,27 @@ public class Config
public static class Fog
{
public static ConfigEntry<EFogDrawMode> drawMode = new ConfigEntry.Builder<EFogDrawMode>()
.set(EFogDrawMode.FOG_ENABLED)
public static ConfigEntry<EDhApiFogDrawMode> drawMode = new ConfigEntry.Builder<EDhApiFogDrawMode>()
.set(EDhApiFogDrawMode.FOG_ENABLED)
.comment(""
+ "When should fog be drawn? \n"
+ "\n"
+ EFogDrawMode.USE_OPTIFINE_SETTING + ": Use whatever Fog setting Optifine is using.\n"
+ "If Optifine isn't installed this defaults to " + EFogDrawMode.FOG_ENABLED + ". \n"
+ EFogDrawMode.FOG_ENABLED + ": Never draw fog on the LODs \n"
+ EFogDrawMode.FOG_DISABLED + ": Always draw fast fog on the LODs \n"
+ EDhApiFogDrawMode.USE_OPTIFINE_SETTING + ": Use whatever Fog setting Optifine is using.\n"
+ "If Optifine isn't installed this defaults to " + EDhApiFogDrawMode.FOG_ENABLED + ". \n"
+ EDhApiFogDrawMode.FOG_ENABLED + ": Never draw fog on the LODs \n"
+ EDhApiFogDrawMode.FOG_DISABLED + ": Always draw fast fog on the LODs \n"
+ "\n"
+ "Disabling fog will improve GPU performance.")
.setPerformance(EConfigEntryPerformance.VERY_LOW)
.build();
public static ConfigEntry<EFogDistance> distance = new ConfigEntry.Builder<EFogDistance>()
.set(EFogDistance.FAR)
.comment("At what distance should Fog be drawn on the LODs?")
.setPerformance(EConfigEntryPerformance.NONE)
.build();
public static ConfigEntry<EFogColorMode> colorMode = new ConfigEntry.Builder<EFogColorMode>()
.set(EFogColorMode.USE_WORLD_FOG_COLOR)
public static ConfigEntry<EDhApiFogColorMode> colorMode = new ConfigEntry.Builder<EDhApiFogColorMode>()
.set(EDhApiFogColorMode.USE_WORLD_FOG_COLOR)
.comment(""
+ "What color should fog use? \n"
+ "\n"
+ EFogColorMode.USE_WORLD_FOG_COLOR + ": Use the world's fog color. \n"
+ EFogColorMode.USE_SKY_COLOR + ": Use the sky's color.")
+ EDhApiFogColorMode.USE_WORLD_FOG_COLOR + ": Use the world's fog color. \n"
+ EDhApiFogColorMode.USE_SKY_COLOR + ": Use the sky's color.")
.setPerformance(EConfigEntryPerformance.NONE)
.build();
@@ -320,14 +316,14 @@ public class Config
+ "1.0: Fully opaque fog.")
.build();
public static ConfigEntry<EFogFalloff> farFogFalloff = new ConfigEntry.Builder<EFogFalloff>()
.set(EFogFalloff.EXPONENTIAL_SQUARED)
public static ConfigEntry<EDhApiFogFalloff> farFogFalloff = new ConfigEntry.Builder<EDhApiFogFalloff>()
.set(EDhApiFogFalloff.EXPONENTIAL_SQUARED)
.comment(""
+ "How should the fog thickness should be calculated? \n"
+ "\n"
+ EFogFalloff.LINEAR + ": Linear based on distance (will ignore 'density')\n"
+ EFogFalloff.EXPONENTIAL + ": 1/(e^(distance*density)) \n"
+ EFogFalloff.EXPONENTIAL_SQUARED + ": 1/(e^((distance*density)^2)")
+ EDhApiFogFalloff.LINEAR + ": Linear based on distance (will ignore 'density')\n"
+ EDhApiFogFalloff.EXPONENTIAL + ": 1/(e^(distance*density)) \n"
+ EDhApiFogFalloff.EXPONENTIAL_SQUARED + ": 1/(e^((distance*density)^2)")
.build();
public static ConfigEntry<Double> farFogDensity = new ConfigEntry.Builder<Double>()
@@ -343,37 +339,37 @@ public class Config
public static class HeightFog
{
public static ConfigEntry<EHeightFogMixMode> heightFogMixMode = new ConfigEntry.Builder<EHeightFogMixMode>()
.set(EHeightFogMixMode.BASIC)
public static ConfigEntry<EDhApiHeightFogMixMode> heightFogMixMode = new ConfigEntry.Builder<EDhApiHeightFogMixMode>()
.set(EDhApiHeightFogMixMode.BASIC)
.comment(""
+ "How should height effect the fog thickness? \n"
+ "Note: height fog is combined with the other fog settings. \n"
+ "\n"
+ EHeightFogMixMode.BASIC + ": No special height fog effect. Fog is calculated based on camera distance \n"
+ EHeightFogMixMode.IGNORE_HEIGHT + ": Ignore height completely. Fog is only calculated with horizontal distance \n"
+ EHeightFogMixMode.ADDITION + ": heightFog + farFog \n"
+ EHeightFogMixMode.MAX + ": max(heightFog, farFog) \n"
+ EHeightFogMixMode.MULTIPLY + ": heightFog * farFog \n"
+ EHeightFogMixMode.INVERSE_MULTIPLY + ": 1 - (1-heightFog) * (1-farFog) \n"
+ EHeightFogMixMode.LIMITED_ADDITION + ": farFog + max(farFog, heightFog) \n"
+ EHeightFogMixMode.MULTIPLY_ADDITION + ": farFog + farFog * heightFog \n"
+ EHeightFogMixMode.INVERSE_MULTIPLY_ADDITION + ": farFog + 1 - (1-heightFog) * (1-farFog) \n"
+ EHeightFogMixMode.AVERAGE + ": farFog*0.5 + heightFog*0.5 \n"
+ EDhApiHeightFogMixMode.BASIC + ": No special height fog effect. Fog is calculated based on camera distance \n"
+ EDhApiHeightFogMixMode.IGNORE_HEIGHT + ": Ignore height completely. Fog is only calculated with horizontal distance \n"
+ EDhApiHeightFogMixMode.ADDITION + ": heightFog + farFog \n"
+ EDhApiHeightFogMixMode.MAX + ": max(heightFog, farFog) \n"
+ EDhApiHeightFogMixMode.MULTIPLY + ": heightFog * farFog \n"
+ EDhApiHeightFogMixMode.INVERSE_MULTIPLY + ": 1 - (1-heightFog) * (1-farFog) \n"
+ EDhApiHeightFogMixMode.LIMITED_ADDITION + ": farFog + max(farFog, heightFog) \n"
+ EDhApiHeightFogMixMode.MULTIPLY_ADDITION + ": farFog + farFog * heightFog \n"
+ EDhApiHeightFogMixMode.INVERSE_MULTIPLY_ADDITION + ": farFog + 1 - (1-heightFog) * (1-farFog) \n"
+ EDhApiHeightFogMixMode.AVERAGE + ": farFog*0.5 + heightFog*0.5 \n"
+ "\n"
+ "Note: height fog settings are ignored if '" + EHeightFogMixMode.BASIC + "' or '" + EHeightFogMixMode.IGNORE_HEIGHT + "' are selected.")
+ "Note: height fog settings are ignored if '" + EDhApiHeightFogMixMode.BASIC + "' or '" + EDhApiHeightFogMixMode.IGNORE_HEIGHT + "' are selected.")
.build();
public static ConfigEntry<EHeightFogMode> heightFogMode = new ConfigEntry.Builder<EHeightFogMode>()
.set(EHeightFogMode.ABOVE_AND_BELOW_CAMERA)
public static ConfigEntry<EDhApiHeightFogMode> heightFogMode = new ConfigEntry.Builder<EDhApiHeightFogMode>()
.set(EDhApiHeightFogMode.ABOVE_AND_BELOW_CAMERA)
.comment(""
+ "Where should the height fog start? \n"
+ "\n"
+ EHeightFogMode.ABOVE_CAMERA + ": Height fog starts at the camera and goes towards the sky \n"
+ EHeightFogMode.BELOW_CAMERA + ": Height fog starts at the camera and goes towards the void \n"
+ EHeightFogMode.ABOVE_AND_BELOW_CAMERA + ": Height fog starts from the camera to goes towards both the sky and void \n"
+ EHeightFogMode.ABOVE_SET_HEIGHT + ": Height fog starts from a set height and goes towards the sky \n"
+ EHeightFogMode.BELOW_SET_HEIGHT + ": Height fog starts from a set height and goes towards the void \n"
+ EHeightFogMode.ABOVE_AND_BELOW_SET_HEIGHT + ": Height fog starts from a set height and goes towards both the sky and void")
+ EDhApiHeightFogMode.ABOVE_CAMERA + ": Height fog starts at the camera and goes towards the sky \n"
+ EDhApiHeightFogMode.BELOW_CAMERA + ": Height fog starts at the camera and goes towards the void \n"
+ EDhApiHeightFogMode.ABOVE_AND_BELOW_CAMERA + ": Height fog starts from the camera to goes towards both the sky and void \n"
+ EDhApiHeightFogMode.ABOVE_SET_HEIGHT + ": Height fog starts from a set height and goes towards the sky \n"
+ EDhApiHeightFogMode.BELOW_SET_HEIGHT + ": Height fog starts from a set height and goes towards the void \n"
+ EDhApiHeightFogMode.ABOVE_AND_BELOW_SET_HEIGHT + ": Height fog starts from a set height and goes towards both the sky and void")
.build();
public static ConfigEntry<Double> heightFogBaseHeight = new ConfigEntry.Builder<Double>()
@@ -417,14 +413,14 @@ public class Config
+ "1.0: Fully opaque fog.")
.build();
public static ConfigEntry<EFogFalloff> heightFogFalloff = new ConfigEntry.Builder<EFogFalloff>()
.set(EFogFalloff.EXPONENTIAL_SQUARED)
public static ConfigEntry<EDhApiFogFalloff> heightFogFalloff = new ConfigEntry.Builder<EDhApiFogFalloff>()
.set(EDhApiFogFalloff.EXPONENTIAL_SQUARED)
.comment(""
+ "How should the height fog thickness should be calculated? \n"
+ "\n"
+ EFogFalloff.LINEAR + ": Linear based on height (will ignore 'density')\n"
+ EFogFalloff.EXPONENTIAL + ": 1/(e^(height*density)) \n"
+ EFogFalloff.EXPONENTIAL_SQUARED + ": 1/(e^((height*density)^2)")
+ EDhApiFogFalloff.LINEAR + ": Linear based on height (will ignore 'density')\n"
+ EDhApiFogFalloff.EXPONENTIAL + ": 1/(e^(height*density)) \n"
+ EDhApiFogFalloff.EXPONENTIAL_SQUARED + ": 1/(e^((height*density)^2)")
.build();
public static ConfigEntry<Double> heightFogDensity = new ConfigEntry.Builder<Double>()
@@ -534,18 +530,6 @@ public class Config
public static class AdvancedGraphics
{
/**
* @deprecated Use overdrawPrevention instead, will be removed when DH updates to MC 1.21 <br>
* After removal a float value will be used to control overdraw instead. <br>
*/
@Deprecated
public static ConfigEntry<EOverdrawPrevention> overdrawPreventionPreset = new ConfigEntry.Builder<EOverdrawPrevention>()
.set(EOverdrawPrevention.MEDIUM)
.comment("")
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
.setPerformance(EConfigEntryPerformance.NONE)
.build();
public static ConfigEntry<Double> overdrawPrevention = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0.0, 0.4, 1.0)
.comment(""
@@ -561,21 +545,6 @@ public class Config
.setPerformance(EConfigEntryPerformance.NONE)
.build();
@Deprecated // TODO remove failed experiment
public static ConfigEntry<Boolean> seamlessOverdraw = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "Buggy experimental option that will attempt to match up \n"
+ "Distant Horizons' and Minecraft's near/far clip planes, \n"
+ "reducing overdraw. \n"
+ "\n"
+ "Only functional on Fabric.\n"
+ "Works best with an overdraw prevention setting of " + EOverdrawPrevention.MEDIUM + " or higher \n"
+ " and cave culling is disabled. \n"
+ "")
.setPerformance(EConfigEntryPerformance.NONE)
.build();
// move into "shader compatibility"
public static ConfigEntry<Double> brightnessMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats)
.set(1.0)
@@ -641,15 +610,15 @@ public class Config
+ "If set to 0 the mod wont overwrite vanilla's default (which so happens to also be 0)")
.build();
public static ConfigEntry<ELodShading> lodShading = new ConfigEntry.Builder<ELodShading>()
.set(ELodShading.MINECRAFT)
public static ConfigEntry<EDhApiLodShading> lodShading = new ConfigEntry.Builder<EDhApiLodShading>()
.set(EDhApiLodShading.AUTO)
.comment(""
+ "How should LODs be shaded? \n"
+ "\n"
+ ELodShading.MINECRAFT + ": Uses the same side shading as vanilla Minecraft blocks. \n"
+ ELodShading.OLD_LIGHTING + ": Simulates Minecraft's block shading for LODs. \n"
+ EDhApiLodShading.AUTO + ": Uses the same side shading as vanilla Minecraft blocks. \n"
+ EDhApiLodShading.ENABLED + ": Simulates Minecraft's block shading for LODs. \n"
+ " Can be used to force LOD shading when using some shaders. \n"
+ ELodShading.NONE + ": All LOD sides will be rendered with the same brightness. \n"
+ EDhApiLodShading.DISABLED + ": All LOD sides will be rendered with the same brightness. \n"
+ "")
.setPerformance(EConfigEntryPerformance.NONE)
.build();
@@ -676,6 +645,17 @@ public class Config
+ "Disable this if shadows render incorrectly.")
.build();
public static ConfigEntry<EDhApiGrassSideRendering> grassSideRendering = new ConfigEntry.Builder<EDhApiGrassSideRendering>()
.set(EDhApiGrassSideRendering.FADE_TO_DIRT)
.comment(""
+ "How should the sides and bottom of grass block LODs render? \n"
+ "\n"
+ EDhApiGrassSideRendering.AS_GRASS + ": all sides of dirt LOD's render using the top (green) color. \n"
+ EDhApiGrassSideRendering.FADE_TO_DIRT + ": sides fade from grass to dirt. \n"
+ EDhApiGrassSideRendering.AS_DIRT + ": sides render entirely as dirt. \n"
+ "")
.setPerformance(EConfigEntryPerformance.NONE)
.build();
}
@@ -743,36 +723,6 @@ public class Config
+ "")
.build();
// deprecated and not implemented, can be made public if we ever re-implement it
@Deprecated
private static ConfigEntry<EGenerationPriority> generationPriority = new ConfigEntry.Builder<EGenerationPriority>()
.set(EGenerationPriority.NEAR_FIRST)
.comment(""
+ "In what priority should fake chunks be generated outside the vanilla render distance? \n"
+ "\n"
+ EGenerationPriority.FAR_FIRST + " \n"
+ "Fake chunks are generated from lowest to highest detail \n"
+ " with a priority for far away regions. \n"
+ "This fills in the world fastest, but you will have large low detail \n"
+ " blocks for a while while the generation happens. \n"
+ "\n"
+ EGenerationPriority.NEAR_FIRST + " \n"
+ "Fake chunks are generated around the player \n"
+ " in a spiral, similar to vanilla minecraft. \n"
+ "Best used when on a server since we can't generate \n"
+ " fake chunks. \n"
+ "\n"
+ EGenerationPriority.BALANCED + " \n"
+ "A mix between " + EGenerationPriority.NEAR_FIRST + "and" + EGenerationPriority.FAR_FIRST + ". \n"
+ "First prioritise completing nearby highest detail chunks, \n"
+ " then focus on filling in the low detail areas away from the player. \n"
+ "\n"
+ EGenerationPriority.AUTO + " \n"
+ "Uses " + EGenerationPriority.BALANCED + " when on a single player world \n"
+ " and " + EGenerationPriority.NEAR_FIRST + " when connected to a server.")
.setPerformance(EConfigEntryPerformance.NONE)
.build();
}
public static class LodBuilding
@@ -801,19 +751,75 @@ public class Config
+ "")
.build();
public static ConfigEntry<EDhApiDataCompressionMode> dataCompression = new ConfigEntry.Builder<EDhApiDataCompressionMode>()
.set(EDhApiDataCompressionMode.LZMA2)
.comment(""
+ "What algorithm should be used to compress new LOD data? \n"
+ "This setting will only affect new or updated LOD data, \n"
+ "any data already generated when this setting is changed will be\n"
+ "unaffected until it needs to be re-written to the database.\n"
+ "\n"
+ EDhApiDataCompressionMode.UNCOMPRESSED + " \n"
+ "Should only be used for testing, is worse in every way vs ["+EDhApiDataCompressionMode.LZ4+"].\n"
+ "Expected Compression Ratio: 1.0\n"
+ "Estimated average DTO read speed: 1.64 milliseconds\n"
+ "Estimated average DTO write speed: 12.44 milliseconds\n"
+ "\n"
+ EDhApiDataCompressionMode.LZ4 + " \n"
+ "A good option if you're CPU limited and have plenty of hard drive space.\n"
+ "Expected Compression Ratio: 0.36\n"
+ "Estimated average DTO read speed: 1.85 ms\n"
+ "Estimated average DTO write speed: 9.46 ms\n"
+ "\n"
+ EDhApiDataCompressionMode.LZMA2 + " \n"
+ "Slow but very good compression.\n"
+ "Expected Compression Ratio: 0.14\n"
+ "Estimated average DTO read speed: 11.89 ms\n"
+ "Estimated average DTO write speed: 192.01 ms\n"
+ "")
.build();
public static ConfigEntry<EDhApiWorldCompressionMode> worldCompression = new ConfigEntry.Builder<EDhApiWorldCompressionMode>()
.set(EDhApiWorldCompressionMode.VISUALLY_EQUAL)
.comment(""
+ "How should block data be compressed when creating LOD data? \n"
+ "This setting will only affect new or updated LOD data, \n"
+ "any data already generated when this setting is changed will be\n"
+ "unaffected until it is modified or re-loaded.\n"
+ "\n"
+ EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS + " \n"
+ "Every block/biome change is recorded in the database. \n"
+ "This is what DH 2.0 and 2.0.1 all used by default and will store a lot of data. \n"
+ "Expected Compression Ratio: 1.0\n"
+ "\n"
+ EDhApiWorldCompressionMode.VISUALLY_EQUAL + " \n"
+ "Only visible block/biome changes are recorded in the database. \n"
+ "Hidden blocks (IE ores) are ignored. \n"
+ "Expected Compression Ratio: 0.7\n"
+ "")
.build();
public static ConfigEntry<Boolean> showMigrationChatWarning = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "Determines if a message should be displayed in the chat when LOD migration starts. \n"
+ "")
.build();
}
public static class Multiplayer
{
public static ConfigEntry<EServerFolderNameMode> serverFolderNameMode = new ConfigEntry.Builder<EServerFolderNameMode>()
.set(EServerFolderNameMode.NAME_ONLY)
public static ConfigEntry<EDhApiServerFolderNameMode> serverFolderNameMode = new ConfigEntry.Builder<EDhApiServerFolderNameMode>()
.set(EDhApiServerFolderNameMode.NAME_ONLY)
.comment(""
+ "How should multiplayer save folders should be named? \n"
+ "\n"
+ EServerFolderNameMode.NAME_ONLY + ": Example: \"Minecraft Server\" \n"
+ EServerFolderNameMode.NAME_IP + ": Example: \"Minecraft Server IP 192.168.1.40\" \n"
+ EServerFolderNameMode.NAME_IP_PORT + ": Example: \"Minecraft Server IP 192.168.1.40:25565\""
+ EServerFolderNameMode.NAME_IP_PORT_MC_VERSION + ": Example: \"Minecraft Server IP 192.168.1.40:25565 GameVersion 1.16.5\"")
+ EDhApiServerFolderNameMode.NAME_ONLY + ": Example: \"Minecraft Server\" \n"
+ EDhApiServerFolderNameMode.IP_ONLY + ": Example: \"192.168.1.40\" \n"
+ EDhApiServerFolderNameMode.NAME_IP + ": Example: \"Minecraft Server IP 192.168.1.40\" \n"
+ EDhApiServerFolderNameMode.NAME_IP_PORT + ": Example: \"Minecraft Server IP 192.168.1.40:25565\""
+ EDhApiServerFolderNameMode.NAME_IP_PORT_MC_VERSION + ": Example: \"Minecraft Server IP 192.168.1.40:25565 GameVersion 1.16.5\"")
.build();
public static ConfigEntry<Double> multiverseSimilarityRequiredPercent = new ConfigEntry.Builder<Double>()
@@ -907,7 +913,7 @@ public class Config
ThreadPresetConfigEventHandler.getFileHandlerDefaultThreadCount(),
Runtime.getRuntime().availableProcessors())
.comment(""
+ "How many threads should be used when reading in LOD data from disk? \n"
+ "How many threads should be used when reading/writing LOD data to/from disk? \n"
+ "\n"
+ "Increasing this number will cause LODs to load in faster, \n"
+ "but may cause lag when loading a new world or when \n"
@@ -920,6 +926,31 @@ public class Config
.comment(THREAD_RUN_TIME_RATIO_NOTE)
.build();
public static final ConfigEntry<Integer> numberOfUpdatePropagatorThreads = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(1,
ThreadPresetConfigEventHandler.getUpdatePropagatorDefaultThreadCount(),
Runtime.getRuntime().availableProcessors())
.comment(""
+ "How many threads should be used when applying LOD updates? \n"
+ "An LOD update is the operation of down-sampling a high detail LOD \n"
+ "into a lower detail one.\n"
+ "\n"
+ "This config can have a much higher number of threads \n"
+ "assigned and much lower run time ratio vs other thread pools \n"
+ "because the amount of time any particular thread may run is relatively low.\n"
+ "\n"
+ "This is because LOD updating only only partially thread safe, \n"
+ "so between 40% and 60% of the time a given thread may end up \n"
+ "waiting on another thread to finish updating the same LOD it also wants\n"
+ "to work on.\n"
+ "\n"
+ THREAD_NOTE)
.build();
public static final ConfigEntry<Double> runTimeRatioForUpdatePropagatorThreads = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getUpdatePropagatorDefaultRunTimeRatio(), 1.0)
.comment(THREAD_RUN_TIME_RATIO_NOTE)
.build();
public static final ConfigEntry<Integer> numberOfLodBuilderThreads = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(1,
ThreadPresetConfigEventHandler.getLodBuilderDefaultThreadCount(),
@@ -950,27 +981,27 @@ public class Config
public static class GpuBuffers
{
public static ConfigEntry<EGpuUploadMethod> gpuUploadMethod = new ConfigEntry.Builder<EGpuUploadMethod>()
.set(EGpuUploadMethod.AUTO)
public static ConfigEntry<EDhApiGpuUploadMethod> gpuUploadMethod = new ConfigEntry.Builder<EDhApiGpuUploadMethod>()
.set(EDhApiGpuUploadMethod.AUTO)
.comment(""
+ "What method should be used to upload geometry to the GPU? \n"
+ "\n"
+ EGpuUploadMethod.AUTO + ": Picks the best option based on the GPU you have. \n"
+ EDhApiGpuUploadMethod.AUTO + ": Picks the best option based on the GPU you have. \n"
+ "\n"
+ EGpuUploadMethod.BUFFER_STORAGE + ": Default if OpenGL 4.5 is supported. \n"
+ EDhApiGpuUploadMethod.BUFFER_STORAGE + ": Default if OpenGL 4.5 is supported. \n"
+ " Fast rendering, no stuttering. \n"
+ "\n"
+ EGpuUploadMethod.SUB_DATA + ": Backup option for NVIDIA. \n"
+ EDhApiGpuUploadMethod.SUB_DATA + ": Backup option for NVIDIA. \n"
+ " Fast rendering but may stutter when uploading. \n"
+ "\n"
+ EGpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. \n"
+ EDhApiGpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. \n"
+ " Generally the best option for integrated GPUs. \n"
+ " Default option for AMD/Intel if OpenGL 4.5 isn't supported. \n"
+ " May end up storing buffers in System memory. \n"
+ " Fast rendering if in GPU memory, slow if in system memory, \n"
+ " but won't stutter when uploading. \n"
+ "\n"
+ EGpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n"
+ EDhApiGpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n"
+ " Backup option for AMD/Intel. \n"
+ " Fast rendering but may stutter when uploading. \n"
+ "\n"
@@ -1003,15 +1034,6 @@ public class Config
+ "")
.build();
// deprecated and not implemented, can be made public if we ever re-implement it
@Deprecated
private static ConfigEntry<EBufferRebuildTimes> rebuildTimes = new ConfigEntry.Builder<EBufferRebuildTimes>()
.set(EBufferRebuildTimes.NORMAL)
.comment(""
+ "How frequently should vertex buffers (geometry) be rebuilt and sent to the GPU? \n"
+ "Higher settings may cause stuttering, but will prevent holes in the world")
.build();
}
public static class AutoUpdater
@@ -1030,9 +1052,9 @@ public class Config
+ "Should Distant Horizons silently, automatically download and install new versions?")
.build();
public static ConfigEntry<EUpdateBranch> updateBranch = new ConfigEntry.Builder<EUpdateBranch>()
public static ConfigEntry<EDhApiUpdateBranch> updateBranch = new ConfigEntry.Builder<EDhApiUpdateBranch>()
.set(
ModInfo.IS_DEV_BUILD? EUpdateBranch.NIGHTLY: EUpdateBranch.STABLE // If it's already a nightly build, then download the nightly build ofc
ModInfo.IS_DEV_BUILD? EDhApiUpdateBranch.NIGHTLY: EDhApiUpdateBranch.STABLE // If it's already a nightly build, then download the nightly build ofc
)
.comment(""
+ " If DH should use the nightly (provided by Gitlab), or stable (provided by Modrinth) build")
@@ -1043,64 +1065,64 @@ public class Config
{
// TODO add change all option
// TODO default to error chat and info file
public static ConfigEntry<ELoggerMode> logWorldGenEvent = new ConfigEntry.Builder<ELoggerMode>()
.set(ELoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
public static ConfigEntry<EDhApiLoggerMode> logWorldGenEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the world generation process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<ELoggerMode> logWorldGenPerformance = new ConfigEntry.Builder<ELoggerMode>()
.set(ELoggerMode.LOG_WARNING_TO_CHAT_AND_FILE)
public static ConfigEntry<EDhApiLoggerMode> logWorldGenPerformance = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_FILE)
.comment(""
+ "If enabled, the mod will log performance about the world generation process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<ELoggerMode> logWorldGenLoadEvent = new ConfigEntry.Builder<ELoggerMode>()
.set(ELoggerMode.LOG_WARNING_TO_CHAT_AND_FILE)
public static ConfigEntry<EDhApiLoggerMode> logWorldGenLoadEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_FILE)
.comment(""
+ "If enabled, the mod will log information about the world generation process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<ELoggerMode> logLodBuilderEvent = new ConfigEntry.Builder<ELoggerMode>()
.set(ELoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
public static ConfigEntry<EDhApiLoggerMode> logLodBuilderEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the LOD generation process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<ELoggerMode> logRendererBufferEvent = new ConfigEntry.Builder<ELoggerMode>()
.set(ELoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
public static ConfigEntry<EDhApiLoggerMode> logRendererBufferEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the renderer buffer process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<ELoggerMode> logRendererGLEvent = new ConfigEntry.Builder<ELoggerMode>()
.set(ELoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
public static ConfigEntry<EDhApiLoggerMode> logRendererGLEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the renderer OpenGL process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<ELoggerMode> logFileReadWriteEvent = new ConfigEntry.Builder<ELoggerMode>()
.set(ELoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
public static ConfigEntry<EDhApiLoggerMode> logFileReadWriteEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about file read/write operations. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<ELoggerMode> logFileSubDimEvent = new ConfigEntry.Builder<ELoggerMode>()
.set(ELoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
public static ConfigEntry<EDhApiLoggerMode> logFileSubDimEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about file sub-dimension operations. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<ELoggerMode> logNetworkEvent = new ConfigEntry.Builder<ELoggerMode>()
.set(ELoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about network operations. \n"
+ "This can be useful for debugging.")
@@ -1110,25 +1132,25 @@ public class Config
public static class Debugging
{
public static ConfigEntry<ERendererMode> rendererMode = new ConfigEntry.Builder<ERendererMode>()
.set(ERendererMode.DEFAULT)
public static ConfigEntry<EDhApiRendererMode> rendererMode = new ConfigEntry.Builder<EDhApiRendererMode>()
.set(EDhApiRendererMode.DEFAULT)
.comment(""
+ "What renderer is active? \n"
+ "\n"
+ ERendererMode.DEFAULT + ": Default lod renderer \n"
+ ERendererMode.DEBUG + ": Debug testing renderer \n"
+ ERendererMode.DISABLED + ": Disable rendering")
+ EDhApiRendererMode.DEFAULT + ": Default lod renderer \n"
+ EDhApiRendererMode.DEBUG + ": Debug testing renderer \n"
+ EDhApiRendererMode.DISABLED + ": Disable rendering")
.build();
public static ConfigEntry<EDebugRendering> debugRendering = new ConfigEntry.Builder<EDebugRendering>()
.set(EDebugRendering.OFF)
public static ConfigEntry<EDhApiDebugRendering> debugRendering = new ConfigEntry.Builder<EDhApiDebugRendering>()
.set(EDhApiDebugRendering.OFF)
.comment(""
+ "Should specialized colors/rendering modes be used? \n"
+ "\n"
+ EDebugRendering.OFF + ": LODs will be drawn with their normal colors. \n"
+ EDebugRendering.SHOW_DETAIL + ": LODs' color will be based on their detail level. \n"
+ EDebugRendering.SHOW_BLOCK_MATERIAL + ": LODs' color will be based on their material. \n"
+ EDebugRendering.SHOW_OVERLAPPING_QUADS + ": LODs will be drawn with total white, but overlapping quads will be drawn with red. \n"
+ EDhApiDebugRendering.OFF + ": LODs will be drawn with their normal colors. \n"
+ EDhApiDebugRendering.SHOW_DETAIL + ": LODs' color will be based on their detail level. \n"
+ EDhApiDebugRendering.SHOW_BLOCK_MATERIAL + ": LODs' color will be based on their material. \n"
+ EDhApiDebugRendering.SHOW_OVERLAPPING_QUADS + ": LODs will be drawn with total white, but overlapping quads will be drawn with red. \n"
+ "")
.build();
@@ -1165,6 +1187,14 @@ public class Config
+ "Useful for debugging shaders")
.build();
public static ConfigEntry<Boolean> showOverlappingQuadErrors = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "If true overlapping quads will be rendered as bright red for easy identification. \n"
+ "If false the quads will be rendered normally. \n"
+ "")
.build();
// Note: This will reset on game restart, and should have a warning on the tooltip
public static ConfigEntry<Boolean> allowUnsafeValues = new ConfigEntry.Builder<Boolean>()
.set(false)
@@ -1222,19 +1252,14 @@ public class Config
.comment("Render LOD section status?")
.build();
public static ConfigEntry<Boolean> showFullDataFileStatus = new ConfigEntry.Builder<Boolean>()
public static ConfigEntry<Boolean> showQuadTreeRenderStatus = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("Render full data file status?")
.comment("Render Quad Tree Rendering status?")
.build();
public static ConfigEntry<Boolean> showFullDataFileSampling = new ConfigEntry.Builder<Boolean>()
public static ConfigEntry<Boolean> showFullDataUpdateStatus = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("Render full data file sampling progress?")
.build();
public static ConfigEntry<Boolean> showRenderDataFileStatus = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("Render render data file status?")
.comment("Render full data update/lock status?")
.build();
}
@@ -1254,15 +1279,15 @@ public class Config
+ "")
.build();
public static ConfigEntry<EGLErrorHandlingMode> glErrorHandlingMode = new ConfigEntry.Builder<EGLErrorHandlingMode>()
.set(ModInfo.IS_DEV_BUILD ? EGLErrorHandlingMode.LOG : EGLErrorHandlingMode.IGNORE)
public static ConfigEntry<EDhApiGLErrorHandlingMode> glErrorHandlingMode = new ConfigEntry.Builder<EDhApiGLErrorHandlingMode>()
.set(ModInfo.IS_DEV_BUILD ? EDhApiGLErrorHandlingMode.LOG : EDhApiGLErrorHandlingMode.IGNORE)
.comment(""
+ "Defines how OpenGL errors are handled. \n"
+ "May incorrectly catch OpenGL errors thrown by other mods. \n"
+ "\n"
+ EGLErrorHandlingMode.IGNORE + ": Do nothing. \n"
+ EGLErrorHandlingMode.LOG + ": write an error to the log. \n"
+ EGLErrorHandlingMode.LOG_THROW + ": write to the log and throw an exception. \n"
+ EDhApiGLErrorHandlingMode.IGNORE + ": Do nothing. \n"
+ EDhApiGLErrorHandlingMode.LOG + ": write an error to the log. \n"
+ EDhApiGLErrorHandlingMode.LOG_THROW + ": write to the log and throw an exception. \n"
+ " Warning: this should only be enabled when debugging the LOD renderer \n"
+ " as it may break Minecraft's renderer when an exception is thrown. \n"
+ "")
@@ -1295,14 +1320,14 @@ public class Config
"")
.build();
public static ConfigEntry<EGlProfileMode> glProfileMode = new ConfigEntry.Builder<EGlProfileMode>()
.set(EGlProfileMode.CORE)
public static ConfigEntry<EDhApiGlProfileMode> glProfileMode = new ConfigEntry.Builder<EDhApiGlProfileMode>()
.set(EDhApiGlProfileMode.CORE)
.comment("" +
"Can be changed if you experience crashing when loading into a world.\n" +
"\n" +
"Defines the OpenGL context type Distant Horizon's will create. \n" +
"Generally this should be left as ["+EGlProfileMode.CORE+"] unless there is an issue with your GPU driver. \n" +
"Possible values: ["+ StringUtil.join("],[", EGlProfileMode.values())+"] \n" +
"Generally this should be left as ["+ EDhApiGlProfileMode.CORE+"] unless there is an issue with your GPU driver. \n" +
"Possible values: ["+ StringUtil.join("],[", EDhApiGlProfileMode.values())+"] \n" +
"")
.build();
public static ConfigEntry<Boolean> enableGlForwardCompatibilityMode = new ConfigEntry.Builder<Boolean>()
@@ -1381,10 +1406,20 @@ public class Config
.set(new HashMap<String, String>())
.build();
public static ConfigUIButton uiButtonTest = new ConfigUIButton(() -> { new Thread(() -> {
System.setProperty("java.awt.headless", "false"); // Required to make it work
JOptionPane.showMessageDialog(null, "Button pressed!", "UITester dialog", JOptionPane.INFORMATION_MESSAGE);
});});
public static ConfigUIButton uiButtonTest = new ConfigUIButton(() ->
{
new Thread(() ->
{
if (!GraphicsEnvironment.isHeadless())
{
JOptionPane.showMessageDialog(null, "Button pressed!", "UITester dialog", JOptionPane.INFORMATION_MESSAGE);
}
else
{
LOGGER.info("button pressed!");
}
});
});
public static ConfigCategory categoryTest = new ConfigCategory.Builder().set(CategoryTest.class).build();
@@ -1440,23 +1475,12 @@ public class Config
{
complicatedListenerSetupComplete = true;
try
{
// listener can only be added after the config has finished initializing
Client.Advanced.Graphics.AdvancedGraphics.overdrawPreventionPreset.addListener(OverdrawPreventionPresetConfigEventHandler.INSTANCE);
}
catch (Exception e)
{
LOGGER.error("Unexpected exception when running config delayed listener setup. Error: [" + e.getMessage() + "].", e);
}
try
{
// TODO automatically get all instances of AbstractPresetConfigEventHandler and fire "setUiOnlyConfigValues"
ThreadPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
OverdrawPreventionPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
RenderCacheConfigEventHandler.getInstance();
}
catch (Exception e)
@@ -20,24 +20,8 @@
package com.seibel.distanthorizons.core.config.eventHandlers;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.EHorizontalQuality;
import com.seibel.distanthorizons.api.enums.config.EMaxHorizontalResolution;
import com.seibel.distanthorizons.api.enums.config.EVerticalQuality;
import com.seibel.distanthorizons.api.enums.config.quickOptions.EQualityPreset;
import com.seibel.distanthorizons.api.enums.rendering.ETransparency;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.ConfigEntryWithPresetOptions;
import com.seibel.distanthorizons.core.config.eventHandlers.presets.AbstractPresetConfigEventHandler;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConfigEntry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
public class DebugColumnConfigEventHandler implements IConfigListener
{
@@ -19,7 +19,7 @@
package com.seibel.distanthorizons.core.config.eventHandlers;
import com.seibel.distanthorizons.api.enums.rendering.ERendererMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.Config;
@@ -28,15 +28,15 @@ public class QuickRenderToggleConfigEventHandler
public static QuickRenderToggleConfigEventHandler INSTANCE = new QuickRenderToggleConfigEventHandler();
private final ConfigChangeListener<Boolean> quickRenderChangeListener;
private final ConfigChangeListener<ERendererMode> rendererModeChangeListener;
private final ConfigChangeListener<EDhApiRendererMode> rendererModeChangeListener;
/** private since we only ever need one handler at a time */
private QuickRenderToggleConfigEventHandler()
{
this.quickRenderChangeListener = new ConfigChangeListener<>(Config.Client.quickEnableRendering, (val) -> { Config.Client.Advanced.Debugging.rendererMode.set(Config.Client.quickEnableRendering.get() ? ERendererMode.DEFAULT : ERendererMode.DISABLED); });
this.rendererModeChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Debugging.rendererMode, (val) -> { Config.Client.quickEnableRendering.set(Config.Client.Advanced.Debugging.rendererMode.get() != ERendererMode.DISABLED); });
this.quickRenderChangeListener = new ConfigChangeListener<>(Config.Client.quickEnableRendering, (val) -> { Config.Client.Advanced.Debugging.rendererMode.set(Config.Client.quickEnableRendering.get() ? EDhApiRendererMode.DEFAULT : EDhApiRendererMode.DISABLED); });
this.rendererModeChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Debugging.rendererMode, (val) -> { Config.Client.quickEnableRendering.set(Config.Client.Advanced.Debugging.rendererMode.get() != EDhApiRendererMode.DISABLED); });
}
/**
@@ -45,7 +45,7 @@ public class QuickRenderToggleConfigEventHandler
*/
public void setUiOnlyConfigValues()
{
Config.Client.quickEnableRendering.set(Config.Client.Advanced.Debugging.rendererMode.get() != ERendererMode.DISABLED);
Config.Client.quickEnableRendering.set(Config.Client.Advanced.Debugging.rendererMode.get() != EDhApiRendererMode.DISABLED);
}
}
@@ -20,13 +20,10 @@
package com.seibel.distanthorizons.core.config.eventHandlers;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.EBlocksToAvoid;
import com.seibel.distanthorizons.api.enums.config.ELodShading;
import com.seibel.distanthorizons.api.enums.config.EMaxHorizontalResolution;
import com.seibel.distanthorizons.api.enums.config.EVerticalQuality;
import com.seibel.distanthorizons.api.enums.rendering.ETransparency;
import com.seibel.distanthorizons.api.enums.config.*;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiTransparency;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.util.TimerUtil;
@@ -45,15 +42,18 @@ public class RenderCacheConfigEventHandler
// previous values used to check if a watched setting was actually modified
private final ConfigChangeListener<EMaxHorizontalResolution> horizontalResolutionChangeListener;
private final ConfigChangeListener<EVerticalQuality> verticalQualityChangeListener;
private final ConfigChangeListener<ETransparency> transparencyChangeListener;
private final ConfigChangeListener<EBlocksToAvoid> blocksToIgnoreChangeListener;
private final ConfigChangeListener<EDhApiMaxHorizontalResolution> horizontalResolutionChangeListener;
private final ConfigChangeListener<EDhApiVerticalQuality> verticalQualityChangeListener;
private final ConfigChangeListener<EDhApiTransparency> transparencyChangeListener;
private final ConfigChangeListener<EDhApiBlocksToAvoid> blocksToIgnoreChangeListener;
private final ConfigChangeListener<Boolean> tintWithAvoidedBlocksChangeListener;
private final ConfigChangeListener<Double> brightnessMultiplierChangeListener;
private final ConfigChangeListener<Double> saturationMultiplierChangeListener;
private final ConfigChangeListener<ELodShading> lodShadingChangeListener;
private final ConfigChangeListener<EDhApiLodShading> lodShadingChangeListener;
private final ConfigChangeListener<EDhApiGrassSideRendering> grassSideChangeListener;
private final ConfigChangeListener<EDhApiDebugRendering> debugRenderingChangeListener;
/** how long to wait in milliseconds before applying the config changes */
private static final long TIMEOUT_IN_MS = 4_000L;
@@ -83,6 +83,9 @@ public class RenderCacheConfigEventHandler
this.brightnessMultiplierChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.AdvancedGraphics.brightnessMultiplier, (newValue) -> this.refreshRenderDataAfterTimeout());
this.saturationMultiplierChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.AdvancedGraphics.saturationMultiplier, (newValue) -> this.refreshRenderDataAfterTimeout());
this.lodShadingChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.AdvancedGraphics.lodShading, (newValue) -> this.refreshRenderDataAfterTimeout());
this.grassSideChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.AdvancedGraphics.grassSideRendering, (newValue) -> this.refreshRenderDataAfterTimeout());
this.debugRenderingChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Debugging.debugRendering, (newValue) -> this.refreshRenderDataAfterTimeout());
}
@@ -1,86 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.config.eventHandlers.presets;
import com.seibel.distanthorizons.api.enums.config.EMaxHorizontalResolution;
import com.seibel.distanthorizons.api.enums.config.EOverdrawPrevention;
import com.seibel.distanthorizons.api.enums.config.quickOptions.EQualityPreset;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.ConfigEntryWithPresetOptions;
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 java.util.Arrays;
import java.util.HashMap;
import java.util.List;
public class OverdrawPreventionPresetConfigEventHandler extends AbstractPresetConfigEventHandler<EOverdrawPrevention>
{
public static final OverdrawPreventionPresetConfigEventHandler INSTANCE = new OverdrawPreventionPresetConfigEventHandler();
private static final Logger LOGGER = LogManager.getLogger();
private final ConfigEntryWithPresetOptions<EOverdrawPrevention, Double> overdrawPrevention = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention,
new HashMap<EOverdrawPrevention, Double>()
{{
this.put(EOverdrawPrevention.HEAVY, 0.6);
this.put(EOverdrawPrevention.MEDIUM, 0.4);
this.put(EOverdrawPrevention.LIGHT, 0.25);
this.put(EOverdrawPrevention.NONE, 0.0);
}});
//==============//
// constructors //
//==============//
/** private since we only ever need one handler at a time */
private OverdrawPreventionPresetConfigEventHandler()
{
// add each config used by this preset
this.configList.add(this.overdrawPrevention);
for (ConfigEntryWithPresetOptions<EOverdrawPrevention, ?> 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(); });
}
}
//==============//
// enum getters //
//==============//
@Override
protected IConfigEntry<EOverdrawPrevention> getPresetConfigEntry() { return Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPreventionPreset; }
@Override
protected List<EOverdrawPrevention> getPresetEnumList() { return Arrays.asList(EOverdrawPrevention.values()); }
@Override
protected EOverdrawPrevention getCustomPresetEnum() { return EOverdrawPrevention.CUSTOM; }
}
@@ -19,11 +19,11 @@
package com.seibel.distanthorizons.core.config.eventHandlers.presets;
import com.seibel.distanthorizons.api.enums.config.EHorizontalQuality;
import com.seibel.distanthorizons.api.enums.config.EMaxHorizontalResolution;
import com.seibel.distanthorizons.api.enums.config.EVerticalQuality;
import com.seibel.distanthorizons.api.enums.config.quickOptions.EQualityPreset;
import com.seibel.distanthorizons.api.enums.rendering.ETransparency;
import com.seibel.distanthorizons.api.enums.config.EDhApiHorizontalQuality;
import com.seibel.distanthorizons.api.enums.config.EDhApiMaxHorizontalResolution;
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.listeners.ConfigChangeListener;
@@ -33,57 +33,57 @@ import org.apache.logging.log4j.Logger;
import java.util.*;
public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigEventHandler<EQualityPreset>
public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigEventHandler<EDhApiQualityPreset>
{
public static final RenderQualityPresetConfigEventHandler INSTANCE = new RenderQualityPresetConfigEventHandler();
private static final Logger LOGGER = LogManager.getLogger();
private final ConfigEntryWithPresetOptions<EQualityPreset, EMaxHorizontalResolution> drawResolution = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.maxHorizontalResolution,
new HashMap<EQualityPreset, EMaxHorizontalResolution>()
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiMaxHorizontalResolution> drawResolution = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.maxHorizontalResolution,
new HashMap<EDhApiQualityPreset, EDhApiMaxHorizontalResolution>()
{{
this.put(EQualityPreset.MINIMUM, EMaxHorizontalResolution.TWO_BLOCKS);
this.put(EQualityPreset.LOW, EMaxHorizontalResolution.BLOCK);
this.put(EQualityPreset.MEDIUM, EMaxHorizontalResolution.BLOCK);
this.put(EQualityPreset.HIGH, EMaxHorizontalResolution.BLOCK);
this.put(EQualityPreset.EXTREME, EMaxHorizontalResolution.BLOCK);
this.put(EDhApiQualityPreset.MINIMUM, EDhApiMaxHorizontalResolution.TWO_BLOCKS);
this.put(EDhApiQualityPreset.LOW, EDhApiMaxHorizontalResolution.BLOCK);
this.put(EDhApiQualityPreset.MEDIUM, EDhApiMaxHorizontalResolution.BLOCK);
this.put(EDhApiQualityPreset.HIGH, EDhApiMaxHorizontalResolution.BLOCK);
this.put(EDhApiQualityPreset.EXTREME, EDhApiMaxHorizontalResolution.BLOCK);
}});
private final ConfigEntryWithPresetOptions<EQualityPreset, EVerticalQuality> verticalQuality = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.verticalQuality,
new HashMap<EQualityPreset, EVerticalQuality>()
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiVerticalQuality> verticalQuality = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.verticalQuality,
new HashMap<EDhApiQualityPreset, EDhApiVerticalQuality>()
{{
this.put(EQualityPreset.MINIMUM, EVerticalQuality.HEIGHT_MAP);
this.put(EQualityPreset.LOW, EVerticalQuality.LOW);
this.put(EQualityPreset.MEDIUM, EVerticalQuality.MEDIUM);
this.put(EQualityPreset.HIGH, EVerticalQuality.HIGH);
this.put(EQualityPreset.EXTREME, EVerticalQuality.EXTREME);
this.put(EDhApiQualityPreset.MINIMUM, EDhApiVerticalQuality.HEIGHT_MAP);
this.put(EDhApiQualityPreset.LOW, EDhApiVerticalQuality.LOW);
this.put(EDhApiQualityPreset.MEDIUM, EDhApiVerticalQuality.MEDIUM);
this.put(EDhApiQualityPreset.HIGH, EDhApiVerticalQuality.HIGH);
this.put(EDhApiQualityPreset.EXTREME, EDhApiVerticalQuality.EXTREME);
}});
private final ConfigEntryWithPresetOptions<EQualityPreset, EHorizontalQuality> horizontalQuality = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.horizontalQuality,
new HashMap<EQualityPreset, EHorizontalQuality>()
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiHorizontalQuality> horizontalQuality = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.horizontalQuality,
new HashMap<EDhApiQualityPreset, EDhApiHorizontalQuality>()
{{
this.put(EQualityPreset.MINIMUM, EHorizontalQuality.LOWEST);
this.put(EQualityPreset.LOW, EHorizontalQuality.LOW);
this.put(EQualityPreset.MEDIUM, EHorizontalQuality.MEDIUM);
this.put(EQualityPreset.HIGH, EHorizontalQuality.HIGH);
this.put(EQualityPreset.EXTREME, EHorizontalQuality.EXTREME);
this.put(EDhApiQualityPreset.MINIMUM, EDhApiHorizontalQuality.LOWEST);
this.put(EDhApiQualityPreset.LOW, EDhApiHorizontalQuality.LOW);
this.put(EDhApiQualityPreset.MEDIUM, EDhApiHorizontalQuality.MEDIUM);
this.put(EDhApiQualityPreset.HIGH, EDhApiHorizontalQuality.HIGH);
this.put(EDhApiQualityPreset.EXTREME, EDhApiHorizontalQuality.EXTREME);
}});
private final ConfigEntryWithPresetOptions<EQualityPreset, ETransparency> transparency = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.transparency,
new HashMap<EQualityPreset, ETransparency>()
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, EDhApiTransparency> transparency = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Quality.transparency,
new HashMap<EDhApiQualityPreset, EDhApiTransparency>()
{{
this.put(EQualityPreset.MINIMUM, ETransparency.DISABLED);
this.put(EQualityPreset.LOW, ETransparency.DISABLED); // should be fake if/when fake is fixed
this.put(EQualityPreset.MEDIUM, ETransparency.COMPLETE);
this.put(EQualityPreset.HIGH, ETransparency.COMPLETE);
this.put(EQualityPreset.EXTREME, ETransparency.COMPLETE);
this.put(EDhApiQualityPreset.MINIMUM, EDhApiTransparency.DISABLED);
this.put(EDhApiQualityPreset.LOW, EDhApiTransparency.DISABLED); // should be fake if/when fake is fixed
this.put(EDhApiQualityPreset.MEDIUM, EDhApiTransparency.COMPLETE);
this.put(EDhApiQualityPreset.HIGH, EDhApiTransparency.COMPLETE);
this.put(EDhApiQualityPreset.EXTREME, EDhApiTransparency.COMPLETE);
}});
private final ConfigEntryWithPresetOptions<EQualityPreset, Boolean> ssaoEnabled = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Ssao.enabled,
new HashMap<EQualityPreset, Boolean>()
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, Boolean> ssaoEnabled = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Ssao.enabled,
new HashMap<EDhApiQualityPreset, Boolean>()
{{
this.put(EQualityPreset.MINIMUM, false);
this.put(EQualityPreset.LOW, false);
this.put(EQualityPreset.MEDIUM, true);
this.put(EQualityPreset.HIGH, true);
this.put(EQualityPreset.EXTREME, true);
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);
}});
@@ -103,7 +103,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.configList.add(this.ssaoEnabled);
for (ConfigEntryWithPresetOptions<EQualityPreset, ?> config : this.configList)
for (ConfigEntryWithPresetOptions<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(); });
@@ -117,11 +117,11 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
//==============//
@Override
protected IConfigEntry<EQualityPreset> getPresetConfigEntry() { return Config.Client.qualityPresetSetting; }
protected IConfigEntry<EDhApiQualityPreset> getPresetConfigEntry() { return Config.Client.qualityPresetSetting; }
@Override
protected List<EQualityPreset> getPresetEnumList() { return Arrays.asList(EQualityPreset.values()); }
protected List<EDhApiQualityPreset> getPresetEnumList() { return Arrays.asList(EDhApiQualityPreset.values()); }
@Override
protected EQualityPreset getCustomPresetEnum() { return EQualityPreset.CUSTOM; }
protected EDhApiQualityPreset getCustomPresetEnum() { return EDhApiQualityPreset.CUSTOM; }
}
@@ -19,7 +19,7 @@
package com.seibel.distanthorizons.core.config.eventHandlers.presets;
import com.seibel.distanthorizons.api.enums.config.quickOptions.EThreadPreset;
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;
@@ -32,7 +32,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHandler<EThreadPreset>
public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHandler<EDhApiThreadPreset>
{
public static final ThreadPresetConfigEventHandler INSTANCE = new ThreadPresetConfigEventHandler();
@@ -42,68 +42,90 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
public static int getWorldGenDefaultThreadCount() { return getThreadCountByPercent(0.15); }
private final ConfigEntryWithPresetOptions<EThreadPreset, Integer> worldGenThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads,
new HashMap<EThreadPreset, Integer>()
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> worldGenThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads,
new HashMap<EDhApiThreadPreset, Integer>()
{{
this.put(EThreadPreset.MINIMAL_IMPACT, 1);
this.put(EThreadPreset.LOW_IMPACT, getWorldGenDefaultThreadCount());
this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.25));
this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.5));
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1);
this.put(EDhApiThreadPreset.LOW_IMPACT, getWorldGenDefaultThreadCount());
this.put(EDhApiThreadPreset.BALANCED, getThreadCountByPercent(0.25));
this.put(EDhApiThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.5));
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
}});
public static double getWorldGenDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.5 : 0.75; }
private final ConfigEntryWithPresetOptions<EThreadPreset, Double> worldGenRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads,
new HashMap<EThreadPreset, Double>()
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> worldGenRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads,
new HashMap<EDhApiThreadPreset, Double>()
{{
this.put(EThreadPreset.MINIMAL_IMPACT, LOW_THREAD_COUNT_CPU ? 0.1 : 0.25);
this.put(EThreadPreset.LOW_IMPACT, getWorldGenDefaultRunTimeRatio());
this.put(EThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.5 : 0.75);
this.put(EThreadPreset.AGGRESSIVE, LOW_THREAD_COUNT_CPU ? 0.75 : 1.0);
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, LOW_THREAD_COUNT_CPU ? 0.1 : 0.25);
this.put(EDhApiThreadPreset.LOW_IMPACT, getWorldGenDefaultRunTimeRatio());
this.put(EDhApiThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.5 : 0.75);
this.put(EDhApiThreadPreset.AGGRESSIVE, LOW_THREAD_COUNT_CPU ? 0.75 : 1.0);
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
}});
public static int getFileHandlerDefaultThreadCount() { return getThreadCountByPercent(0.1); }
private final ConfigEntryWithPresetOptions<EThreadPreset, Integer> fileHandlerThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads,
new HashMap<EThreadPreset, Integer>()
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> fileHandlerThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfFileHandlerThreads,
new HashMap<EDhApiThreadPreset, Integer>()
{{
this.put(EThreadPreset.MINIMAL_IMPACT, 1);
this.put(EThreadPreset.LOW_IMPACT, getFileHandlerDefaultThreadCount());
this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.2));
this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.2));
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1);
this.put(EDhApiThreadPreset.LOW_IMPACT, getFileHandlerDefaultThreadCount());
this.put(EDhApiThreadPreset.BALANCED, getThreadCountByPercent(0.2));
this.put(EDhApiThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.2));
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
}});
public static double getFileHandlerDefaultRunTimeRatio() { return 0.5; }
private final ConfigEntryWithPresetOptions<EThreadPreset, Double> fileHandlerRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForFileHandlerThreads,
new HashMap<EThreadPreset, Double>()
public static double getFileHandlerDefaultRunTimeRatio() { return 0.75; }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> fileHandlerRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForFileHandlerThreads,
new HashMap<EDhApiThreadPreset, Double>()
{{
this.put(EThreadPreset.MINIMAL_IMPACT, 0.25);
this.put(EThreadPreset.LOW_IMPACT, getFileHandlerDefaultRunTimeRatio());
this.put(EThreadPreset.BALANCED, 0.75);
this.put(EThreadPreset.AGGRESSIVE, 1.0);
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.50);
this.put(EDhApiThreadPreset.LOW_IMPACT, getFileHandlerDefaultRunTimeRatio());
this.put(EDhApiThreadPreset.BALANCED, 1.0);
this.put(EDhApiThreadPreset.AGGRESSIVE, 1.0);
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
}});
public static int getUpdatePropagatorDefaultThreadCount() { return getThreadCountByPercent(0.25); }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> UpdatePropagatorThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfUpdatePropagatorThreads,
new HashMap<EDhApiThreadPreset, Integer>()
{{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1);
this.put(EDhApiThreadPreset.LOW_IMPACT, getUpdatePropagatorDefaultThreadCount());
this.put(EDhApiThreadPreset.BALANCED, getThreadCountByPercent(0.5));
this.put(EDhApiThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.75));
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
}});
public static double getUpdatePropagatorDefaultRunTimeRatio() { return 0.5; }
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> UpdatePropagatorRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForUpdatePropagatorThreads,
new HashMap<EDhApiThreadPreset, Double>()
{{
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.25);
this.put(EDhApiThreadPreset.LOW_IMPACT, getUpdatePropagatorDefaultRunTimeRatio());
this.put(EDhApiThreadPreset.BALANCED, 0.75);
this.put(EDhApiThreadPreset.AGGRESSIVE, 1.0);
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
}});
public static int getLodBuilderDefaultThreadCount() { return getThreadCountByPercent(0.1); }
private final ConfigEntryWithPresetOptions<EThreadPreset, Integer> lodBuilderThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads,
new HashMap<EThreadPreset, Integer>()
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> lodBuilderThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads,
new HashMap<EDhApiThreadPreset, Integer>()
{{
this.put(EThreadPreset.MINIMAL_IMPACT, 1);
this.put(EThreadPreset.LOW_IMPACT, getLodBuilderDefaultThreadCount());
this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.2));
this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.4));
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1);
this.put(EDhApiThreadPreset.LOW_IMPACT, getLodBuilderDefaultThreadCount());
this.put(EDhApiThreadPreset.BALANCED, getThreadCountByPercent(0.2));
this.put(EDhApiThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.4));
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
}});
public static double getLodBuilderDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.25 : 0.5; }
private final ConfigEntryWithPresetOptions<EThreadPreset, Double> lodBuilderRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForLodBuilderThreads,
new HashMap<EThreadPreset, Double>()
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> lodBuilderRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForLodBuilderThreads,
new HashMap<EDhApiThreadPreset, Double>()
{{
this.put(EThreadPreset.MINIMAL_IMPACT, 0.1);
this.put(EThreadPreset.LOW_IMPACT, getLodBuilderDefaultRunTimeRatio());
this.put(EThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.5 : 0.75);
this.put(EThreadPreset.AGGRESSIVE, 1.0);
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.1);
this.put(EDhApiThreadPreset.LOW_IMPACT, getLodBuilderDefaultRunTimeRatio());
this.put(EDhApiThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.5 : 0.75);
this.put(EDhApiThreadPreset.AGGRESSIVE, 1.0);
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
}});
@@ -122,11 +144,14 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
this.configList.add(this.fileHandlerThreadCount);
this.configList.add(this.fileHandlerRunTime);
this.configList.add(this.UpdatePropagatorThreadCount);
this.configList.add(this.UpdatePropagatorRunTime);
this.configList.add(this.lodBuilderThreadCount);
this.configList.add(this.lodBuilderRunTime);
for (ConfigEntryWithPresetOptions<EThreadPreset, ?> config : this.configList)
for (ConfigEntryWithPresetOptions<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(); });
@@ -171,11 +196,11 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
//==============//
@Override
protected IConfigEntry<EThreadPreset> getPresetConfigEntry() { return Config.Client.threadPresetSetting; }
protected IConfigEntry<EDhApiThreadPreset> getPresetConfigEntry() { return Config.Client.threadPresetSetting; }
@Override
protected List<EThreadPreset> getPresetEnumList() { return Arrays.asList(EThreadPreset.values()); }
protected List<EDhApiThreadPreset> getPresetEnumList() { return Arrays.asList(EDhApiThreadPreset.values()); }
@Override
protected EThreadPreset getCustomPresetEnum() { return EThreadPreset.CUSTOM; }
protected EDhApiThreadPreset getCustomPresetEnum() { return EDhApiThreadPreset.CUSTOM; }
}
@@ -37,7 +37,8 @@ public class JavaScreenHandlerScreen extends AbstractScreen
static
{
// Required to run this
// 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");
}
@@ -19,7 +19,7 @@
package com.seibel.distanthorizons.core.config.gui;
import com.seibel.distanthorizons.api.enums.config.EGpuUploadMethod;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.GLState;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
@@ -76,7 +76,7 @@ public class OpenGLConfigScreen extends AbstractScreen
buffer.rewind();
GLVertexBuffer vbo = new GLVertexBuffer(false);
vbo.bind();
vbo.uploadBuffer(buffer, 4, EGpuUploadMethod.DATA, vertices.length * Float.BYTES);
vbo.uploadBuffer(buffer, 4, EDhApiGpuUploadMethod.DATA, vertices.length * Float.BYTES);
return vbo;
}
@@ -1,159 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.SingleColumnFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
public class FullDataDownSampler
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public static CompletableFuture<IFullDataSource> createDownSamplingFuture(DhSectionPos newTarget, IFullDataSourceProvider provider)
{
// TODO: Make this future somehow run with lowest priority (to ensure ram usage stays low)
return createDownSamplingFuture(CompleteFullDataSource.createEmpty(newTarget), provider);
}
public static CompletableFuture<IFullDataSource> createDownSamplingFuture(CompleteFullDataSource target, IFullDataSourceProvider provider)
{
int sectionSizeNeeded = 1 << target.getDataDetailLevel();
ArrayList<CompletableFuture<IFullDataSource>> futures;
DhLodPos basePos = target.getSectionPos().getSectionBBoxPos().getCornerLodPos(CompleteFullDataSource.SECTION_SIZE_OFFSET);
if (sectionSizeNeeded <= CompleteFullDataSource.SECTION_SIZE_OFFSET)
{
futures = new ArrayList<>(sectionSizeNeeded * sectionSizeNeeded);
for (int xOffset = 0; xOffset < sectionSizeNeeded; xOffset++)
{
for (int zOffset = 0; zOffset < sectionSizeNeeded; zOffset++)
{
CompletableFuture<IFullDataSource> future = provider.getAsync(new DhSectionPos(
CompleteFullDataSource.SECTION_SIZE_OFFSET, basePos.x + xOffset, basePos.z + zOffset));
future = future.whenComplete((source, ex) -> {
if (ex == null && source != null && source instanceof CompleteFullDataSource)
{
downSample(target, (CompleteFullDataSource) source);
}
else if (ex != null)
{
LOGGER.error("Error while down sampling", ex);
}
});
futures.add(future);
}
}
}
else
{
futures = new ArrayList<>(CompleteFullDataSource.WIDTH * CompleteFullDataSource.WIDTH);
int multiplier = sectionSizeNeeded / CompleteFullDataSource.WIDTH;
for (int xOffset = 0; xOffset < CompleteFullDataSource.WIDTH; xOffset++)
{
for (int zOffset = 0; zOffset < CompleteFullDataSource.WIDTH; zOffset++)
{
CompletableFuture<IFullDataSource> future = provider.getAsync(new DhSectionPos(
CompleteFullDataSource.SECTION_SIZE_OFFSET, basePos.x + xOffset * multiplier, basePos.z + zOffset * multiplier));
future = future.whenComplete((source, ex) -> {
if (ex == null && source != null && source instanceof CompleteFullDataSource)
{
downSample(target, (CompleteFullDataSource) source);
}
else if (ex != null)
{
LOGGER.error("Error while down sampling", ex);
}
});
futures.add(future);
}
}
}
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenApply(v -> target);
}
public static void downSample(CompleteFullDataSource target, CompleteFullDataSource source)
{
LodUtil.assertTrue(target.getSectionPos().overlapsExactly(source.getSectionPos()));
LodUtil.assertTrue(target.getDataDetailLevel() > source.getDataDetailLevel());
byte detailDiff = (byte) (target.getDataDetailLevel() - source.getDataDetailLevel());
DhSectionPos trgPos = target.getSectionPos();
DhSectionPos srcPos = source.getSectionPos();
if (detailDiff >= CompleteFullDataSource.SECTION_SIZE_OFFSET)
{
// The source occupies only 1 datapoint in the target
// FIXME: TEMP method for down-sampling: take only the corner column
int sourceSectionPerTargetData = 1 << (detailDiff - CompleteFullDataSource.SECTION_SIZE_OFFSET);
if (srcPos.getX() % sourceSectionPerTargetData != 0 || srcPos.getZ() % sourceSectionPerTargetData != 0)
{
return;
}
DhLodPos trgOffset = trgPos.getMinCornerLodPos(target.getDataDetailLevel());
DhLodPos srcOffset = srcPos.getSectionBBoxPos().convertToDetailLevel(target.getDataDetailLevel());
int offsetX = trgOffset.x - srcOffset.x;
int offsetZ = trgOffset.z - srcOffset.z;
LodUtil.assertTrue(offsetX >= 0 && offsetX < CompleteFullDataSource.WIDTH
&& offsetZ >= 0 && offsetZ < CompleteFullDataSource.WIDTH);
target.markNotEmpty();
source.get(0, 0).deepCopyTo(target.get(offsetX, offsetZ));
}
else if (detailDiff > 0)
{
// The source occupies multiple data-points in the target
int srcDataPerTrgData = 1 << detailDiff;
int overlappedTrgDataSize = CompleteFullDataSource.WIDTH / srcDataPerTrgData;
DhLodPos trgOffset = trgPos.getMinCornerLodPos(target.getDataDetailLevel());
DhLodPos srcOffset = srcPos.getSectionBBoxPos().getCornerLodPos(target.getDataDetailLevel());
int offsetX = trgOffset.x - srcOffset.x;
int offsetZ = trgOffset.z - srcOffset.z;
LodUtil.assertTrue(offsetX >= 0 && offsetX < CompleteFullDataSource.WIDTH
&& offsetZ >= 0 && offsetZ < CompleteFullDataSource.WIDTH);
target.markNotEmpty();
for (int ox = 0; ox < overlappedTrgDataSize; ox++)
{
for (int oz = 0; oz < overlappedTrgDataSize; oz++)
{
SingleColumnFullDataAccessor column = target.get(ox + offsetX, oz + offsetZ);
column.downsampleFrom(source.subView(srcDataPerTrgData, ox * srcDataPerTrgData, oz * srcDataPerTrgData));
}
}
}
else
{
LodUtil.assertNotReach();
}
}
}
@@ -21,25 +21,33 @@ package com.seibel.distanthorizons.core.dataObjects.fullData;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* WARNING: This is not THREAD-SAFE!
* <p>
* Used to map a numerical IDs to a Biome/BlockState pair.
*
* WARNING: This is not THREAD-SAFE! <br><br>
*
* Used to map a numerical IDs to a Biome/BlockState pair. <br><br>
*
* TODO the serializing of this map might be really big
* since it stringifies every block and biome name, which is quite bulky.
* It might be worth while to have a biome and block ID that then both get mapped
* to the data point ID to reduce file size.
* And/or it would be good to dynamically remove IDs that aren't currently in use.
*
* @author Leetom
*/
public class FullDataPointIdMap
@@ -55,12 +63,11 @@ public class FullDataPointIdMap
private static final String BLOCK_STATE_SEPARATOR_STRING = "_DH-BSW_";
// FIXME: Improve performance maybe?
/** used when the data point map is running normally */
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/** should only be used for debugging */
private DhSectionPos pos;
private long pos;
/** The index should be the same as the Entry's ID */
private final ArrayList<Entry> entryList = new ArrayList<>();
@@ -72,7 +79,7 @@ public class FullDataPointIdMap
// constructor //
//=============//
public FullDataPointIdMap(DhSectionPos pos) { this.pos = pos; }
public FullDataPointIdMap(long pos) { this.pos = pos; }
@@ -112,8 +119,11 @@ public class FullDataPointIdMap
/** @return -1 if the list is empty */
public int getMaxValidId() { return this.entryList.size() - 1; }
public int size() { return this.entryList.size(); }
public DhSectionPos getPos() { return this.pos; }
public boolean isEmpty() { return this.entryList.isEmpty(); }
public long getPos() { return this.pos; }
@@ -125,7 +135,7 @@ public class FullDataPointIdMap
* If an entry with the given values already exists nothing will
* be added but the existing item's ID will still be returned.
*/
public int addIfNotPresentAndGetId(IBiomeWrapper biome, IBlockStateWrapper blockState) { return this.addIfNotPresentAndGetId(new Entry(biome, blockState), true); }
public int addIfNotPresentAndGetId(IBiomeWrapper biome, IBlockStateWrapper blockState) { return this.addIfNotPresentAndGetId(Entry.getEntry(biome, blockState), true); }
/** @param useWriteLocks should only be false if this method is already in a write lock to prevent unlocking at the wrong time */
private int addIfNotPresentAndGetId(Entry biomeBlockStateEntry, boolean useWriteLocks)
{
@@ -162,22 +172,84 @@ public class FullDataPointIdMap
}
}
/**
* Adds each entry from the given map to this map.
*
* @return an array of each added entry's ID in this map in order
*/
public int[] mergeAndReturnRemappedEntityIds(FullDataPointIdMap target)
/** allows for adding duplicate {@link Entry} */
private void add(Entry biomeBlockStateEntry, boolean useWriteLocks)
{
try
{
LOGGER.trace("merging {" + this.pos + ", " + this.entryList.size() + "} and {" + target.pos + ", " + target.entryList.size() + "}");
if (useWriteLocks)
{
this.readWriteLock.writeLock().lock();
}
target.readWriteLock.readLock().lock();
int id = this.entryList.size();
this.entryList.add(biomeBlockStateEntry);
this.idMap.put(biomeBlockStateEntry, id);
}
finally
{
if (useWriteLocks)
{
this.readWriteLock.writeLock().unlock();
}
}
}
/**
* Adds every {@link Entry} from inputMap into this map. <br>
* Allows duplicate entries. <br><br>
*
* Allowing duplicate entries should be done if a datasource is just being read in and
* a merge step isn't being done afterwards. If duplicates are removed it may cause
* the ID's to get out of sync since everything will be shifted down after the removed
* ID(s).
*/
public void addAll(FullDataPointIdMap inputMap)
{
try
{
//LOGGER.trace("adding {" + this.pos + ", " + this.entryList.size() + "} and {" + inputMap.pos + ", " + inputMap.entryList.size() + "}");
inputMap.readWriteLock.readLock().lock();
this.readWriteLock.writeLock().lock();
ArrayList<Entry> entriesToMerge = target.entryList;
ArrayList<Entry> entriesToMerge = inputMap.entryList;
for (int i = 0; i < entriesToMerge.size(); i++)
{
Entry entity = entriesToMerge.get(i);
this.add(entity, false);
}
}
finally
{
this.readWriteLock.writeLock().unlock();
inputMap.readWriteLock.readLock().unlock();
//LOGGER.trace("finished merging {" + this.pos + ", " + this.entryList.size() + "} and {" + inputMap.pos + ", " + inputMap.entryList.size() + "}");
}
}
/**
* Adds each entry from the given map to this map. <br><br>
*
* Note: when using this function be careful about re-mapping the
* same data source multiple times.
* Doing so may cause indexOutOfBounds issues.
*
* @return an array of each added entry's ID in this map in order
*/
public int[] mergeAndReturnRemappedEntityIds(FullDataPointIdMap inputMap)
{
try
{
//LOGGER.trace("merging {" + this.pos + ", " + this.entryList.size() + "} and {" + inputMap.pos + ", " + inputMap.entryList.size() + "}");
inputMap.readWriteLock.readLock().lock();
this.readWriteLock.writeLock().lock();
ArrayList<Entry> entriesToMerge = inputMap.entryList;
int[] remappedEntryIds = new int[entriesToMerge.size()];
for (int i = 0; i < entriesToMerge.size(); i++)
{
@@ -191,14 +263,14 @@ public class FullDataPointIdMap
finally
{
this.readWriteLock.writeLock().unlock();
target.readWriteLock.readLock().unlock();
inputMap.readWriteLock.readLock().unlock();
LOGGER.trace("finished merging {" + this.pos + ", " + this.entryList.size() + "} and {" + target.pos + ", " + target.entryList.size() + "}");
//LOGGER.trace("finished merging {" + this.pos + ", " + this.entryList.size() + "} and {" + inputMap.pos + ", " + inputMap.entryList.size() + "}");
}
}
/** Should only be used if this map is going to be reused, otherwise bad things will happen. */
public void clear(DhSectionPos pos)
public void clear(long pos)
{
this.pos = pos;
this.entryList.clear();
@@ -244,14 +316,19 @@ public class FullDataPointIdMap
finally
{
this.readWriteLock.readLock().unlock();
LOGGER.trace("serialize " + this.pos + " " + this.entryList.size());
//LOGGER.trace("serialize " + this.pos + " " + this.entryList.size());
}
}
/** Creates a new IdBiomeBlockStateMap from the given UTF formatted stream */
public static FullDataPointIdMap deserialize(DhDataInputStream inputStream, DhSectionPos pos, ILevelWrapper levelWrapper) throws IOException, InterruptedException
public static FullDataPointIdMap deserialize(DhDataInputStream inputStream, long pos, ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{
int entityCount = inputStream.readInt();
if (entityCount < 0)
{
throw new DataCorruptedException("FullDataPointIdMap deserialize entry count should have a number greater than or equal to 0, returned value ["+entityCount+"].");
}
// only used when debugging
HashMap<String, FullDataPointIdMap.Entry> dataPointEntryBySerialization = new HashMap<>();
@@ -259,6 +336,13 @@ public class FullDataPointIdMap
FullDataPointIdMap newMap = new FullDataPointIdMap(pos);
for (int i = 0; i < entityCount; i++)
{
// necessary to prevent issues with deserializing objects after the level has been closed
if (Thread.interrupted())
{
throw new InterruptedException(FullDataPointIdMap.class.getSimpleName() + " task interrupted.");
}
String entryString = inputStream.readUTF();
Entry newEntry = Entry.deserialize(entryString, levelWrapper);
newMap.entryList.add(newEntry);
@@ -277,7 +361,11 @@ public class FullDataPointIdMap
}
}
LOGGER.trace("deserialized " + pos + " " + newMap.entryList.size() + "-" + entityCount);
if (newMap.size() != entityCount)
{
// if the mappings are out of sync then the LODs will render incorrectly due to IDs being wrong
LodUtil.assertNotReach("ID maps failed to deserialize for pos: ["+ DhSectionPos.toString(pos)+"], incorrect entity count. Expected count ["+entityCount+"], actual count ["+newMap.size()+"]");
}
return newMap;
}
@@ -312,15 +400,81 @@ public class FullDataPointIdMap
{
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final Int2ReferenceOpenHashMap<ArrayList<Entry>> ENTRY_POOL = new Int2ReferenceOpenHashMap<>();
/** lock is necessary since {@link Int2ReferenceOpenHashMap} isn't concurrent and concurrent threads can cause infinite loops */
private static final ReentrantReadWriteLock ENTRY_POOL_LOCK = new ReentrantReadWriteLock();
public final IBiomeWrapper biome;
public final IBlockStateWrapper blockState;
private Integer hashCode = null;
// constructor //
public Entry(IBiomeWrapper biome, IBlockStateWrapper blockState)
//=============//
// constructor //
//=============//
public static Entry getEntry(IBiomeWrapper biome, IBlockStateWrapper blockState)
{
int entryHash = getHashCode(biome, blockState);
// try getting the existing entry
try
{
ENTRY_POOL_LOCK.readLock().lock();
// check if an entry already exists
ArrayList<Entry> entryList = ENTRY_POOL.get(entryHash);
if (entryList != null)
{
// at least one entry exists with this hash code
for (int i = 0; i < entryList.size(); i++)
{
Entry entry = entryList.get(i);
if (entry.biome.equals(biome) && entry.blockState.equals(blockState))
{
return entry;
}
}
// if we got here, then there was a hash collision and this entry wasn't present in the array
}
}
finally
{
ENTRY_POOL_LOCK.readLock().unlock();
}
// no entry exists,
// create a new one
try
{
ENTRY_POOL_LOCK.writeLock().lock();
ArrayList<Entry> entryList = ENTRY_POOL.get(entryHash);
if (entryList == null)
{
// no entries exist for this hash code
// we assume that hash collisions should basically never happen,
// so the array starts with an initial capacity of 1.
// However, since collisions will eventually happen, using an arrayList prevents unexpected bugs caused by collisions.
entryList = new ArrayList<>(1);
ENTRY_POOL.put(entryHash, entryList);
}
Entry newEntry = new Entry(biome, blockState);
entryList.add(newEntry);
return newEntry;
}
finally
{
ENTRY_POOL_LOCK.writeLock().unlock();
}
}
private Entry(IBiomeWrapper biome, IBlockStateWrapper blockState)
{
this.biome = biome;
this.blockState = blockState;
@@ -328,15 +482,29 @@ public class FullDataPointIdMap
// methods //
//===========//
// overrides //
//===========//
public static int getHashCode(Entry entry) { return getHashCode(entry.biome, entry.blockState); }
public static int getHashCode(IBiomeWrapper biome, IBlockStateWrapper blockState)
{
final int prime = 31;
int result = 1;
// the biome and blockstate hashcode should be already calculated by the time
// we get here, so this operation should be very fast
result = prime * result + (biome == null ? 0 : biome.hashCode());
result = prime * result + (blockState == null ? 0 : blockState.hashCode());
return result;
}
@Override
public int hashCode()
{
// cache the hash code to improve speed
if (this.hashCode == null)
{
this.hashCode = this.serialize().hashCode();
this.hashCode = getHashCode(this);
}
return this.hashCode;
@@ -361,25 +529,23 @@ public class FullDataPointIdMap
//=================//
// (de)serializing //
//=================//
public String serialize() { return this.biome.getSerialString() + BLOCK_STATE_SEPARATOR_STRING + this.blockState.getSerialString(); }
public static Entry deserialize(String str, ILevelWrapper levelWrapper) throws IOException, InterruptedException
public static Entry deserialize(String str, ILevelWrapper levelWrapper) throws IOException, DataCorruptedException
{
String[] stringArray = str.split(BLOCK_STATE_SEPARATOR_STRING);
if (stringArray.length != 2)
{
throw new IOException("Failed to deserialize BiomeBlockStateEntry");
throw new DataCorruptedException("Failed to deserialize BiomeBlockStateEntry");
}
// necessary to prevent issues with deserializing objects after the level has been closed
if (Thread.interrupted())
{
throw new InterruptedException(FullDataPointIdMap.class.getSimpleName() + " task interrupted.");
}
IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapper(stringArray[0], levelWrapper);
IBlockStateWrapper blockState = WRAPPER_FACTORY.deserializeBlockStateWrapper(stringArray[1], levelWrapper);
return new Entry(biome, blockState);
IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapperOrGetDefault(stringArray[0], levelWrapper);
IBlockStateWrapper blockState = WRAPPER_FACTORY.deserializeBlockStateWrapperOrGetDefault(stringArray[1], levelWrapper);
return Entry.getEntry(biome, blockState);
}
}
@@ -1,79 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.accessor;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
/**
* A more specific version of {@link FullDataArrayAccessor}
* that only contains full data for a single chunk.
*
* @see FullDataPointUtil
*/
public class ChunkSizedFullDataAccessor extends FullDataArrayAccessor
{
public final DhChunkPos chunkPos;
public final DhSectionPos sectionPos;
// TODO replace this var with LodUtil.BLOCK_DETAIL_LEVEL
public final byte detailLevel = LodUtil.BLOCK_DETAIL_LEVEL;
public ChunkSizedFullDataAccessor(DhChunkPos chunkPos)
{
super(new FullDataPointIdMap(new DhSectionPos(chunkPos)),
new long[LodUtil.CHUNK_WIDTH * LodUtil.CHUNK_WIDTH][0],
LodUtil.CHUNK_WIDTH);
this.chunkPos = chunkPos;
// TODO the fact this is using a LodUtil detail level instead of the DhSectionPos detail level may cause confusion and trouble down the line
this.sectionPos = new DhSectionPos(LodUtil.CHUNK_DETAIL_LEVEL, this.chunkPos.x, this.chunkPos.z);
}
public void setSingleColumn(long[] data, int xRelative, int zRelative) { this.dataArrays[xRelative * LodUtil.CHUNK_WIDTH + zRelative] = data; }
public long nonEmptyCount()
{
long count = 0;
for (long[] data : this.dataArrays)
{
if (data.length != 0)
{
count += 1;
}
}
return count;
}
public long emptyCount() { return (LodUtil.CHUNK_WIDTH * LodUtil.CHUNK_WIDTH) - this.nonEmptyCount(); }
public DhSectionPos getSectionPos() { return this.sectionPos; }
@Override
public String toString() { return this.chunkPos + " " + this.nonEmptyCount(); }
}
@@ -1,189 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.accessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.LowDetailIncompleteFullDataSource;
/**
* Contains Full Data points and basic methods for getting and setting them. <br>
* Can be used standalone or as the base for Full data sources.
*
* @see CompleteFullDataSource
* @see LowDetailIncompleteFullDataSource
*/
public class FullDataArrayAccessor implements IFullDataAccessor
{
protected final FullDataPointIdMap mapping;
/** A flattened 2D array (for the X and Z directions) containing an array for the Y direction. */
protected final long[][] dataArrays;
/** measured in data points */
protected final int width;
/** measured in data points */
protected final int dataWidth;
/** index offset used when getting/setting data in {@link FullDataArrayAccessor#dataArrays}. */
protected final int offset;
//==============//
// constructors //
//==============//
public FullDataArrayAccessor(FullDataPointIdMap mapping, long[][] dataArrays, int width)
{
if (dataArrays.length != width * width)
{
throw new IllegalArgumentException("tried constructing dataArrayView with invalid input!");
}
this.dataArrays = dataArrays;
this.width = width;
this.dataWidth = width;
this.mapping = mapping;
this.offset = 0;
}
public FullDataArrayAccessor(FullDataArrayAccessor source, int width, int offsetX, int offsetZ)
{
if (source.width < width || source.width < width + offsetX || source.width < width + offsetZ)
{
throw new IllegalArgumentException("tried constructing dataArrayView subview with invalid input!");
}
this.dataArrays = source.dataArrays;
this.width = width;
this.dataWidth = source.dataWidth;
this.mapping = source.mapping;
this.offset = source.offset + offsetX * this.dataWidth + offsetZ;
}
//=========//
// methods //
//=========//
@Override
public FullDataArrayAccessor subView(int width, int xOffset, int zOffset) { return new FullDataArrayAccessor(this, width, xOffset, zOffset); }
/** WARNING: This will potentially share the underlying array object! */
public void shadowCopyTo(FullDataArrayAccessor target)
{
if (target.width != this.width)
{
throw new IllegalArgumentException("Target view must have same size as this view");
}
if (target.mapping.equals(this.mapping))
{
for (int x = 0; x < this.width; x++)
{
System.arraycopy(this.dataArrays, this.offset + x * this.dataWidth,
target.dataArrays, target.offset + x * target.dataWidth, this.width);
}
}
else
{
int[] remappedIds = target.mapping.mergeAndReturnRemappedEntityIds(this.mapping);
for (int x = 0; x < this.width; x++)
{
for (int z = 0; z < this.width; z++)
{
long[] currentData = this.dataArrays[this.offset + x * this.dataWidth + z];
// may be null if no data exists for this column yet
if (currentData != null)
{
long[] newData = new long[currentData.length]; // TODO what to do if null?
for (int dataPointIndex = 0; dataPointIndex < newData.length; dataPointIndex++)
{
newData[dataPointIndex] = FullDataPointUtil.remap(remappedIds, currentData[dataPointIndex]);
}
target.dataArrays[target.offset + x * target.dataWidth + z] = newData;
}
}
}
}
}
/**
* Takes a higher detail {@link FullDataArrayAccessor}'s and converts the data to a lower detail level.
*
* @param incomingFullDataAccessor must be larger than this {@link FullDataArrayAccessor} and its width must a power of two larger (example: this.width = 4, other.width = 8)
*/
public void downsampleFrom(FullDataArrayAccessor incomingFullDataAccessor)
{
// validate that the incoming data isn't smaller than this accessor
LodUtil.assertTrue(incomingFullDataAccessor.width >= this.width && incomingFullDataAccessor.width % this.width == 0);
int dataPointsPerWidthUnit = incomingFullDataAccessor.width / this.width;
for (int xOffset = 0; xOffset < this.width; xOffset++)
{
for (int zOffset = 0; zOffset < this.width; zOffset++)
{
FullDataArrayAccessor subView = incomingFullDataAccessor.subView(dataPointsPerWidthUnit,
xOffset * dataPointsPerWidthUnit,
zOffset * dataPointsPerWidthUnit);
SingleColumnFullDataAccessor column = this.get(xOffset, zOffset);
column.downsampleFrom(subView);
}
}
}
//=========//
// getters //
//=========//
@Override
public FullDataPointIdMap getMapping() { return this.mapping; }
@Override
public SingleColumnFullDataAccessor get(int index) { return this.get(index / this.width, index % this.width); }
@Override
public SingleColumnFullDataAccessor get(int relativeX, int relativeZ)
{
int dataArrayIndex = (relativeX * this.width) + relativeZ + this.offset;
if (dataArrayIndex >= this.dataArrays.length)
{
LodUtil.assertNotReach(
"FullDataArrayAccessor.get() called with a relative position that is outside the data source. \n" +
"source width: [" + this.width + "] source offset: [" + this.offset + "]\n" +
"given relative pos X: [" + relativeX + "] Z: [" + relativeZ + "]\n" +
"dataArrays.length: [" + this.dataArrays.length + "] dataArrayIndex: [" + dataArrayIndex + "].");
}
return new SingleColumnFullDataAccessor(this.mapping, this.dataArrays, dataArrayIndex);
}
@Override
public int width() { return this.width; }
}
@@ -1,77 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.accessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import java.util.Iterator;
/**
* Contains raw full data points, which must be interpreted by the {@link FullDataPointUtil}. <br>
* Often used by {@link IFullDataSource}'s.
*
* @see IFullDataSource
* @see FullDataArrayAccessor
* @see FullDataPointUtil
*/
public interface IFullDataAccessor
{
FullDataPointIdMap getMapping();
/** generally used for iterating through the whole data set */
SingleColumnFullDataAccessor get(int index);
SingleColumnFullDataAccessor get(int relativeX, int relativeZ);
/** measured in full data points */
int width();
/**
* Creates a new {@link IFullDataAccessor} with the given width and starting at the given X and Z offsets. <br>
* The returned object will use the same underlining data structure (IE memory addresses) as the source {@link IFullDataAccessor}.
*/
IFullDataAccessor subView(int width, int xOffset, int zOffset);
/** Returns an iterator that goes over each data column */
default Iterator<SingleColumnFullDataAccessor> iterator()
{
return new Iterator<SingleColumnFullDataAccessor>()
{
private int index = 0;
private final int size = width() * width();
@Override
public boolean hasNext() { return this.index < this.size; }
@Override
public SingleColumnFullDataAccessor next()
{
LodUtil.assertTrue(this.hasNext(), "No more data to iterate!");
return get(this.index++);
}
};
}
}
@@ -1,182 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.accessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
/**
* Represents a single column of Full LOD data.
*
* @see FullDataPointUtil
*/
public class SingleColumnFullDataAccessor implements IFullDataAccessor
{
/**
* A flattened 2D array (for the X and Z directions) containing an array for the Y direction.
* TODO the flattened array is probably to reduce garbage collection overhead, but is doing it this way worth while? Having a 3D array would be much easier to understand
*
* @see FullDataArrayAccessor#dataArrays
*/
private final long[][] dataArrays;
/** indicates what index of the {@link SingleColumnFullDataAccessor#dataArrays} is used by this accessor */
private final int dataArrayIndex;
private final FullDataPointIdMap mapping;
//=============//
// constructor //
//=============//
public SingleColumnFullDataAccessor(FullDataPointIdMap mapping, long[][] dataArrays, int dataArrayIndex)
{
this.dataArrays = dataArrays;
this.dataArrayIndex = dataArrayIndex;
this.mapping = mapping;
LodUtil.assertTrue(this.dataArrayIndex < this.dataArrays.length, "dataArrays.length [" + this.dataArrays.length + "] is less than the dataArrayIndex [" + this.dataArrayIndex + "].");
}
//=========//
// methods //
//=========//
/** @return true if any data exists in this column. */
public boolean doesColumnExist()
{
long[] dataColumn = this.dataArrays[this.dataArrayIndex];
return dataColumn != null && dataColumn.length != 0;
}
@Override
public FullDataPointIdMap getMapping() { return this.mapping; }
@Override
public SingleColumnFullDataAccessor get(int index)
{
if (index != 0)
{
throw new IllegalArgumentException("Only contains 1 column of full data!");
}
return this;
}
@Override
public SingleColumnFullDataAccessor get(int relativeX, int relativeZ)
{
if (relativeX != 0 || relativeZ != 0)
{
throw new IllegalArgumentException("Only contains 1 column of full data!");
}
return this;
}
/** @return the entire array of raw full data points. */
public long[] getRaw() { return this.dataArrays[this.dataArrayIndex]; }
public long getSingle(int yIndex) { return this.dataArrays[this.dataArrayIndex][yIndex]; }
public void setSingle(int yIndex, long fullDataPoint) { this.dataArrays[this.dataArrayIndex][yIndex] = fullDataPoint; }
public void setNew(long[] newArray) { this.dataArrays[this.dataArrayIndex] = newArray; }
/** @return how many data points are in this column */
public int getSingleLength()
{
long[] singleDataArray = this.dataArrays[this.dataArrayIndex];
return singleDataArray != null ? singleDataArray.length : 0;
}
@Override
public int width() { return 1; }
@Override
public IFullDataAccessor subView(int width, int xOffset, int zOffset)
{
if (width != 1 || xOffset != 1 || zOffset != 1)
{
throw new IllegalArgumentException("Getting invalid range of subView from SingleColumnFullDataAccessor!");
}
return this;
}
/** WARNING: This may potentially share the underlying array objects! */
public void shadowCopyTo(SingleColumnFullDataAccessor target)
{
if (target.mapping.equals(this.mapping))
{
target.dataArrays[target.dataArrayIndex] = this.dataArrays[this.dataArrayIndex];
}
else
{
int[] remappedEntryIds = target.mapping.mergeAndReturnRemappedEntityIds(this.mapping);
long[] sourceData = this.dataArrays[this.dataArrayIndex];
long[] newData = new long[sourceData.length];
for (int i = 0; i < newData.length; i++)
{
newData[i] = FullDataPointUtil.remap(remappedEntryIds, sourceData[i]);
}
target.dataArrays[target.dataArrayIndex] = newData;
}
}
/** Copies both ID data and mapping data. */
public void deepCopyTo(SingleColumnFullDataAccessor target)
{
if (target.mapping.equals(this.mapping))
{
System.arraycopy(this.dataArrays[this.dataArrayIndex], 0, target.dataArrays[target.dataArrayIndex], 0, this.dataArrays[this.dataArrayIndex].length);
}
else
{
int[] remappedEntryIds = target.mapping.mergeAndReturnRemappedEntityIds(this.mapping);
long[] sourceData = this.dataArrays[this.dataArrayIndex];
// FIXME sourceData.length != 0 may not be a good solution and may end up breaking issues down the line, but fixes exceptions being fired here
if (sourceData != null && sourceData.length != 0)
{
long[] newData = new long[sourceData.length];
for (int i = 0; i < newData.length; i++)
{
newData[i] = FullDataPointUtil.remap(remappedEntryIds, sourceData[i]);
}
target.dataArrays[target.dataArrayIndex] = newData;
}
}
}
/**
* Replaces this column's data with data from the input {@link IFullDataAccessor}. <br>
* This is used to convert higher detail LOD data to lower detail LOD data.
*/
public void downsampleFrom(IFullDataAccessor source)
{
//TODO: average the data instead of just picking the first column
SingleColumnFullDataAccessor firstColumn = source.get(0);
firstColumn.deepCopyTo(this);
}
}
@@ -1,209 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.loader;
import com.google.common.collect.HashMultimap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.DataSourceDto;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
public abstract class AbstractFullDataSourceLoader
{
public static final HashMultimap<Class<? extends IFullDataSource>, AbstractFullDataSourceLoader> LOADER_REGISTRY = HashMultimap.create();
public static final HashMap<String, Class<? extends IFullDataSource>> DATATYPE_REGISTRY = new HashMap<>();
public final Class<? extends IFullDataSource> fullDataSourceClass;
public final String datatype;
public final byte[] loaderSupportedVersions;
/** used when pooling data sources */
private final ArrayList<IFullDataSource> cachedSources = new ArrayList<>();
private final ReentrantLock cacheLock = new ReentrantLock();
//=============//
// constructor //
//=============//
public AbstractFullDataSourceLoader(Class<? extends IFullDataSource> fullDataSourceClass, String datatype, byte[] loaderSupportedVersions)
{
this.datatype = datatype;
this.loaderSupportedVersions = loaderSupportedVersions;
Arrays.sort(loaderSupportedVersions); // sort to allow fast access
this.fullDataSourceClass = fullDataSourceClass;
if (DATATYPE_REGISTRY.containsKey(datatype) && DATATYPE_REGISTRY.get(datatype) != fullDataSourceClass)
{
throw new IllegalArgumentException("Loader for datatype: [" + datatype + "] already registered with different class: "
+ DATATYPE_REGISTRY.get(datatype) + " != " + fullDataSourceClass);
}
Set<AbstractFullDataSourceLoader> loaders = LOADER_REGISTRY.get(fullDataSourceClass);
if (loaders.stream().anyMatch(other ->
{
// see if any loaderSupportsVersion conflicts with this one
for (byte otherVer : other.loaderSupportedVersions)
{
if (Arrays.binarySearch(loaderSupportedVersions, otherVer) >= 0)
{
return true;
}
}
return false;
}))
{
throw new IllegalArgumentException("Loader for class " + fullDataSourceClass + " that supports one of the version in "
+ Arrays.toString(loaderSupportedVersions) + " already registered!");
}
DATATYPE_REGISTRY.put(datatype, fullDataSourceClass);
LOADER_REGISTRY.put(fullDataSourceClass, this);
}
//================//
// loader getters //
//================//
public static AbstractFullDataSourceLoader getLoader(String dataType, byte dataVersion)
{
return LOADER_REGISTRY.get(DATATYPE_REGISTRY.get(dataType)).stream()
.filter(loader -> Arrays.binarySearch(loader.loaderSupportedVersions, dataVersion) >= 0)
.findFirst().orElse(null);
}
public static AbstractFullDataSourceLoader getLoader(Class<? extends IFullDataSource> clazz, byte dataVersion)
{
return LOADER_REGISTRY.get(clazz).stream()
.filter(loader -> Arrays.binarySearch(loader.loaderSupportedVersions, dataVersion) >= 0)
.findFirst().orElse(null);
}
//==================//
// abstract methods //
//==================//
protected abstract IFullDataSource createEmptyDataSource(DhSectionPos pos);
//==============//
// data loading //
//==============//
/** Should be used in conjunction with {@link AbstractFullDataSourceLoader#returnPooledDataSource} to return the pooled sources. */
public IFullDataSource loadTemporaryDataSource(DataSourceDto dto, IDhLevel level) throws IOException, InterruptedException
{
IFullDataSource dataSource = this.tryGetPooledSource();
if (dataSource != null)
{
dataSource.repopulateFromStream(dto, dto.getInputStream(), level);
}
else
{
dataSource = this.loadDataSource(dto, level);
}
return dataSource;
}
/**
* Can return null if any of the requirements aren't met.
*
* @throws InterruptedException if the loader thread is interrupted, generally happens when the level is shutting down
*/
public IFullDataSource loadDataSource(DataSourceDto dto, IDhLevel level) throws IOException, InterruptedException
{
IFullDataSource dataSource = this.createEmptyDataSource(dto.pos);
dataSource.populateFromStream(dto, dto.getInputStream(), level);
return dataSource;
}
//=====================//
// data source pooling //
//=====================//
/** @return null if no pooled source exists */
public IFullDataSource tryGetPooledSource()
{
try
{
this.cacheLock.lock();
int index = this.cachedSources.size() - 1;
if (index == -1)
{
return null;
}
else
{
return this.cachedSources.remove(index);
}
}
finally
{
this.cacheLock.unlock();
}
}
/**
* Doesn't have to be called, if a data source isn't returned, nothing will be leaked.
* It just means a new source must be constructed next time {@link AbstractFullDataSourceLoader#tryGetPooledSource} is called.
*/
public void returnPooledDataSource(IFullDataSource dataSource)
{
if (dataSource == null)
{
return;
}
else if (dataSource.getClass() != this.fullDataSourceClass)
{
return;
}
else if (this.cachedSources.size() > 25)
{
return;
}
try
{
this.cacheLock.lock();
this.cachedSources.add(dataSource);
}
finally
{
this.cacheLock.unlock();
}
}
}
@@ -1,33 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.loader;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
public class CompleteFullDataSourceLoader extends AbstractFullDataSourceLoader
{
public CompleteFullDataSourceLoader() { super(CompleteFullDataSource.class, CompleteFullDataSource.DATA_TYPE_NAME, new byte[]{CompleteFullDataSource.DATA_FORMAT_VERSION}); }
@Override
protected IFullDataSource createEmptyDataSource(DhSectionPos pos) { return CompleteFullDataSource.createEmpty(pos); }
}
@@ -1,33 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.loader;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.HighDetailIncompleteFullDataSource;
public class HighDetailIncompleteFullDataSourceLoader extends AbstractFullDataSourceLoader
{
public HighDetailIncompleteFullDataSourceLoader() { super(HighDetailIncompleteFullDataSource.class, HighDetailIncompleteFullDataSource.DATA_TYPE_NAME, new byte[]{HighDetailIncompleteFullDataSource.DATA_FORMAT_VERSION}); }
@Override
protected IFullDataSource createEmptyDataSource(DhSectionPos pos) { return HighDetailIncompleteFullDataSource.createEmpty(pos); }
}
@@ -1,33 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.loader;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.LowDetailIncompleteFullDataSource;
public class LowDetailIncompleteFullDataSourceLoader extends AbstractFullDataSourceLoader
{
public LowDetailIncompleteFullDataSourceLoader() { super(LowDetailIncompleteFullDataSource.class, LowDetailIncompleteFullDataSource.DATA_TYPE_NAME, new byte[]{LowDetailIncompleteFullDataSource.DATA_FORMAT_VERSION}); }
@Override
protected IFullDataSource createEmptyDataSource(DhSectionPos pos) { return LowDetailIncompleteFullDataSource.createEmpty(pos); }
}
@@ -1,447 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.sources;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.FullDataArrayAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.SingleColumnFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IStreamableFullDataSource;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.DataSourceDto;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.util.Arrays;
/**
* This data source contains every datapoint over its given {@link DhSectionPos}.
*
* @see FullDataPointUtil
* @see LowDetailIncompleteFullDataSource
* @see HighDetailIncompleteFullDataSource
*/
public class CompleteFullDataSource extends FullDataArrayAccessor implements IFullDataSource, IStreamableFullDataSource<IStreamableFullDataSource.FullDataSourceSummaryData, long[][]>
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public static final byte SECTION_SIZE_OFFSET = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
/** measured in dataPoints */
public static final int WIDTH = BitShiftUtil.powerOfTwo(SECTION_SIZE_OFFSET);
public static final byte DATA_FORMAT_VERSION = 3;
public static final String DATA_TYPE_NAME = "CompleteFullDataSource";
@Override
public String getDataTypeName() { return DATA_TYPE_NAME; }
private DhSectionPos sectionPos;
private boolean isEmpty = true;
public EDhApiWorldGenerationStep worldGenStep = EDhApiWorldGenerationStep.EMPTY;
//==============//
// constructors //
//==============//
public static CompleteFullDataSource createEmpty(DhSectionPos pos) { return new CompleteFullDataSource(pos); }
private CompleteFullDataSource(DhSectionPos sectionPos)
{
super(new FullDataPointIdMap(sectionPos), new long[WIDTH * WIDTH][0], WIDTH);
this.sectionPos = sectionPos;
}
public CompleteFullDataSource(DhSectionPos pos, FullDataPointIdMap mapping, long[][] data)
{
super(mapping, data, WIDTH);
LodUtil.assertTrue(data.length == WIDTH * WIDTH);
this.sectionPos = pos;
this.isEmpty = false;
}
//=================//
// stream handling //
//=================//
@Override
public void writeSourceSummaryInfo(IDhLevel level, DhDataOutputStream outputStream) throws IOException
{
outputStream.writeInt(this.getDataDetailLevel());
outputStream.writeInt(this.width);
outputStream.writeInt(level.getMinY());
outputStream.writeByte(this.worldGenStep.value);
}
@Override
public FullDataSourceSummaryData readSourceSummaryInfo(DataSourceDto dto, DhDataInputStream inputStream, IDhLevel level) throws IOException
{
int dataDetail = inputStream.readInt();
if (dataDetail != dto.dataDetailLevel)
{
throw new IOException(LodUtil.formatLog("Data level mismatch. Expected: ["+dto.dataDetailLevel+"], found ["+dataDetail+"]."));
}
int width = inputStream.readInt();
if (width != WIDTH)
{
throw new IOException(LodUtil.formatLog("Section width mismatch: " + width + " != " + WIDTH + " (Currently only 1 section width is supported)"));
}
int minY = inputStream.readInt();
if (minY != level.getMinY())
{
LOGGER.warn("Data minY mismatch: " + minY + " != " + level.getMinY() + ". Will ignore data's y level");
}
byte worldGenByte = inputStream.readByte();
EDhApiWorldGenerationStep worldGenStep = EDhApiWorldGenerationStep.fromValue(worldGenByte);
if (worldGenStep == null)
{
worldGenStep = EDhApiWorldGenerationStep.SURFACE;
LOGGER.warn("Missing WorldGenStep, defaulting to: " + worldGenStep.name());
}
return new FullDataSourceSummaryData(width, worldGenStep);
}
public void setSourceSummaryData(FullDataSourceSummaryData summaryData)
{
this.worldGenStep = summaryData.worldGenStep;
}
@Override
public boolean writeDataPoints(DhDataOutputStream outputStream) throws IOException
{
if (this.isEmpty())
{
outputStream.writeInt(IFullDataSource.NO_DATA_FLAG_BYTE);
return false;
}
outputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE);
// Data array length
for (int x = 0; x < this.width; x++)
{
for (int z = 0; z < this.width; z++)
{
outputStream.writeInt(this.get(x, z).getSingleLength());
}
}
// Data array content (only on non-empty columns)
outputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE);
for (int x = 0; x < this.width; x++)
{
for (int z = 0; z < this.width; z++)
{
SingleColumnFullDataAccessor columnAccessor = this.get(x, z);
if (columnAccessor.doesColumnExist())
{
long[] dataPointArray = columnAccessor.getRaw();
for (long dataPoint : dataPointArray)
{
outputStream.writeLong(dataPoint);
}
}
}
}
return true;
}
@Override
public long[][] readDataPoints(DataSourceDto dto, int width, DhDataInputStream dataInputStream) throws IOException
{
// Data array length
int dataPresentFlag = dataInputStream.readInt();
if (dataPresentFlag == IFullDataSource.NO_DATA_FLAG_BYTE)
{
// Section is empty
return null;
}
else if (dataPresentFlag != IFullDataSource.DATA_GUARD_BYTE)
{
throw new IOException("Invalid file format. Data Points guard byte expected: (no data) [" + IFullDataSource.NO_DATA_FLAG_BYTE + "] or (data present) [" + IFullDataSource.DATA_GUARD_BYTE + "], but found [" + dataPresentFlag + "].");
}
long[][] dataPointArrays;
if (this.width == width) // attempt to use the existing dataArrays if possible
{
dataPointArrays = this.dataArrays;
}
else
{
dataPointArrays = new long[width * width][];
}
for (int x = 0; x < width; x++)
{
for (int z = 0; z < width; z++)
{
int requestedArrayLength = dataInputStream.readInt();
int arrayIndex = x * width + z;
// attempt to use the existing dataArrays if possible
if (dataPointArrays[arrayIndex] == null || dataPointArrays[arrayIndex].length != requestedArrayLength)
{
dataPointArrays[arrayIndex] = new long[requestedArrayLength];
}
else
{
// clear the existing array to prevent any data leakage
Arrays.fill(dataPointArrays[arrayIndex], 0);
}
}
}
// check if the array start flag is present
int arrayStartFlag = dataInputStream.readInt();
if (arrayStartFlag != IFullDataSource.DATA_GUARD_BYTE)
{
throw new IOException("invalid data length end guard");
}
for (int xz = 0; xz < dataPointArrays.length; xz++) // x and z are combined
{
if (dataPointArrays[xz].length != 0)
{
for (int y = 0; y < dataPointArrays[xz].length; y++)
{
dataPointArrays[xz][y] = dataInputStream.readLong();
}
}
}
return dataPointArrays;
}
@Override
public void setDataPoints(long[][] dataPoints)
{
LodUtil.assertTrue(this.dataArrays.length == dataPoints.length, "Data point array length mismatch.");
this.isEmpty = false;
System.arraycopy(dataPoints, 0, this.dataArrays, 0, dataPoints.length);
}
@Override
public void writeIdMappings(DhDataOutputStream outputStream) throws IOException
{
outputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE);
this.mapping.serialize(outputStream);
}
@Override
public FullDataPointIdMap readIdMappings(long[][] dataPoints, DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException
{
int guardByte = inputStream.readInt();
if (guardByte != IFullDataSource.DATA_GUARD_BYTE)
{
throw new IOException("Invalid data content end guard for ID mapping");
}
return FullDataPointIdMap.deserialize(inputStream, this.sectionPos, levelWrapper);
}
@Override
public void setIdMapping(FullDataPointIdMap mappings) { this.mapping.mergeAndReturnRemappedEntityIds(mappings); }
//======//
// data //
//======//
@Override
public SingleColumnFullDataAccessor tryGet(int relativeX, int relativeZ) { return this.get(relativeX, relativeZ); }
@Override
public SingleColumnFullDataAccessor getOrCreate(int relativeX, int relativeZ) { return this.get(relativeX, relativeZ); }
@Override
public void update(ChunkSizedFullDataAccessor chunkDataView)
{
LodUtil.assertTrue(this.sectionPos.overlapsExactly(chunkDataView.getSectionPos()));
if (this.getDataDetailLevel() == LodUtil.BLOCK_DETAIL_LEVEL)
{
DhBlockPos2D chunkBlockPos = new DhBlockPos2D(chunkDataView.chunkPos.x * LodUtil.CHUNK_WIDTH, chunkDataView.chunkPos.z * LodUtil.CHUNK_WIDTH);
DhBlockPos2D blockOffset = chunkBlockPos.subtract(this.sectionPos.getMinCornerLodPos().getCornerBlockPos());
LodUtil.assertTrue(blockOffset.x >= 0 && blockOffset.x < WIDTH && blockOffset.z >= 0 && blockOffset.z < WIDTH);
this.isEmpty = false;
chunkDataView.shadowCopyTo(this.subView(LodUtil.CHUNK_WIDTH, blockOffset.x, blockOffset.z));
// DEBUG ASSERTION
{
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
SingleColumnFullDataAccessor column = this.get(x + blockOffset.x, z + blockOffset.z);
LodUtil.assertTrue(column.doesColumnExist());
}
}
}
}
else if (this.getDataDetailLevel() < LodUtil.CHUNK_DETAIL_LEVEL)
{
int dataPerFull = 1 << this.getDataDetailLevel();
int fullSize = LodUtil.CHUNK_WIDTH / dataPerFull;
DhLodPos dataOffset = chunkDataView.getSectionPos().getMinCornerLodPos(this.getDataDetailLevel());
DhLodPos baseOffset = this.sectionPos.getMinCornerLodPos(this.getDataDetailLevel());
int offsetX = dataOffset.x - baseOffset.x;
int offsetZ = dataOffset.z - baseOffset.z;
LodUtil.assertTrue(offsetX >= 0 && offsetX < WIDTH && offsetZ >= 0 && offsetZ < WIDTH);
this.isEmpty = false;
for (int xOffset = 0; xOffset < fullSize; xOffset++)
{
for (int zOffset = 0; zOffset < fullSize; zOffset++)
{
SingleColumnFullDataAccessor column = this.get(xOffset + offsetX, zOffset + offsetZ);
column.downsampleFrom(chunkDataView.subView(dataPerFull, xOffset * dataPerFull, zOffset * dataPerFull));
}
}
}
else if (this.getDataDetailLevel() >= LodUtil.CHUNK_DETAIL_LEVEL)
{
//FIXME: TEMPORARY
int chunkPerFull = 1 << (this.getDataDetailLevel() - LodUtil.CHUNK_DETAIL_LEVEL);
if (chunkDataView.chunkPos.x % chunkPerFull != 0 || chunkDataView.chunkPos.z % chunkPerFull != 0)
{
return;
}
DhLodPos baseOffset = this.sectionPos.getMinCornerLodPos(this.getDataDetailLevel());
DhSectionPos dataOffset = chunkDataView.getSectionPos().convertNewToDetailLevel(this.getDataDetailLevel());
int offsetX = dataOffset.getX() - baseOffset.x;
int offsetZ = dataOffset.getZ() - baseOffset.z;
LodUtil.assertTrue(offsetX >= 0 && offsetX < WIDTH && offsetZ >= 0 && offsetZ < WIDTH);
this.isEmpty = false;
chunkDataView.get(0, 0).deepCopyTo(this.get(offsetX, offsetZ));
}
else
{
LodUtil.assertNotReach();
//TODO
}
}
//================//
// helper methods //
//================//
/** Returns whether data at the given posToWrite can effect the target region file at posToTest. */
public static boolean firstDataPosCanAffectSecond(DhSectionPos posToWrite, DhSectionPos posToTest)
{
if (!posToWrite.overlapsExactly(posToTest))
{
// the testPosition is outside the writePosition
return false;
}
else if (posToTest.getDetailLevel() > posToWrite.getDetailLevel())
{
// the testPosition is larger (aka is less detailed) than the writePosition,
// more detailed sections shouldn't be updated by lower detail sections
return false;
}
else if (posToWrite.getDetailLevel() - posToTest.getDetailLevel() <= SECTION_SIZE_OFFSET)
{
// if the difference in detail levels is very large, the posToWrite
// may be skipped, due to how we sample large detail levels by only
// getting the corners.
// In this case the difference isn't very large, so return true
return true;
}
else
{
// the difference in detail levels is very large,
// check if the posToWrite is in a corner of posToTest
byte sectPerData = (byte) BitShiftUtil.powerOfTwo(posToWrite.getDetailLevel() - posToTest.getDetailLevel() - SECTION_SIZE_OFFSET);
LodUtil.assertTrue(sectPerData != 0);
return posToTest.getX() % sectPerData == 0 && posToTest.getZ() % sectPerData == 0;
}
}
//=====================//
// setters and getters //
//=====================//
@Override
public DhSectionPos getSectionPos() { return this.sectionPos; }
@Override
public void resizeDataStructuresForRepopulation(DhSectionPos pos)
{
// no data structures need to be changed, only the source's position
this.sectionPos = pos;
}
@Override
public byte getDataDetailLevel() { return (byte) (this.sectionPos.getDetailLevel() - SECTION_SIZE_OFFSET); }
@Override
public byte getDataFormatVersion() { return DATA_FORMAT_VERSION; }
@Override
public EDhApiWorldGenerationStep getWorldGenStep() { return this.worldGenStep; }
@Override
public boolean isEmpty() { return this.isEmpty; }
@Override
public void markNotEmpty() { this.isEmpty = false; }
@Override
public int getWidthInDataPoints() { return this.width; }
}
@@ -0,0 +1,410 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.sources;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.file.IDataSource;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.util.Arrays;
/**
* Formerly "CompleteFullDataSource". <br>
* Should be fully populated, containing 1 data point for each column. <br><br>
*
* Replaced by {@link FullDataSourceV2}.
*
* @see FullDataPointUtil
* @see FullDataSourceV2
*/
public class FullDataSourceV1 implements IDataSource<IDhLevel>
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public static final byte SECTION_SIZE_OFFSET = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
/** measured in dataPoints */
public static final int WIDTH = BitShiftUtil.powerOfTwo(SECTION_SIZE_OFFSET);
public static final byte DATA_FORMAT_VERSION = 3;
/** never used but should stay here. */
public static final String DATA_TYPE_NAME = "CompleteFullDataSource";
/**
* This is the byte put between different sections in the binary save file.
* The presence and absence of this byte indicates if the file is correctly formatted.
*/
private static final int DATA_GUARD_BYTE = 0xFFFFFFFF;
/** indicates the binary save file represents an empty data source */
private static final int NO_DATA_FLAG_BYTE = 0x00000001;
public final FullDataPointIdMap mapping;
public EDhApiWorldGenerationStep worldGenStep = EDhApiWorldGenerationStep.EMPTY;
/** A flattened 2D array (for the X and Z directions) containing an array for the Y direction. */
private final long[][] dataArrays;
private long sectionPos;
private boolean isEmpty = true;
//==============//
// constructors //
//==============//
public static FullDataSourceV1 createEmpty(long pos) { return new FullDataSourceV1(pos); }
private FullDataSourceV1(long sectionPos)
{
this.dataArrays = new long[WIDTH * WIDTH][0];
this.mapping = new FullDataPointIdMap(sectionPos);
this.sectionPos = sectionPos;
}
//======//
// data //
//======//
@Deprecated
@Override
public boolean update(FullDataSourceV2 dataSource, IDhLevel level) { throw new UnsupportedOperationException("Deprecated"); }
//=====================//
// setters and getters //
//=====================//
@Override
public Long getKey() { return this.sectionPos; }
@Override
public Long getPos() { return this.sectionPos; }
public void resizeDataStructuresForRepopulation(long pos)
{
// no data structures need to be changed, only the source's position
this.sectionPos = pos;
}
@Override
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.sectionPos) - SECTION_SIZE_OFFSET); }
public boolean isEmpty() { return this.isEmpty; }
public long[] get(int index) { return this.get(index / WIDTH, index % WIDTH); }
public long[] get(int relativeX, int relativeZ)
{
int dataArrayIndex = (relativeX * WIDTH) + relativeZ;
if (dataArrayIndex >= this.dataArrays.length)
{
LodUtil.assertNotReach(
"FullDataArrayAccessor.get() called with a relative position that is outside the data source. \n" +
"given relative pos X: [" + relativeX + "] Z: [" + relativeZ + "]\n" +
"dataArrays.length: [" + this.dataArrays.length + "] dataArrayIndex: [" + dataArrayIndex + "].");
}
return this.dataArrays[dataArrayIndex];
}
//=================//
// stream handling //
//=================//
/**
* Clears and then overwrites any data in this object with the data from the given file and stream.
* This is expected to be used with an existing {@link FullDataSourceV1} and can be used in place of a constructor to reuse an existing {@link FullDataSourceV1} object.
*/
public void repopulateFromStream(FullDataSourceV1DTO dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException, DataCorruptedException
{
// clear/overwrite the old data
this.resizeDataStructuresForRepopulation(dto.pos);
this.mapping.clear(dto.pos);
// set the new data
this.populateFromStream(dto, inputStream, level);
}
/**
* Overwrites any data in this object with the data from the given file and stream.
* This is expected to be used with an empty {@link FullDataSourceV1} and functions similar to a constructor.
*/
public void populateFromStream(FullDataSourceV1DTO dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException, DataCorruptedException
{
FullDataSourceSummaryData summaryData = this.readSourceSummaryInfo(dto, inputStream, level);
this.setSourceSummaryData(summaryData);
long[][] dataPoints = this.readDataPoints(summaryData.dataWidth, inputStream);
if (dataPoints == null)
{
return;
}
this.setDataPoints(dataPoints);
FullDataPointIdMap mapping = this.readIdMappings(inputStream, level.getLevelWrapper());
this.setIdMapping(mapping);
}
// low level stream methods //
/** unused, just here for reference as to how the data was written */
@Deprecated
public void writeSourceSummaryInfo(IDhLevel level, DhDataOutputStream outputStream) throws IOException
{
outputStream.writeInt(this.getDataDetailLevel());
outputStream.writeInt(WIDTH);
outputStream.writeInt(level.getMinY());
outputStream.writeByte(this.worldGenStep.value);
}
public FullDataSourceSummaryData readSourceSummaryInfo(FullDataSourceV1DTO dto, DhDataInputStream inputStream, IDhLevel level) throws IOException
{
int dataDetail = inputStream.readInt();
if (dataDetail != dto.dataDetailLevel)
{
throw new IOException(LodUtil.formatLog("Data level mismatch. Expected: ["+dto.dataDetailLevel+"], found ["+dataDetail+"]."));
}
int width = inputStream.readInt();
if (width != WIDTH)
{
throw new IOException(LodUtil.formatLog("Section width mismatch: " + width + " != " + WIDTH + " (Currently only 1 section width is supported)"));
}
int minY = inputStream.readInt();
if (minY != level.getMinY())
{
LOGGER.warn("Data minY mismatch: " + minY + " != " + level.getMinY() + ". Will ignore data's y level");
}
byte worldGenByte = inputStream.readByte();
EDhApiWorldGenerationStep worldGenStep = EDhApiWorldGenerationStep.fromValue(worldGenByte);
if (worldGenStep == null)
{
worldGenStep = EDhApiWorldGenerationStep.SURFACE;
LOGGER.warn("Missing WorldGenStep, defaulting to: " + worldGenStep.name());
}
return new FullDataSourceSummaryData(width, worldGenStep);
}
public void setSourceSummaryData(FullDataSourceSummaryData summaryData) { this.worldGenStep = summaryData.worldGenStep; }
/** unused, just here for reference as to how the data was written */
@Deprecated
public boolean writeDataPoints(DhDataOutputStream outputStream) throws IOException
{
if (this.isEmpty())
{
outputStream.writeInt(NO_DATA_FLAG_BYTE);
return false;
}
outputStream.writeInt(DATA_GUARD_BYTE);
// Data array length
for (int x = 0; x < WIDTH; x++)
{
for (int z = 0; z < WIDTH; z++)
{
outputStream.writeInt(this.get(x, z).length);
}
}
// Data array content (only on non-empty columns)
outputStream.writeInt(DATA_GUARD_BYTE);
for (int x = 0; x < WIDTH; x++)
{
for (int z = 0; z < WIDTH; z++)
{
long[] dataColumn = this.get(x, z);
if (dataColumn != null)
{
for (long dataPoint : dataColumn)
{
outputStream.writeLong(dataPoint);
}
}
}
}
return true;
}
public long[][] readDataPoints(int width, DhDataInputStream dataInputStream) throws IOException
{
// Data array length
int dataPresentFlag = dataInputStream.readInt();
if (dataPresentFlag == NO_DATA_FLAG_BYTE)
{
// Section is empty
return null;
}
else if (dataPresentFlag != DATA_GUARD_BYTE)
{
throw new IOException("Invalid file format. Data Points guard byte expected: (no data) [" + NO_DATA_FLAG_BYTE + "] or (data present) [" + DATA_GUARD_BYTE + "], but found [" + dataPresentFlag + "].");
}
long[][] dataPointArrays;
if (WIDTH == width) // attempt to use the existing dataArrays if possible
{
dataPointArrays = this.dataArrays;
}
else
{
dataPointArrays = new long[width * width][];
}
for (int x = 0; x < width; x++)
{
for (int z = 0; z < width; z++)
{
int requestedArrayLength = dataInputStream.readInt();
int arrayIndex = x * width + z;
// attempt to use the existing dataArrays if possible
if (dataPointArrays[arrayIndex] == null || dataPointArrays[arrayIndex].length != requestedArrayLength)
{
dataPointArrays[arrayIndex] = new long[requestedArrayLength];
}
else
{
// clear the existing array to prevent any data leakage
Arrays.fill(dataPointArrays[arrayIndex], 0);
}
}
}
// check if the array start flag is present
int arrayStartFlag = dataInputStream.readInt();
if (arrayStartFlag != DATA_GUARD_BYTE)
{
throw new IOException("invalid data length end guard");
}
for (int xz = 0; xz < dataPointArrays.length; xz++) // x and z are combined
{
if (dataPointArrays[xz].length != 0)
{
for (int y = 0; y < dataPointArrays[xz].length; y++)
{
dataPointArrays[xz][y] = dataInputStream.readLong();
}
}
}
return dataPointArrays;
}
public void setDataPoints(long[][] dataPoints)
{
LodUtil.assertTrue(this.dataArrays.length == dataPoints.length, "Data point array length mismatch.");
this.isEmpty = false;
System.arraycopy(dataPoints, 0, this.dataArrays, 0, dataPoints.length);
}
/** unused, just here for reference as to how the data was written */
@Deprecated
public void writeIdMappings(DhDataOutputStream outputStream) throws IOException
{
outputStream.writeInt(DATA_GUARD_BYTE);
this.mapping.serialize(outputStream);
}
public FullDataPointIdMap readIdMappings(DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{
int guardByte = inputStream.readInt();
if (guardByte != DATA_GUARD_BYTE)
{
throw new IOException("Invalid data content end guard for ID mapping");
}
return FullDataPointIdMap.deserialize(inputStream, this.sectionPos, levelWrapper);
}
public void setIdMapping(FullDataPointIdMap mappings) { this.mapping.mergeAndReturnRemappedEntityIds(mappings); }
//==================//
// override methods //
//==================//
@Override
public void close() throws Exception
{ /* not currently needed */ }
//================//
// helper classes //
//================//
/**
* This holds information that is relevant to the entire source and isn't stored in the data points. <br>
* Example: minimum height, detail level, source type, etc.
*/
private static class FullDataSourceSummaryData
{
public final int dataWidth;
public EDhApiWorldGenerationStep worldGenStep;
public FullDataSourceSummaryData(int dataWidth, EDhApiWorldGenerationStep worldGenStep)
{
this.dataWidth = dataWidth;
this.worldGenStep = worldGenStep;
}
}
}
@@ -0,0 +1,951 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.sources;
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.transformers.LodDataBuilder;
import com.seibel.distanthorizons.core.file.AbstractDataSourceHandler;
import com.seibel.distanthorizons.core.file.DataSourcePool;
import com.seibel.distanthorizons.core.file.IDataSource;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.Arrays;
/**
* This data source contains every datapoint over its given {@link DhSectionPos}. <br><br>
*
* @see FullDataPointUtil
* @see FullDataSourceV1
*/
public class FullDataSourceV2 implements IDataSource<IDhLevel>
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/** useful for debugging, but can slow down update operations quite a bit due to being called so often. */
private static final boolean RUN_UPDATE_DEV_VALIDATION = false;
/**
* If the data column order isn't correct
* block lighting may appear broken
* and/or certain detail level LODs may not appear at all.
*/
private static final boolean RUN_DATA_ORDER_VALIDATION = ModInfo.IS_DEV_BUILD;
/** measured in data columns */
public static final int WIDTH = 64;
public static final byte DATA_FORMAT_VERSION = 1;
public static final DataSourcePool<FullDataSourceV2, IDhLevel> DATA_SOURCE_POOL = new DataSourcePool<>(FullDataSourceV2::createEmpty, FullDataSourceV2::prepPooledDataSource);
private int cachedHashCode = 0;
private long pos;
@Override
public Long getKey() { return this.pos; }
public final FullDataPointIdMap mapping;
public long lastModifiedUnixDateTime;
public long createdUnixDateTime;
public int levelMinY;
/**
* stores how far each column has been generated should start with {@link EDhApiWorldGenerationStep#EMPTY}
* @see EDhApiWorldGenerationStep
*/
public byte[] columnGenerationSteps;
/**
* stores what world compression was used for each column.
* @see EDhApiWorldCompressionMode
*/
public byte[] columnWorldCompressionMode;
/**
* stored x/z, y <br>
* The y data should be sorted from top to bottom <br>
* TODO that ordering feels weird, it'd be nice to reverse that order, unfortunately
* there's something in the render data logic that expects this order so we can't change it right now
*/
public LongArrayList[] dataPoints;
public boolean isEmpty;
public boolean applyToParent = false;
//==============//
// constructors //
//==============//
public static FullDataSourceV2 createEmpty(long pos) { return new FullDataSourceV2(pos); }
private FullDataSourceV2(long pos)
{
this.pos = pos;
this.dataPoints = new LongArrayList[WIDTH * WIDTH];
this.mapping = new FullDataPointIdMap(pos);
this.isEmpty = true;
// doesn't need to be populated since nothing has been generated yet
// the default value of all 0's is adequate
this.columnGenerationSteps = new byte[WIDTH * WIDTH];
this.columnWorldCompressionMode = new byte[WIDTH * WIDTH];
}
public static FullDataSourceV2 createWithData(long pos, FullDataPointIdMap mapping, LongArrayList[] data, byte[] columnGenerationStep, byte[] columnWorldCompressionMode) { return new FullDataSourceV2(pos, mapping, data, columnGenerationStep, columnWorldCompressionMode); }
private FullDataSourceV2(long pos, FullDataPointIdMap mapping, LongArrayList[] data, byte[] columnGenerationSteps, byte[] columnWorldCompressionMode)
{
LodUtil.assertTrue(data.length == WIDTH * WIDTH);
this.pos = pos;
this.dataPoints = data;
this.mapping = mapping;
this.isEmpty = false;
this.columnGenerationSteps = columnGenerationSteps;
this.columnWorldCompressionMode = columnWorldCompressionMode;
}
public static FullDataSourceV2 createFromChunk(IChunkWrapper chunkWrapper) { return LodDataBuilder.createGeneratedDataSource(chunkWrapper); }
public static FullDataSourceV2 createFromLegacyDataSourceV1(FullDataSourceV1 legacyData)
{
if (FullDataSourceV1.WIDTH != WIDTH)
{
throw new UnsupportedOperationException(
"Unable to convert ["+FullDataSourceV1.class.getSimpleName()+"] into ["+FullDataSourceV2.class.getSimpleName()+"]. " +
"Data sources have different data point widths and no converter is present. " +
"input width ["+ FullDataSourceV1.WIDTH+"], recipient width ["+WIDTH+"].");
}
// Note: this logic only works if the data point data is the same between both versions
byte[] columnGenerationSteps = new byte[WIDTH * WIDTH];
byte[] columnWorldCompressionMode = new byte[WIDTH * WIDTH];
LongArrayList[] dataPoints = new LongArrayList[WIDTH * WIDTH];
for (int x = 0; x < WIDTH; x++)
{
for (int z = 0; z < WIDTH; z++)
{
long[] legacyDataColumn = legacyData.get(x, z);
if (legacyDataColumn != null && legacyDataColumn.length != 0)
{
int index = relativePosToIndex(x, z);
LongArrayList newDataColumn = new LongArrayList(legacyDataColumn);
// convert the data point format
boolean columnHasNonAirBlock = false;
for (int i = 0; i < legacyDataColumn.length; i++)
{
long dataPoint = legacyDataColumn[i];
boolean isAir = legacyData.mapping.getBlockStateWrapper(FullDataPointUtil.getId(dataPoint)).isAir();
byte blockLight = (byte) FullDataPointUtil.getBlockLight(dataPoint);
if (isAir)
{
// air shouldn't have any light, otherwise down sampling will look weird
blockLight = 0;
}
dataPoint = FullDataPointUtil.setBlockLight(dataPoint, blockLight);
newDataColumn.set(i, dataPoint);
// check if this datapoint is air
if (!columnHasNonAirBlock && !isAir)
{
columnHasNonAirBlock = true;
}
}
// save the converted data point
ensureDataColumnOrder(newDataColumn);
dataPoints[index] = newDataColumn;
// the old data sources didn't have a generation step written down
// if the column has any data points, assume it's fully generated, otherwise assume it's empty
columnGenerationSteps[index] = (columnHasNonAirBlock ? EDhApiWorldGenerationStep.LIGHT.value : EDhApiWorldGenerationStep.EMPTY.value);
columnWorldCompressionMode[index] = EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS.value;
}
}
}
FullDataSourceV2 fullDataSource = FullDataSourceV2.createWithData(legacyData.getPos(), legacyData.mapping, dataPoints, columnGenerationSteps, columnWorldCompressionMode);
return fullDataSource;
}
//======//
// data //
//======//
public LongArrayList get(int relX, int relZ) throws IndexOutOfBoundsException { return this.dataPoints[relativePosToIndex(relX, relZ)]; }
@Override
public boolean update(@NotNull FullDataSourceV2 inputDataSource, @Nullable IDhLevel level) { return this.update(inputDataSource); }
public boolean update(@NotNull FullDataSourceV2 inputDataSource)
{
// don't try updating if the input is empty
if (inputDataSource.mapping.isEmpty())
{
return false;
}
byte thisDetailLevel = DhSectionPos.getDetailLevel(this.pos);
byte inputDetailLevel = DhSectionPos.getDetailLevel(inputDataSource.pos);
// determine the mapping changes necessary for the input to map onto this datasource
int[] remappedIds = this.mapping.mergeAndReturnRemappedEntityIds(inputDataSource.mapping);
boolean dataChanged;
if (inputDetailLevel == thisDetailLevel)
{
dataChanged = this.updateFromSameDetailLevel(inputDataSource, remappedIds);
}
else if (inputDetailLevel + 1 == thisDetailLevel)
{
dataChanged = this.updateFromOneBelowDetailLevel(inputDataSource, remappedIds);
}
else
{
// other detail levels aren't supported since it would be more difficult to maintain
// and would lead to edge cases that don't necessarily need to be supported
// (IE what do you do when the input is smaller than a single datapoint in the receiving data source?)
// instead it's better to just percolate the updates up
throw new UnsupportedOperationException("Unsupported data source update. Expected input detail level of ["+thisDetailLevel+"] or ["+(thisDetailLevel+1)+"], received detail level ["+inputDetailLevel+"].");
}
// determine if this data source should be applied to its parent
this.applyToParent = (dataChanged && DhSectionPos.getDetailLevel(this.pos) < AbstractDataSourceHandler.TOP_SECTION_DETAIL_LEVEL);
if (dataChanged)
{
// update the hash code
this.generateHashCode();
}
return dataChanged;
}
public boolean updateFromSameDetailLevel(FullDataSourceV2 inputDataSource, int[] remappedIds)
{
// both data sources should have the same detail level
if (DhSectionPos.getDetailLevel(inputDataSource.pos) != DhSectionPos.getDetailLevel(this.pos))
{
throw new IllegalArgumentException("Both data sources must have the same detail level. Expected ["+ DhSectionPos.getDetailLevel(this.pos)+"], received ["+ DhSectionPos.getDetailLevel(inputDataSource.pos)+"].");
}
// copy over everything from the input data source into this one
// provided there is data to copy and the world generation step is the same or more complete
boolean dataChanged = false;
for (int x = 0; x < WIDTH; x++)
{
for (int z = 0; z < WIDTH; z++)
{
int index = relativePosToIndex(x, z);
LongArrayList inputDataArray = inputDataSource.dataPoints[index];
if (inputDataArray != null)
{
byte thisGenState = this.columnGenerationSteps[index];
byte inputGenState = inputDataSource.columnGenerationSteps[index];
if (inputGenState != EDhApiWorldGenerationStep.EMPTY.value
&& thisGenState <= inputGenState)
{
// check if the data changed
if (this.dataPoints[index] == null)
{
// no data was present previously
this.dataPoints[index] = new LongArrayList(inputDataArray);
dataChanged = true;
}
else if (this.dataPoints[index].size() != inputDataArray.size())
{
// data is present, but the size is different
dataChanged = true;
}
int oldDataHash = 0;
if (!dataChanged)
{
// some old data existed with the same length,
// we'll have to compare the caches
oldDataHash = this.dataPoints[index].hashCode();
}
// copy over the new data
this.dataPoints[index].clear();
this.dataPoints[index].addAll(inputDataArray);
this.remapDataColumn(index, remappedIds);
if (RUN_DATA_ORDER_VALIDATION)
{
throwIfDataColumnInWrongOrder(inputDataSource.pos, this.dataPoints[index]);
}
if (!dataChanged)
{
// check if the identical length data column hashes are the same
// hashes need to be compared after the ID's have been remapped otherwise the ID's won't match even if the data is the same
if (oldDataHash != this.dataPoints[index].hashCode())
{
// the hashes are different, something was changed
dataChanged = true;
}
}
this.columnGenerationSteps[index] = inputGenState;
// always overwrite the compression mode since we're replacing this column
this.columnWorldCompressionMode[index] = inputDataSource.columnWorldCompressionMode[index];
this.isEmpty = false;
}
}
}
}
return dataChanged;
}
public boolean updateFromOneBelowDetailLevel(FullDataSourceV2 inputDataSource, int[] remappedIds)
{
if (DhSectionPos.getDetailLevel(inputDataSource.pos) + 1 != DhSectionPos.getDetailLevel(this.pos))
{
throw new IllegalArgumentException("Input data source must be exactly 1 detail level below this data source. Expected [" + (DhSectionPos.getDetailLevel(this.pos) - 1) + "], received [" + DhSectionPos.getDetailLevel(inputDataSource.pos) + "].");
}
// input is one detail level lower (higher detail)
// so 2x2 input data points will be converted into 1 recipient data point
// determine where in the input data source should be written to
// since the input is one detail level below it will be one of this position's 4 children
int minChildXPos = DhSectionPos.getX(DhSectionPos.getChildByIndex(this.pos, 0));
int recipientOffsetX = (DhSectionPos.getX(inputDataSource.pos) == minChildXPos) ? 0 : (WIDTH / 2);
int minChildZPos = DhSectionPos.getZ(DhSectionPos.getChildByIndex(this.pos, 0));
int recipientOffsetZ = (DhSectionPos.getZ(inputDataSource.pos) == minChildZPos) ? 0 : (WIDTH / 2);
// merge the input's data points
// into this data source's
boolean dataChanged = false;
for (int x = 0; x < WIDTH; x += 2)
{
for (int z = 0; z < WIDTH; z += 2)
{
int recipientX = (x / 2) + recipientOffsetX;
int recipientZ = (z / 2) + recipientOffsetZ;
int recipientIndex = relativePosToIndex(recipientX, recipientZ);
// world gen //
byte inputGenStep = determineMinWorldGenStepForTwoByTwoColumn(inputDataSource.columnGenerationSteps, x, z);
this.columnGenerationSteps[recipientIndex] = inputGenStep;
// world compression //
byte worldCompressionMode = determineHighestWorldCompressionForTwoByTwoColumn(inputDataSource.columnWorldCompressionMode, x, z);
this.columnWorldCompressionMode[recipientIndex] = worldCompressionMode;
// data points //
LongArrayList mergedInputDataArray = mergeInputTwoByTwoDataColumn(inputDataSource, x, z);
// check if the data changed
if (this.dataPoints[recipientIndex] == null)
{
// no data was present previously
dataChanged = true;
}
else if (this.dataPoints[recipientIndex].size() != mergedInputDataArray.size())
{
// data is present, but the size is different
dataChanged = true;
}
int oldDataHash = 0;
if (!dataChanged)
{
// some old data existed with the same length,
// we'll have to compare the caches
oldDataHash = this.dataPoints[recipientIndex].hashCode();
}
this.dataPoints[recipientIndex] = mergedInputDataArray;
this.remapDataColumn(recipientIndex, remappedIds);
if (RUN_DATA_ORDER_VALIDATION)
{
throwIfDataColumnInWrongOrder(inputDataSource.pos, this.dataPoints[recipientIndex]);
}
if (!dataChanged)
{
// check if the identical length data column hashes are the same
// hashes need to be compared after the ID's have been remapped otherwise the ID's won't match even if the data is the same
if (oldDataHash != this.dataPoints[recipientIndex].hashCode())
{
// the hashes are different, something was changed
dataChanged = true;
}
}
this.isEmpty = false;
}
}
return dataChanged;
}
/**
* The minimum value is used because we don't want to accidentally record that
* something was generated when it wasn't.
*/
private static byte determineMinWorldGenStepForTwoByTwoColumn(byte[] columnGenerationSteps, int relX, int relZ)
{
// TODO merge similar logic with determineHighestWorldCompressionForTwoByTwoColumn
byte minWorldGenStepValue = Byte.MAX_VALUE;
for (int x = 0; x < 2; x++)
{
for (int z = 0; z < 2; z++)
{
int index = relativePosToIndex(x + relX, z + relZ);
byte worldGenStepValue = columnGenerationSteps[index];
minWorldGenStepValue = (byte) Math.min(minWorldGenStepValue, worldGenStepValue);
}
}
return minWorldGenStepValue;
}
/**
* The minimum value is used because we don't want to accidentally record that
* something was generated when it wasn't.
*/
private static byte determineHighestWorldCompressionForTwoByTwoColumn(byte[] columnCompressionMode, int relX, int relZ)
{
// TODO merge similar logic with determineMinWorldGenStepForTwoByTwoColumn
byte minWorldGenStepValue = Byte.MIN_VALUE;
for (int x = 0; x < 2; x++)
{
for (int z = 0; z < 2; z++)
{
int index = relativePosToIndex(x + relX, z + relZ);
byte worldGenStepValue = columnCompressionMode[index];
minWorldGenStepValue = (byte) Math.max(minWorldGenStepValue, worldGenStepValue);
}
}
return minWorldGenStepValue;
}
private static LongArrayList mergeInputTwoByTwoDataColumn(FullDataSourceV2 inputDataSource, int x, int z)
{
LongArrayList newColumnList = new LongArrayList();
// special numbers:
// -2 = the column's height hasn't been determined yet
// -1 = we've reached the end of the column
int[] currentDatapointIndex = new int[] { -2, -2, -2, -2 };
int lastId = 0;
byte lastBlockLight = 0;
byte lastSkyLight = 0;
int height = 0;
int minY = 0;
// these arrays will be reused quite often, so re-using them helps reduce some GC pressure
long[] datapointsForYSlice = new long[4];
int[] mergeIds = new int[4];
int[] mergeBlockLights = new int[4];
int[] mergeSkyLights = new int[4];
for (int blockY = 0; blockY < RenderDataPointUtil.MAX_WORLD_Y_SIZE; blockY++, height++)
{
// if each column has reached the end of their data, nothing more needs to be done
if (currentDatapointIndex[0] == -1
&& currentDatapointIndex[1] == -1
&& currentDatapointIndex[2] == -1
&& currentDatapointIndex[3] == -1
)
{
break;
}
// scary double loop but,
// this will only ever loop 4 times,
// once for each of the 4 input columns
Arrays.fill(datapointsForYSlice, 0L);
int colIndex = 0;
for (int inputX = x; inputX < x + 2; inputX++)
{
for (int inputZ = z; inputZ < z + 2; inputZ++, colIndex++)
{
// TODO throw an assertion if the column isn't in top-down order or just fix it...
LongArrayList inputDataArray = inputDataSource.dataPoints[relativePosToIndex(inputX, inputZ)];
if (inputDataArray == null || inputDataArray.size() == 0)
{
currentDatapointIndex[colIndex] = -1;
continue;
}
// determine the last index (the lowest data point) for each column
if (currentDatapointIndex[colIndex] == -2)
{
currentDatapointIndex[colIndex] = inputDataArray.size() - 1;
if (RUN_DATA_ORDER_VALIDATION)
{
throwIfDataColumnInWrongOrder(inputDataSource.pos, inputDataArray);
}
}
int dataPointIndex = currentDatapointIndex[colIndex];
if (dataPointIndex == -1)
{
// went over the end
continue;
}
long datapoint = inputDataArray.getLong(dataPointIndex);
int datapointMinY = FullDataPointUtil.getBottomY(datapoint);
int numbOfBlocksTall = FullDataPointUtil.getHeight(datapoint);
int datapointMaxY = (datapointMinY + numbOfBlocksTall);
// check if y position is inside this datapoint
if (blockY < datapointMinY)
{
// this y-slice is below this datapoint, nothing can be added
continue;
}
else if (blockY >= datapointMaxY)
{
// this y-slice is above the current datapoint,
// try the next data point
int newDatapointIndex = currentDatapointIndex[colIndex] - 1;
if (newDatapointIndex < 0)
{
// went to far, no additional data present
newDatapointIndex = -1;
}
currentDatapointIndex[colIndex] = newDatapointIndex;
// try again with the next data point
inputZ--;
colIndex--;
continue;
}
datapointsForYSlice[colIndex] = datapoint;
}
}
Arrays.fill(mergeIds, 0);
Arrays.fill(mergeBlockLights, 0);
Arrays.fill(mergeSkyLights, 0);
for (int i = 0; i < 4; i++)
{
mergeIds[i] = FullDataPointUtil.getId(datapointsForYSlice[i]);
mergeBlockLights[i] = FullDataPointUtil.getBlockLight(datapointsForYSlice[i]);
mergeSkyLights[i] = FullDataPointUtil.getSkyLight(datapointsForYSlice[i]);
}
// determine the most common values for this slice
int id = determineMostValueInColumnSlice(mergeIds, inputDataSource.mapping);
byte blockLight = (byte) determineAverageValueInColumnSlice(mergeBlockLights);
byte skyLight = (byte) determineAverageValueInColumnSlice(mergeSkyLights);
// if this slice is different then the last one, create a new one
if (id != lastId
// block and sky light might not be necessary
|| blockLight != lastBlockLight
|| skyLight != lastSkyLight)
{
if (height != 0)
{
try
{
long datapoint = FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight);
newColumnList.add(datapoint);
}
catch (DataCorruptedException e)
{
// shouldn't happen, (especially if validation is disabled) but just in case
LOGGER.warn("Skipping corrupt datapoint for pos "+inputDataSource.pos+" at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
}
}
lastId = id;
lastBlockLight = blockLight;
lastSkyLight = skyLight;
height = 0;
minY = blockY;
}
}
// add the last slice if present
if (height != 0)
{
try
{
newColumnList.add(FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight));
}
catch (DataCorruptedException e)
{
// shouldn't happen, (especially if validation is disabled) but just in case
LOGGER.warn("Skipping corrupt datapoint for pos "+inputDataSource.pos+" at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
}
}
// flip the array if necessary
// TODO why is this sometimes necessary? What did I (James) screw up that causes the mergedInputDataArray
// to sometimes be in a different order? Is it potentially related to what detail level is coming in?
ensureDataColumnOrder(newColumnList);
return newColumnList;
}
/**
* Only update the ID once it's been added to this data source.
* Updating the incoming data source will cause issues if it is applied
* to anything else due to multiple remapping.
*/
private void remapDataColumn(int dataPointIndex, int[] remappedIds)
{
LongArrayList dataColumn = this.dataPoints[dataPointIndex];
for (int i = 0; i < dataColumn.size(); i++)
{
dataColumn.set(i, FullDataPointUtil.remap(remappedIds, dataColumn.getLong(i)));
}
}
private static boolean areDataColumnsDifferent(long[] oldDataArray, long[] newDataArray)
{
if (oldDataArray == null || oldDataArray.length != newDataArray.length)
{
// new data was added/removed
return true;
}
else
{
// check if the new column data is different
int oldArrayHash = Arrays.hashCode(oldDataArray);
int newArrayHash = Arrays.hashCode(newDataArray);
return (newArrayHash != oldArrayHash);
}
}
/** @param mapping can be included to ignore air ID's, otherwise all 4 values are treated equally */
private static int determineMostValueInColumnSlice(int[] sliceArray, @Nullable FullDataPointIdMap mapping)
{
if (RUN_UPDATE_DEV_VALIDATION)
{
LodUtil.assertTrue(sliceArray.length == 4, "Column Slice should only contain 4 values.");
}
int value0 = sliceArray[0];
int count0 = 0;
int value1 = sliceArray[1];
int count1 = 0;
int value2 = sliceArray[2];
int count2 = 0;
int value3 = sliceArray[3];
int count3 = 0;
// count the occurrences of each value
for (int i = 0; i < 4; i++)
{
int value = sliceArray[i];
if (mapping != null && mapping.getBlockStateWrapper(value).isAir())
{
// always overwrite air to prevent holes in hollow structures
continue;
}
if (value == value0)
count0++;
else if (value == value1)
count1++;
else if (value == value2)
count2++;
else
count3++;
}
// return the most common occurance
int maxCount = Math.max(count0, Math.max(count1, Math.max(count2, count3)));
if (maxCount == count0)
// if the max count is 1 then we'll just go with the first column
return value0;
else if (maxCount == count1)
return value1;
else if (maxCount == count2)
return value2;
else
return value3;
}
private static int determineAverageValueInColumnSlice(int[] sliceArray)
{
if (RUN_UPDATE_DEV_VALIDATION)
{
LodUtil.assertTrue(sliceArray.length == 4, "Column Slice should only contain 4 values.");
}
int value = 0;
for (int i = 0; i < 4; i++)
{
value += sliceArray[i];
}
value /= 4;
return value;
}
//================//
// helper methods //
//================//
/**
* Usually this should just be used internally, but there may be instances
* where the raw data arrays are available without the data source object.
*/
public static int relativePosToIndex(int relX, int relZ) throws IndexOutOfBoundsException
{
if (relX < 0 || relZ < 0 ||
relX > WIDTH || relZ > WIDTH)
{
throw new IndexOutOfBoundsException("Relative data source positions must be between [0] and ["+WIDTH+"] (inclusive) the relative pos: ["+relX+","+relZ+"] is outside of those boundaries.");
}
return (relX * WIDTH) + relZ;
}
/**
* Throws an exception if the given
* full data column array is in the wrong order
* IE if the first data point is the lowest and the last data point is the highest.
* Data columns should be in reverse order, IE the first data point should be the highest data point.
*
* @see FullDataSourceV2#dataPoints
*/
public static void throwIfDataColumnInWrongOrder(long pos, LongArrayList dataArray) throws IllegalStateException
{
long firstDataPoint = dataArray.getLong(0);
int firstBottomY = FullDataPointUtil.getBottomY(firstDataPoint);
long lastDataPoint = dataArray.getLong(dataArray.size() - 1);
int lastBottomY = FullDataPointUtil.getBottomY(lastDataPoint);
if (firstBottomY < lastBottomY)
{
throw new IllegalStateException("Incorrect data point order at pos: ["+ DhSectionPos.toString(pos)+"], first datapoint bottom Y ["+firstBottomY+"], last datapoint bottom Y ["+lastBottomY+"].");
}
}
/**
* Ensures the given data column is in the correct Y order, specifically
* top-to-bottom.
*/
private static void ensureDataColumnOrder(LongArrayList dataColumn)
{
long firstDataPoint = dataColumn.getLong(0);
int firstBottomY = FullDataPointUtil.getBottomY(firstDataPoint);
long lastDataPoint = dataColumn.getLong(dataColumn.size() - 1);
int lastBottomY = FullDataPointUtil.getBottomY(lastDataPoint);
if (firstBottomY < lastBottomY)
{
// reverse the array so index 0 is the highest,
// this is necessary for later logic
// source: https://stackoverflow.com/questions/2137755/how-do-i-reverse-an-int-array-in-java
for(int i = 0; i < dataColumn.size() / 2; i++)
{
long temp = dataColumn.getLong(i);
dataColumn.set(i, dataColumn.getLong(dataColumn.size() - i - 1));
dataColumn.set(dataColumn.size() - i - 1, temp);
}
}
}
//=========//
// pooling //
//=========//
private static void prepPooledDataSource(long pos, boolean clearData, FullDataSourceV2 dataSource)
{
dataSource.pos = pos;
if (clearData)
{
dataSource.mapping.clear(pos);
for (int i = 0; i < dataSource.dataPoints.length; i++)
{
if (dataSource.dataPoints[i] != null)
{
dataSource.dataPoints[i].clear();
}
}
Arrays.fill(dataSource.columnGenerationSteps, (byte) 0);
Arrays.fill(dataSource.columnWorldCompressionMode, (byte) 0);
}
}
//=====================//
// setters and getters //
//=====================//
@Override
public Long getPos() { return this.pos; }
@Override
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); }
public EDhApiWorldGenerationStep getWorldGenStepAtRelativePos(int relX, int relZ)
{
int index = relativePosToIndex(relX, relZ);
return EDhApiWorldGenerationStep.fromValue(this.columnGenerationSteps[index]);
}
public void setSingleColumn(LongArrayList longArray, int relX, int relZ, EDhApiWorldGenerationStep worldGenStep, EDhApiWorldCompressionMode worldCompressionMode)
{
int index = relativePosToIndex(relX, relZ);
this.dataPoints[index] = longArray;
this.columnGenerationSteps[index] = worldGenStep.value;
this.columnWorldCompressionMode[index] = worldCompressionMode.value;
if (RUN_UPDATE_DEV_VALIDATION)
{
// validate the incoming ID's
int maxValidId = this.mapping.getMaxValidId();
for (int i = 0; i < longArray.size(); i++)
{
long dataPoint = longArray.getLong(i);
int id = FullDataPointUtil.getId(dataPoint);
if (id > maxValidId)
{
LodUtil.assertNotReach("Column set with higher than possible ID. ID [" + id + "], max valid ID [" + maxValidId + "].");
}
}
}
}
//================//
// base overrides //
//================//
@Override
public String toString() { return DhSectionPos.toString(this.pos); }
@Override
public int hashCode()
{
if (this.cachedHashCode == 0)
{
this.generateHashCode();
}
return this.cachedHashCode;
}
private void generateHashCode()
{
int result = DhSectionPos.hashCode(this.pos);
result = 31 * result + Arrays.deepHashCode(this.dataPoints);
result = 17 * result + Arrays.hashCode(this.columnGenerationSteps);
result = 43 * result + Arrays.hashCode(this.columnWorldCompressionMode);
this.cachedHashCode = result;
}
@Override
public boolean equals(Object obj)
{
if (!(obj instanceof FullDataSourceV2))
{
return false;
}
FullDataSourceV2 other = (FullDataSourceV2) obj;
if (other.pos != this.pos)
{
return false;
}
else
{
// the positions are the same, use the hash as a quick method
// to determine if the data inside is the same.
// Note: this isn't perfect, but should work well enough for our use case.
return other.hashCode() == this.hashCode();
}
}
@Override
public void close() throws Exception
{
DATA_SOURCE_POOL.returnPooledDataSource(this);
}
}
@@ -1,583 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.sources;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.FullDataArrayAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.SingleColumnFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IStreamableFullDataSource;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.DataSourceDto;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.util.BitSet;
/**
* Used for small incomplete LOD blocks.<br>
* Handles incomplete full data with a detail level equal to or lower than
* {@link HighDetailIncompleteFullDataSource#MAX_SECTION_DETAIL}. <br><br>
*
* Compared to other {@link IIncompleteFullDataSource}'s, this object doesn't extend {@link FullDataArrayAccessor},
* instead it contains several "sections" of data, represented by {@link FullDataArrayAccessor}s. <br><br>
*
* Formerly "SparseFullDataSource".
*
* @see LowDetailIncompleteFullDataSource
* @see CompleteFullDataSource
* @see FullDataPointUtil
*/
public class HighDetailIncompleteFullDataSource implements IIncompleteFullDataSource, IStreamableFullDataSource<IStreamableFullDataSource.FullDataSourceSummaryData, long[][][]>
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
// TODO James would like to rename, comment, and potentially remove some of these constants.
// But he doesn't currently have the understanding to do so.
public static final byte SPARSE_UNIT_DETAIL = LodUtil.CHUNK_DETAIL_LEVEL;
public static final byte SPARSE_UNIT_SIZE = (byte) BitShiftUtil.powerOfTwo(SPARSE_UNIT_DETAIL);
public static final byte SECTION_SIZE_OFFSET = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
public static final int SECTION_SIZE = (byte) BitShiftUtil.powerOfTwo(SECTION_SIZE_OFFSET);
/** aka max detail level */
public static final byte MAX_SECTION_DETAIL = SECTION_SIZE_OFFSET + SPARSE_UNIT_DETAIL;
public static final byte DATA_FORMAT_VERSION = 3;
public static final String DATA_TYPE_NAME = "HighDetailIncompleteFullDataSource";
@Override
public String getDataTypeName() { return DATA_TYPE_NAME; }
protected final FullDataPointIdMap mapping;
private DhSectionPos sectionPos;
private FullDataArrayAccessor[] sparseData;
private DhLodPos chunkPos;
public int sectionCount;
public int dataPointsPerSection;
public boolean isEmpty = true;
public EDhApiWorldGenerationStep worldGenStep = EDhApiWorldGenerationStep.EMPTY;
private boolean isPromoted = false;
//==============//
// constructors //
//==============//
public static HighDetailIncompleteFullDataSource createEmpty(DhSectionPos pos) { return new HighDetailIncompleteFullDataSource(pos); }
private HighDetailIncompleteFullDataSource(DhSectionPos sectionPos)
{
LodUtil.assertTrue(sectionPos.getDetailLevel() > SPARSE_UNIT_DETAIL);
LodUtil.assertTrue(sectionPos.getDetailLevel() <= MAX_SECTION_DETAIL);
this.sectionPos = sectionPos;
this.sectionCount = BitShiftUtil.powerOfTwo(sectionPos.getDetailLevel() - SPARSE_UNIT_DETAIL);
this.dataPointsPerSection = SECTION_SIZE / this.sectionCount;
this.sparseData = new FullDataArrayAccessor[this.sectionCount * this.sectionCount];
this.chunkPos = sectionPos.getMinCornerLodPos(SPARSE_UNIT_DETAIL);
this.mapping = new FullDataPointIdMap(sectionPos);
}
protected HighDetailIncompleteFullDataSource(DhSectionPos sectionPos, FullDataPointIdMap mapping, FullDataArrayAccessor[] data)
{
LodUtil.assertTrue(sectionPos.getDetailLevel() > SPARSE_UNIT_DETAIL);
LodUtil.assertTrue(sectionPos.getDetailLevel() <= MAX_SECTION_DETAIL);
this.sectionPos = sectionPos;
this.sectionCount = 1 << (byte) (sectionPos.getDetailLevel() - SPARSE_UNIT_DETAIL);
this.dataPointsPerSection = SECTION_SIZE / this.sectionCount;
LodUtil.assertTrue(this.sectionCount * this.sectionCount == data.length);
this.sparseData = data;
this.chunkPos = sectionPos.getMinCornerLodPos(SPARSE_UNIT_DETAIL);
this.isEmpty = false;
this.mapping = mapping;
}
//=================//
// stream handling //
//=================//
@Override
public void writeSourceSummaryInfo(IDhLevel level, DhDataOutputStream dataOutputStream) throws IOException
{
dataOutputStream.writeShort(this.getDataDetailLevel());
dataOutputStream.writeShort(SPARSE_UNIT_DETAIL);
dataOutputStream.writeInt(SECTION_SIZE);
dataOutputStream.writeInt(level.getMinY());
dataOutputStream.writeByte(this.worldGenStep.value);
}
@Override
public FullDataSourceSummaryData readSourceSummaryInfo(DataSourceDto dto, DhDataInputStream inputStream, IDhLevel level) throws IOException
{
LodUtil.assertTrue(dto.pos.getDetailLevel() > SPARSE_UNIT_DETAIL);
LodUtil.assertTrue(dto.pos.getDetailLevel() <= MAX_SECTION_DETAIL);
int dataDetailLevel = inputStream.readShort();
if (dataDetailLevel != dto.dataDetailLevel)
{
throw new IOException("Data level mismatch: ["+dataDetailLevel+"] != ["+dto.dataDetailLevel+"]");
}
// confirm that the detail level is correct
int sparseDetail = inputStream.readShort();
if (sparseDetail != SPARSE_UNIT_DETAIL)
{
throw new IOException("Unexpected sparse detail level: ["+sparseDetail+"] != ["+SPARSE_UNIT_DETAIL+"]");
}
// confirm the scale of the data points is correct
int sectionSize = inputStream.readInt();
if (sectionSize != SECTION_SIZE)
{
throw new IOException("Section size mismatch: ["+sectionSize+"] != ["+SECTION_SIZE+"] (Currently only 1 section size is supported)");
}
int minY = inputStream.readInt();
if (minY != level.getMinY())
{
LOGGER.warn("Data minY mismatch: [" + minY + "] != [" + level.getMinY() + "]. Will ignore data's y level");
}
EDhApiWorldGenerationStep worldGenStep = EDhApiWorldGenerationStep.fromValue(inputStream.readByte());
if (worldGenStep == null)
{
worldGenStep = EDhApiWorldGenerationStep.SURFACE;
LOGGER.warn("Missing WorldGenStep, defaulting to: " + worldGenStep.name());
}
return new FullDataSourceSummaryData(-1, worldGenStep);
}
public void setSourceSummaryData(FullDataSourceSummaryData summaryData) { this.worldGenStep = summaryData.worldGenStep; }
@Override
public boolean writeDataPoints(DhDataOutputStream dataOutputStream) throws IOException
{
if (this.isEmpty)
{
dataOutputStream.writeInt(IFullDataSource.NO_DATA_FLAG_BYTE);
return false;
}
dataOutputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE);
// sparse array existence bitset
BitSet dataArrayIndexHasData = new BitSet(this.sparseData.length);
for (int i = 0; i < this.sparseData.length; i++)
{
dataArrayIndexHasData.set(i, this.sparseData[i] != null);
}
byte[] bytes = dataArrayIndexHasData.toByteArray();
dataOutputStream.writeInt(bytes.length);
dataOutputStream.write(bytes);
// Data array content (only non-empty data is written)
dataOutputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE);
for (int dataArrayIndex = dataArrayIndexHasData.nextSetBit(0);
dataArrayIndex >= 0;
dataArrayIndex = dataArrayIndexHasData.nextSetBit(dataArrayIndex + 1))
{
// column data length
FullDataArrayAccessor array = this.sparseData[dataArrayIndex];
LodUtil.assertTrue(array != null);
for (int x = 0; x < array.width(); x++)
{
for (int z = 0; z < array.width(); z++)
{
SingleColumnFullDataAccessor columnAccessor = array.get(x, z);
int columnLength = 0;
if (columnAccessor != null)
{
columnLength = columnAccessor.getSingleLength();
}
dataOutputStream.writeInt(columnLength);
}
}
// column data
for (int x = 0; x < array.width(); x++)
{
for (int z = 0; z < array.width(); z++)
{
SingleColumnFullDataAccessor column = array.get(x, z);
LodUtil.assertTrue(column.getMapping() == this.mapping); // the mappings must be exactly equal!
if (column.doesColumnExist())
{
long[] rawDataPoints = column.getRaw();
for (long dataPoint : rawDataPoints)
{
dataOutputStream.writeLong(dataPoint);
}
}
}
}
}
return true;
}
@Override
public long[][][] readDataPoints(DataSourceDto dto, int width, DhDataInputStream inputStream) throws IOException
{
// calculate the number of chunks and dataPoints based on the sparseDetail and sectionSize
// TODO these values should be constant, should we still be calculating them like this?
int chunks = BitShiftUtil.powerOfTwo(dto.pos.getDetailLevel() - SPARSE_UNIT_DETAIL);
int dataPointsPerChunk = SECTION_SIZE / chunks;
// check if this file has any data
int dataPresentFlag = inputStream.readInt();
if (dataPresentFlag == IFullDataSource.NO_DATA_FLAG_BYTE)
{
// this file is empty
return null;
}
else if (dataPresentFlag != IFullDataSource.DATA_GUARD_BYTE)
{
// the file format is incorrect
throw new IOException("Invalid file format. Data Points guard byte expected: (no data) [" + IFullDataSource.NO_DATA_FLAG_BYTE + "] or (data present) [" + IFullDataSource.DATA_GUARD_BYTE + "], but found [" + dataPresentFlag + "].");
}
// get the number of columns (IE the bitSet from before)
int numberOfDataColumns = inputStream.readInt();
// validate the number of data columns
int maxNumberOfDataColumns = (chunks * chunks / 8 + 64) * 2; // TODO what do these values represent?
if (numberOfDataColumns < 0 || numberOfDataColumns > maxNumberOfDataColumns)
{
throw new IOException(LodUtil.formatLog("Sparse Flag BitSet size outside reasonable range: {} (expects {} to {})",
numberOfDataColumns, 1, maxNumberOfDataColumns));
}
// read in the presence of each data column
byte[] bytes = new byte[numberOfDataColumns];
inputStream.readFully(bytes, 0, numberOfDataColumns);
BitSet dataArrayIndexHasData = BitSet.valueOf(bytes);
//====================//
// Data array content //
//====================//
// (only on non-empty columns)
int dataArrayStartByte = inputStream.readInt();
// confirm the column data is starting
if (dataArrayStartByte != IFullDataSource.DATA_GUARD_BYTE)
{
// the file format is incorrect
throw new IOException("invalid data length end guard");
}
// read in each column that has data written to it
long[][][] rawFullDataArrays = new long[chunks * chunks][][];
for (int fullDataIndex = dataArrayIndexHasData.nextSetBit(0);
fullDataIndex >= 0 && // TODO why does this happen?
fullDataIndex < rawFullDataArrays.length;
fullDataIndex = dataArrayIndexHasData.nextSetBit(fullDataIndex + 1))
{
long[][] dataColumn = new long[dataPointsPerChunk * dataPointsPerChunk][];
// get the column data lengths
rawFullDataArrays[fullDataIndex] = dataColumn;
for (int x = 0; x < dataColumn.length; x++)
{
// this should be zero if the column doesn't have any data
int dataColumnLength = inputStream.readInt();
dataColumn[x] = new long[dataColumnLength];
}
// get the column data
for (int x = 0; x < dataColumn.length; x++)
{
if (dataColumn[x].length != 0)
{
// read in the data columns
for (int z = 0; z < dataColumn[x].length; z++)
{
dataColumn[x][z] = inputStream.readLong();
}
}
}
}
return rawFullDataArrays;
}
@Override
public void setDataPoints(long[][][] dataPoints)
{
LodUtil.assertTrue(this.sparseData.length == dataPoints.length, "Data point array length mismatch.");
this.isEmpty = false;
for (int arrayAccessorIndex = 0; arrayAccessorIndex < dataPoints.length; arrayAccessorIndex++)
{
if (dataPoints[arrayAccessorIndex] == null)
{
this.sparseData[arrayAccessorIndex] = null;
}
else if (this.sparseData[arrayAccessorIndex] == null)
{
int width = (int) Math.sqrt(dataPoints[arrayAccessorIndex].length);
this.sparseData[arrayAccessorIndex] = new FullDataArrayAccessor(this.mapping, dataPoints[arrayAccessorIndex], width);
}
else
{
for (int dataPointColIndex = 0; dataPointColIndex < dataPoints[arrayAccessorIndex].length; dataPointColIndex++)
{
long[] incomingColumn = dataPoints[arrayAccessorIndex][dataPointColIndex];
long[] destinationColumn = this.sparseData[arrayAccessorIndex].get(dataPointColIndex).getRaw();
// use the existing arrays if possible
if (incomingColumn.length == destinationColumn.length)
{
System.arraycopy(incomingColumn, 0, destinationColumn, 0, incomingColumn.length);
}
else
{
this.sparseData[arrayAccessorIndex].get(dataPointColIndex).setNew(incomingColumn);
}
}
}
}
}
@Override
public FullDataPointIdMap readIdMappings(long[][][] dataPoints, DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException
{
// mark the start of the ID data
int idMappingStartByte = inputStream.readInt();
if (idMappingStartByte != DATA_GUARD_BYTE)
{
// the file format is incorrect
throw new IOException("invalid data content end guard");
}
// deserialize the ID data
return FullDataPointIdMap.deserialize(inputStream, this.sectionPos, levelWrapper);
}
@Override
public void writeIdMappings(DhDataOutputStream dataOutputStream) throws IOException
{
dataOutputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE);
this.mapping.serialize(dataOutputStream);
}
@Override
public void setIdMapping(FullDataPointIdMap mappings) { this.mapping.mergeAndReturnRemappedEntityIds(mappings); }
//======//
// data //
//======//
@Override
public SingleColumnFullDataAccessor tryGet(int relativeX, int relativeZ) { return this.tryGetOrCreate(relativeX, relativeZ, false); }
@Override
public SingleColumnFullDataAccessor getOrCreate(int relativeX, int relativeZ) { return this.tryGetOrCreate(relativeX, relativeZ, true); }
private SingleColumnFullDataAccessor tryGetOrCreate(int relativeX, int relativeZ, boolean createIfMissing)
{
LodUtil.assertTrue(relativeX >= 0 && relativeX < SECTION_SIZE && relativeZ >= 0 && relativeZ < SECTION_SIZE);
int chunkX = relativeX / this.dataPointsPerSection;
int chunkZ = relativeZ / this.dataPointsPerSection;
FullDataArrayAccessor accessor = this.sparseData[chunkX * this.sectionCount + chunkZ];
if (accessor == null)
{
if (createIfMissing)
{
// create the missing data so the following get() will succeed
accessor = new FullDataArrayAccessor(this.mapping, new long[this.dataPointsPerSection * this.dataPointsPerSection][], this.dataPointsPerSection);
this.sparseData[chunkX * this.sectionCount + chunkZ] = accessor;
}
else
{
return null;
}
}
return accessor.get(relativeX % this.dataPointsPerSection, relativeZ % this.dataPointsPerSection);
}
//=========//
// getters //
//=========//
@Override
public DhSectionPos getSectionPos() { return this.sectionPos; }
@Override
public void resizeDataStructuresForRepopulation(DhSectionPos pos)
{
// update the position
this.sectionPos = pos;
this.sectionCount = BitShiftUtil.powerOfTwo(this.sectionPos.getDetailLevel() - SPARSE_UNIT_DETAIL);
this.dataPointsPerSection = SECTION_SIZE / this.sectionCount;
this.chunkPos = this.sectionPos.getMinCornerLodPos(SPARSE_UNIT_DETAIL);
// update the data container
int dataPointCount = this.sectionCount * this.sectionCount;
if (this.sparseData.length != dataPointCount)
{
this.sparseData = new FullDataArrayAccessor[this.sectionCount * this.sectionCount];
}
}
@Override
public byte getDataDetailLevel() { return (byte) (this.sectionPos.getDetailLevel() - SECTION_SIZE_OFFSET); }
@Override
public byte getDataFormatVersion() { return DATA_FORMAT_VERSION; }
@Override
public EDhApiWorldGenerationStep getWorldGenStep() { return this.worldGenStep; }
@Override
public FullDataPointIdMap getMapping() { return this.mapping; }
@Override
public boolean isEmpty() { return this.isEmpty; }
@Override
public void markNotEmpty() { this.isEmpty = false; }
@Override
public int getWidthInDataPoints() { return SECTION_SIZE; }
private int calculateOffset(int chunkX, int chunkZ)
{
int offsetX = chunkX - this.chunkPos.x;
int offsetZ = chunkZ - this.chunkPos.z;
LodUtil.assertTrue(offsetX >= 0 && offsetZ >= 0 && offsetX < this.sectionCount && offsetZ < this.sectionCount);
return offsetX * this.sectionCount + offsetZ;
}
//=============//
// data update //
//=============//
@Override
public void update(ChunkSizedFullDataAccessor chunkDataView)
{
int arrayOffset = this.calculateOffset(chunkDataView.chunkPos.x, chunkDataView.chunkPos.z);
FullDataArrayAccessor newArray = new FullDataArrayAccessor(this.mapping, new long[this.dataPointsPerSection * this.dataPointsPerSection][], this.dataPointsPerSection);
if (this.getDataDetailLevel() == chunkDataView.detailLevel)
{
chunkDataView.shadowCopyTo(newArray);
}
else
{
int count = this.dataPointsPerSection;
int dataPerCount = SPARSE_UNIT_SIZE / this.dataPointsPerSection;
for (int xOffset = 0; xOffset < count; xOffset++)
{
for (int zOffset = 0; zOffset < count; zOffset++)
{
SingleColumnFullDataAccessor column = newArray.get(xOffset, zOffset);
column.downsampleFrom(chunkDataView.subView(dataPerCount, xOffset * dataPerCount, zOffset * dataPerCount));
}
}
}
this.isEmpty = false;
this.sparseData[arrayOffset] = newArray;
}
// data sampling //
private void applyToFullDataSource(CompleteFullDataSource dataSource)
{
LodUtil.assertTrue(dataSource.getSectionPos().equals(this.sectionPos));
LodUtil.assertTrue(dataSource.getDataDetailLevel() == this.getDataDetailLevel());
for (int x = 0; x < this.sectionCount; x++)
{
for (int z = 0; z < this.sectionCount; z++)
{
FullDataArrayAccessor array = this.sparseData[x * this.sectionCount + z];
if (array == null)
continue;
// Otherwise, apply data to dataSource
dataSource.markNotEmpty();
FullDataArrayAccessor view = dataSource.subView(this.dataPointsPerSection, x * this.dataPointsPerSection, z * this.dataPointsPerSection);
array.shadowCopyTo(view);
}
}
}
public IFullDataSource tryPromotingToCompleteDataSource()
{
if (this.isEmpty)
{
return this;
}
// promotion can only succeed if every data column is present
for (FullDataArrayAccessor array : this.sparseData)
{
if (array == null)
{
return this;
}
}
this.isPromoted = true;
CompleteFullDataSource fullDataSource = CompleteFullDataSource.createEmpty(this.sectionPos);
this.applyToFullDataSource(fullDataSource);
return fullDataSource;
}
@Override
public boolean hasBeenPromoted() { return this.isPromoted; }
}
@@ -1,436 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.sources;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.FullDataArrayAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.SingleColumnFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IStreamableFullDataSource;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.DataSourceDto;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.util.BitSet;
/**
* Used for large incomplete LOD blocks. <Br>
* Handles incomplete full data with a detail level higher than
* {@link HighDetailIncompleteFullDataSource#MAX_SECTION_DETAIL}. <br><br>
*
* Formerly "SpottyFullDataSource".
*
* @see HighDetailIncompleteFullDataSource
* @see CompleteFullDataSource
* @see FullDataPointUtil
*/
public class LowDetailIncompleteFullDataSource extends FullDataArrayAccessor implements IIncompleteFullDataSource, IStreamableFullDataSource<IStreamableFullDataSource.FullDataSourceSummaryData, LowDetailIncompleteFullDataSource.StreamDataPointContainer>
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public static final byte SECTION_SIZE_OFFSET = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
/** measured in dataPoints */
public static final int WIDTH = BitShiftUtil.powerOfTwo(SECTION_SIZE_OFFSET);
public static final byte DATA_FORMAT_VERSION = 3;
public static final String DATA_TYPE_NAME = "LowDetailIncompleteFullDataSource";
@Override
public String getDataTypeName() { return DATA_TYPE_NAME; }
private DhSectionPos sectionPos;
private final BitSet isColumnNotEmpty;
private boolean isEmpty = true;
public EDhApiWorldGenerationStep worldGenStep = EDhApiWorldGenerationStep.EMPTY;
private boolean isPromoted = false;
//==============//
// constructors //
//==============//
public static LowDetailIncompleteFullDataSource createEmpty(DhSectionPos pos) { return new LowDetailIncompleteFullDataSource(pos); }
private LowDetailIncompleteFullDataSource(DhSectionPos sectionPos)
{
super(new FullDataPointIdMap(sectionPos), new long[WIDTH * WIDTH][0], WIDTH);
LodUtil.assertTrue(sectionPos.getDetailLevel() > HighDetailIncompleteFullDataSource.MAX_SECTION_DETAIL);
this.sectionPos = sectionPos;
this.isColumnNotEmpty = new BitSet(WIDTH * WIDTH);
this.worldGenStep = EDhApiWorldGenerationStep.EMPTY;
}
private LowDetailIncompleteFullDataSource(DhSectionPos pos, FullDataPointIdMap mapping, EDhApiWorldGenerationStep worldGenStep, BitSet isColumnNotEmpty, long[][] data)
{
super(mapping, data, WIDTH);
LodUtil.assertTrue(data.length == WIDTH * WIDTH);
this.sectionPos = pos;
this.isColumnNotEmpty = isColumnNotEmpty;
this.worldGenStep = worldGenStep;
this.isEmpty = false;
}
//=================//
// stream handling //
//=================//
@Override
public void writeSourceSummaryInfo(IDhLevel level, DhDataOutputStream outputStream) throws IOException
{
outputStream.writeInt(this.getDataDetailLevel());
outputStream.writeInt(this.width);
outputStream.writeInt(level.getMinY());
outputStream.writeByte(this.worldGenStep.value);
}
@Override
public FullDataSourceSummaryData readSourceSummaryInfo(DataSourceDto dto, DhDataInputStream inputStream, IDhLevel level) throws IOException
{
int dataDetailLevel = inputStream.readInt();
if (dataDetailLevel != dto.dataDetailLevel)
{
throw new IOException(LodUtil.formatLog("Data level mismatch: " + dataDetailLevel + " != " + dto.dataDetailLevel));
}
int width = inputStream.readInt();
if (width != WIDTH)
{
throw new IOException(LodUtil.formatLog("Section size mismatch: " + width + " != " + WIDTH + " (Currently only 1 section size is supported)"));
}
int minY = inputStream.readInt();
if (minY != level.getMinY())
{
LOGGER.warn("Data minY mismatch: " + minY + " != " + level.getMinY() + ". Will ignore data's y level");
}
EDhApiWorldGenerationStep worldGenStep = EDhApiWorldGenerationStep.fromValue(inputStream.readByte());
if (worldGenStep == null)
{
worldGenStep = EDhApiWorldGenerationStep.SURFACE;
LOGGER.warn("Missing WorldGenStep, defaulting to: " + worldGenStep.name());
}
return new FullDataSourceSummaryData(this.width, worldGenStep);
}
public void setSourceSummaryData(FullDataSourceSummaryData summaryData)
{
this.worldGenStep = summaryData.worldGenStep;
}
@Override
public boolean writeDataPoints(DhDataOutputStream dataOutputStream) throws IOException
{
if (this.isEmpty)
{
dataOutputStream.writeInt(IFullDataSource.NO_DATA_FLAG_BYTE);
return false;
}
dataOutputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE);
// data column presence
byte[] bytes = this.isColumnNotEmpty.toByteArray();
dataOutputStream.writeInt(bytes.length);
dataOutputStream.write(bytes);
// Data content
dataOutputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE);
for (int i = this.isColumnNotEmpty.nextSetBit(0); i >= 0; i = this.isColumnNotEmpty.nextSetBit(i + 1))
{
dataOutputStream.writeByte(this.dataArrays[i].length);
for (long dataPoint : this.dataArrays[i])
{
dataOutputStream.writeLong(dataPoint);
}
}
return true;
}
@Override
public StreamDataPointContainer readDataPoints(DataSourceDto dto, int width, DhDataInputStream inputStream) throws IOException
{
// is source empty flag
int dataPresentFlag = inputStream.readInt();
if (dataPresentFlag == IFullDataSource.NO_DATA_FLAG_BYTE)
{
// Section is empty
return null;
}
else if (dataPresentFlag != IFullDataSource.DATA_GUARD_BYTE)
{
throw new IOException("Invalid file format. Data Points guard byte expected: (no data) [" + IFullDataSource.NO_DATA_FLAG_BYTE + "] or (data present) [" + IFullDataSource.DATA_GUARD_BYTE + "], but found [" + dataPresentFlag + "].");
}
// data column presence
int length = inputStream.readInt();
if (length < 0 || length > (WIDTH * WIDTH / 8 + 64) * 2) // TODO replace magic numbers or comment what they mean
{
throw new IOException(LodUtil.formatLog("Spotty Flag BitSet size outside reasonable range: {} (expects {} to {})",
length, 1, WIDTH * WIDTH / 8 + 63));
}
byte[] bytes = new byte[length];
inputStream.readFully(bytes, 0, length);
BitSet isColumnNotEmpty = BitSet.valueOf(bytes);
// Data array content
long[][] dataPointArray = new long[WIDTH * WIDTH][];
dataPresentFlag = inputStream.readInt();
if (dataPresentFlag != IFullDataSource.DATA_GUARD_BYTE)
{
throw new IOException("invalid spotty flag end guard");
}
for (int xz = isColumnNotEmpty.nextSetBit(0); xz >= 0; xz = isColumnNotEmpty.nextSetBit(xz + 1))
{
long[] array = new long[inputStream.readByte()];
for (int y = 0; y < array.length; y++)
{
array[y] = inputStream.readLong();
}
dataPointArray[xz] = array;
}
return new StreamDataPointContainer(dataPointArray, isColumnNotEmpty);
}
@Override
public void setDataPoints(StreamDataPointContainer streamDataPointContainer)
{
long[][] dataPoints = streamDataPointContainer.dataPoints;
// copy over the datapoints
LodUtil.assertTrue(this.dataArrays.length == dataPoints.length, "Data point array length mismatch.");
System.arraycopy(dataPoints, 0, this.dataArrays, 0, dataPoints.length);
// overwrite the bitset
for (int i = 0; i < streamDataPointContainer.isColumnNotEmpty.length(); i++)
{
this.isColumnNotEmpty.set(i, streamDataPointContainer.isColumnNotEmpty.get(i));
}
this.isEmpty = false;
}
@Override
public void writeIdMappings(DhDataOutputStream outputStream) throws IOException
{
outputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE);
this.mapping.serialize(outputStream);
}
@Override
public FullDataPointIdMap readIdMappings(StreamDataPointContainer streamDataPointContainer, DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException
{
// Id mapping
int dataPresentFlag = inputStream.readInt();
if (dataPresentFlag != IFullDataSource.DATA_GUARD_BYTE)
{
throw new IOException("invalid ID mapping end guard");
}
return FullDataPointIdMap.deserialize(inputStream, this.sectionPos, levelWrapper);
}
@Override
public void setIdMapping(FullDataPointIdMap mappings) { this.mapping.mergeAndReturnRemappedEntityIds(mappings); }
//======//
// data //
//======//
@Override
public SingleColumnFullDataAccessor tryGet(int relativeX, int relativeZ) { return this.tryGetOrCreate(relativeX, relativeZ, false); }
@Override
public SingleColumnFullDataAccessor getOrCreate(int relativeX, int relativeZ) { return this.tryGetOrCreate(relativeX, relativeZ, true); }
private SingleColumnFullDataAccessor tryGetOrCreate(int relativeX, int relativeZ, boolean createIfMissing)
{
int notEmptyIndex = relativeX * WIDTH + relativeZ;
boolean columnEmpty = this.isColumnNotEmpty.get(notEmptyIndex);
// "create" the missing column if necessary
if (columnEmpty && createIfMissing)
{
this.isColumnNotEmpty.set(notEmptyIndex, true);
columnEmpty = false;
}
return !columnEmpty ? this.get(relativeX, relativeZ) : null;
}
//=====================//
// getters and setters //
//=====================//
@Override
public DhSectionPos getSectionPos() { return this.sectionPos; }
@Override
public void resizeDataStructuresForRepopulation(DhSectionPos pos)
{
// no data structures need to be changed, only the source's position
this.sectionPos = pos;
}
@Override
public byte getDataDetailLevel() { return (byte) (this.sectionPos.getDetailLevel() - SECTION_SIZE_OFFSET); }
@Override
public byte getDataFormatVersion() { return DATA_FORMAT_VERSION; }
@Override
public EDhApiWorldGenerationStep getWorldGenStep() { return this.worldGenStep; }
@Override
public boolean isEmpty() { return this.isEmpty; }
@Override
public void markNotEmpty() { this.isEmpty = false; }
@Override
public int getWidthInDataPoints() { return WIDTH; }
//===============//
// Data updating //
//===============//
@Override
public void update(ChunkSizedFullDataAccessor data)
{
LodUtil.assertTrue(this.sectionPos.overlapsExactly(data.getSectionPos()));
if (this.getDataDetailLevel() >= 4)
{
//FIXME: TEMPORARY
int chunkPerFull = 1 << (this.getDataDetailLevel() - 4);
if (data.chunkPos.x % chunkPerFull != 0 || data.chunkPos.z % chunkPerFull != 0)
{
return;
}
DhLodPos baseOffset = this.sectionPos.getMinCornerLodPos(this.getDataDetailLevel());
DhSectionPos dataOffset = data.getSectionPos().convertNewToDetailLevel(this.getDataDetailLevel());
int offsetX = dataOffset.getX() - baseOffset.x;
int offsetZ = dataOffset.getZ() - baseOffset.z;
LodUtil.assertTrue(offsetX >= 0 && offsetX < WIDTH && offsetZ >= 0 && offsetZ < WIDTH);
this.isEmpty = false;
SingleColumnFullDataAccessor columnFullDataAccessor = this.get(offsetX, offsetZ);
data.get(0, 0).deepCopyTo(columnFullDataAccessor);
this.isColumnNotEmpty.set(offsetX * WIDTH + offsetZ, columnFullDataAccessor.doesColumnExist());
}
else
{
LodUtil.assertNotReach();
//TODO;
}
}
@Override
public IFullDataSource tryPromotingToCompleteDataSource()
{
// promotion can only be completed if every column has data
if (this.isEmpty)
{
return this;
}
else if (this.isColumnNotEmpty.cardinality() != WIDTH * WIDTH)
{
return this;
}
this.isPromoted = true;
return new CompleteFullDataSource(this.sectionPos, this.mapping, this.dataArrays);
}
@Override
public boolean hasBeenPromoted() { return this.isPromoted; }
//================//
// helper classes //
//================//
/** used when reading the datapoints to and from the {@link IStreamableFullDataSource} */
public static class StreamDataPointContainer
{
public long[][] dataPoints;
public BitSet isColumnNotEmpty;
public StreamDataPointContainer(long[][] dataPoints, BitSet isColumnNotEmpty)
{
this.dataPoints = dataPoints;
this.isColumnNotEmpty = isColumnNotEmpty;
}
}
//========//
// unused //
//========//
public static boolean neededForPosition(DhSectionPos posToWrite, DhSectionPos posToTest)
{
if (!posToWrite.overlapsExactly(posToTest))
return false;
if (posToTest.getDetailLevel() > posToWrite.getDetailLevel())
return false;
if (posToWrite.getDetailLevel() - posToTest.getDetailLevel() <= SECTION_SIZE_OFFSET)
return true;
byte sectPerData = (byte) (1 << (posToWrite.getDetailLevel() - posToTest.getDetailLevel() - SECTION_SIZE_OFFSET));
return posToTest.getX() % sectPerData == 0 && posToTest.getZ() % sectPerData == 0;
}
}
@@ -1,107 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces;
import com.seibel.distanthorizons.core.file.IDataSource;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.IFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.SingleColumnFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.sql.DataSourceDto;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
/**
* Base for all Full Data Source objects. <br><br>
*
* Contains full DH data, methods related to file/stream reading/writing, and the data necessary to create {@link ColumnRenderSource}'s. <br>
* {@link IFullDataSource}'s will either implement or contain {@link IFullDataAccessor}'s.
*
* @see IFullDataAccessor
* @see IIncompleteFullDataSource
* @see IStreamableFullDataSource
*/
public interface IFullDataSource extends IDataSource<IDhLevel>
{
/**
* This is the byte put between different sections in the binary save file.
* The presence and absence of this byte indicates if the file is correctly formatted.
*/
int DATA_GUARD_BYTE = 0xFFFFFFFF;
/** indicates the binary save file represents an empty data source */
int NO_DATA_FLAG_BYTE = 0x00000001;
default void update(ChunkSizedFullDataAccessor chunkData, IDhLevel level) { this.update(chunkData); }
void update(ChunkSizedFullDataAccessor data);
boolean isEmpty();
void markNotEmpty();
/** AKA; the max relative position that {@link IFullDataSource#tryGet(int, int)} can accept for either X or Z */
int getWidthInDataPoints();
//======//
// data //
//======//
/**
* Attempts to get the data column for the given relative x and z position.
*
* @return null if the data doesn't exist
*/
@Nullable
SingleColumnFullDataAccessor tryGet(int relativeX, int relativeZ);
/**
* Attempts to get the data column for the given relative x and z position. <br>
* If no data exists yet an empty data column will be created.
*/
SingleColumnFullDataAccessor getOrCreate(int relativeX, int relativeZ);
FullDataPointIdMap getMapping();
//=======================//
// basic stream handling //
//=======================//
/**
* Should only be implemented by {@link IStreamableFullDataSource} to prevent potential stream read/write inconsistencies.
*
* @see IStreamableFullDataSource#populateFromStream(DataSourceDto, DhDataInputStream, IDhLevel)
*/
void populateFromStream(DataSourceDto dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException;
/**
* Should only be implemented by {@link IStreamableFullDataSource} to prevent potential stream read/write inconsistencies.
*
* @see IStreamableFullDataSource#repopulateFromStream(DataSourceDto, DhDataInputStream, IDhLevel)
*/
void repopulateFromStream(DataSourceDto dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException;
}
@@ -1,86 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.SingleColumnFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.LodUtil;
public interface IIncompleteFullDataSource extends IFullDataSource
{
/**
* Overwrites data in this object with non-null data from the input {@link IFullDataSource}. <br><br>
*
* This can be used to either merge same sized data sources or downsample to
*/
default void sampleFrom(IFullDataSource inputSource)
{
DhSectionPos inputPos = inputSource.getSectionPos();
DhSectionPos thisPos = this.getSectionPos();
LodUtil.assertTrue(inputPos.getDetailLevel() < thisPos.getDetailLevel(), "input data source at pos: ["+inputPos+"] has a lower detail level than this: ["+thisPos+"].");
LodUtil.assertTrue(inputPos.overlapsExactly(this.getSectionPos()), "input source at pos: ["+inputPos+"] (converted to ["+inputPos.convertNewToDetailLevel(thisPos.getDetailLevel())+"]) doesn't overlap with this source's pos: ["+thisPos+"].");
if (inputSource.isEmpty())
{
return;
}
this.markNotEmpty();
DhLodPos baseOffset = thisPos.getMinCornerLodPos(this.getDataDetailLevel());
DhSectionPos inputOffset = inputPos.convertNewToDetailLevel(this.getDataDetailLevel());
int offsetX = inputOffset.getX() - baseOffset.x;
int offsetZ = inputOffset.getZ() - baseOffset.z;
int numberOfDataPointsToUpdate = this.getWidthInDataPoints() / thisPos.getWidthCountForLowerDetailedSection(inputSource.getSectionPos().getDetailLevel()); // can be 0 if the input source is significantly smaller than this data source
// should be 1 at minimum, to prevent divide by zero errors (and because trying to get 0 or a fractional data point doesn't make any sense)
numberOfDataPointsToUpdate = Math.max(1, numberOfDataPointsToUpdate);
int inputFractionWidth = inputSource.getWidthInDataPoints() / numberOfDataPointsToUpdate;
for (int x = 0; x < numberOfDataPointsToUpdate; x++)
{
for (int z = 0; z < numberOfDataPointsToUpdate; z++)
{
SingleColumnFullDataAccessor thisDataColumn = this.getOrCreate(offsetX + x, offsetZ + z);
SingleColumnFullDataAccessor inputDataColumn = inputSource.tryGet(inputFractionWidth * x, inputFractionWidth * z);
if (inputDataColumn != null)
{
inputDataColumn.deepCopyTo(thisDataColumn);
}
}
}
}
/**
* Attempts to convert this {@link IIncompleteFullDataSource} into a {@link CompleteFullDataSource}.
*
* @return a new {@link CompleteFullDataSource} if successful, returns itself if not.
*/
IFullDataSource tryPromotingToCompleteDataSource();
boolean hasBeenPromoted();
}
@@ -1,161 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.DataSourceDto;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import java.io.IOException;
/**
* This interface holds the complete method list necessary for reading and writing a {@link IFullDataSource}
* to and from data streams. <br><br>
*
* This interface's purpose is to reduce the chance of accidentally mismatching read/write operation data types or content by splitting
* up each read/write method into small easy to understand chunks.
*
* @param <SummaryDataType> defines the object holding this data source's summary data, extends {@link IStreamableFullDataSource.FullDataSourceSummaryData}.
* @param <DataContainerType> defines the object holding the data points, probably long[][] or long[][][].
* {@link IStreamableFullDataSource#populateFromStream(DataSourceDto, DhDataInputStream, IDhLevel) populateFromStream}
* for the full reasoning.
*/
public interface IStreamableFullDataSource<SummaryDataType extends IStreamableFullDataSource.FullDataSourceSummaryData, DataContainerType> extends IFullDataSource
{
//=================//
// stream handling //
//=================//
/**
* Clears and then overwrites any data in this object with the data from the given file and stream.
* This is expected to be used with an existing {@link IStreamableFullDataSource} and can be used in place of a constructor to reuse an existing {@link IStreamableFullDataSource} object.
*
* @see IStreamableFullDataSource#populateFromStream
*/
@Override
default void repopulateFromStream(DataSourceDto dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
{
// clear/overwrite the old data
this.resizeDataStructuresForRepopulation(dto.pos);
this.getMapping().clear(dto.pos);
// set the new data
this.populateFromStream(dto, inputStream, level);
}
/**
* Overwrites any data in this object with the data from the given file and stream.
* This is expected to be used with an empty {@link IStreamableFullDataSource} and functions similar to a constructor.
*/
@Override
default void populateFromStream(DataSourceDto dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
{
SummaryDataType summaryData = this.readSourceSummaryInfo(dto, inputStream, level);
this.setSourceSummaryData(summaryData);
DataContainerType dataPoints = this.readDataPoints(dto, summaryData.dataWidth, inputStream);
if (dataPoints == null)
{
return;
}
this.setDataPoints(dataPoints);
FullDataPointIdMap mapping = this.readIdMappings(dataPoints, inputStream, level.getLevelWrapper());
this.setIdMapping(mapping);
}
@Override
default void writeToStream(DhDataOutputStream outputStream, IDhLevel level) throws IOException
{
this.writeSourceSummaryInfo(level, outputStream);
boolean hasData = this.writeDataPoints(outputStream);
if (!hasData)
{
return;
}
this.writeIdMappings(outputStream);
}
/** Note: this should only be used if the data source is being reused. Normally data sources shouldn't change. */
void resizeDataStructuresForRepopulation(DhSectionPos pos);
/**
* Includes information about the source file that doesn't need to be saved in each data point. Like the source's size and y-level.
*/
void writeSourceSummaryInfo(IDhLevel level, DhDataOutputStream outputStream) throws IOException;
/**
* Confirms that the given {@link DataSourceDto} is valid for this {@link IStreamableFullDataSource}. <br>
* This specifically checks any fields that should be set when the {@link IStreamableFullDataSource} was first constructed.
*
* @throws IOException if the {@link DataSourceDto} isn't valid for this object.
*/
SummaryDataType readSourceSummaryInfo(DataSourceDto dto, DhDataInputStream inputStream, IDhLevel level) throws IOException;
void setSourceSummaryData(SummaryDataType summaryData);
/** @return true if any data points were present and written, false if this object was empty */
boolean writeDataPoints(DhDataOutputStream outputStream) throws IOException;
/** @return null if no data points were present */
DataContainerType readDataPoints(DataSourceDto dto, int width, DhDataInputStream inputStream) throws IOException;
void setDataPoints(DataContainerType dataPoints);
void writeIdMappings(DhDataOutputStream outputStream) throws IOException;
FullDataPointIdMap readIdMappings(DataContainerType dataPoints, DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException;
void setIdMapping(FullDataPointIdMap mappings);
//================//
// helper classes //
//================//
/**
* This holds information that is relevant to the entire source and isn't stored in the data points. <br>
* Example: minimum height, detail level, source type, etc.
*/
class FullDataSourceSummaryData
{
public final int dataWidth;
public EDhApiWorldGenerationStep worldGenStep;
public FullDataSourceSummaryData(int dataWidth, EDhApiWorldGenerationStep worldGenStep)
{
this.dataWidth = dataWidth;
this.worldGenStep = worldGenStep;
}
}
}
@@ -20,27 +20,24 @@
package com.seibel.distanthorizons.core.dataObjects.render;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.SingleColumnFullDataAccessor;
import com.seibel.distanthorizons.core.file.IDataSource;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.file.DataSourcePool;
import com.seibel.distanthorizons.core.file.IDataSource;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnQuadView;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.IColumnDataView;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;
/**
@@ -56,34 +53,20 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
public static final byte SECTION_SIZE_OFFSET = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
public static final int SECTION_SIZE = BitShiftUtil.powerOfTwo(SECTION_SIZE_OFFSET);
public static final byte DATA_FORMAT_VERSION = 1;
@Override
public byte getDataFormatVersion() { return DATA_FORMAT_VERSION; }
public static final String DATA_NAME = "ColumnRenderSource";
@Override
public String getDataTypeName() { return DATA_NAME; }
/**
* This is the byte put between different sections in the binary save file.
* The presence and absence of this byte indicates if the file is correctly formatted.
*/
public static final int DATA_GUARD_BYTE = 0xFFFFFFFF;
/** indicates the binary save file represents an empty data source */
public static final int NO_DATA_FLAG_BYTE = 0x00000001;
public static final DataSourcePool<ColumnRenderSource, IDhClientLevel> DATA_SOURCE_POOL = new DataSourcePool<>(ColumnRenderSource::createEmptyRenderSource, null /* data source prep/cleanup needs to be done outside the pool since it requires additional inputs */);
/** will be zero if an empty data source was created */
public int verticalDataCount;
public final DhSectionPos sectionPos;
public final int yOffset;
public long pos;
public int yOffset;
public long[] renderDataContainer;
public LongArrayList renderDataContainer;
public final DebugSourceFlag[] debugSourceFlags;
private boolean isEmpty = true;
public EDhApiWorldGenerationStep worldGenStep;
public AtomicLong localVersion = new AtomicLong(0); // used to track changes to the data source, so that buffers can be updated when necessary
@@ -93,44 +76,53 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
// constructors //
//==============//
public static ColumnRenderSource createEmptyRenderSource(DhSectionPos sectionPos) { return new ColumnRenderSource(sectionPos, 0, 0); }
/**
* This is separate from {@link DataSourcePool#getPooledSource(long, boolean)}
* because we need to pass in a couple extra values,
* specifically maxVerticalSize and yOffset.
*/
public static ColumnRenderSource getPooledRenderSource(long pos, int maxVerticalSize, int yOffset, boolean clearData)
{
ColumnRenderSource renderSource = DATA_SOURCE_POOL.getPooledSource(pos);
// set necessary properties
renderSource.pos = pos;
renderSource.verticalDataCount = maxVerticalSize;
renderSource.yOffset = yOffset;
// resize the array if necessary
int dataArraySize = SECTION_SIZE * SECTION_SIZE * maxVerticalSize;
renderSource.renderDataContainer.ensureCapacity(dataArraySize);
while (renderSource.renderDataContainer.size() < dataArraySize)
{
renderSource.renderDataContainer.add(0);
}
if (clearData)
{
Arrays.fill(renderSource.renderDataContainer.elements(), 0);
Arrays.fill(renderSource.debugSourceFlags, null);
}
return renderSource;
}
private static ColumnRenderSource createEmptyRenderSource(long sectionPos) { return new ColumnRenderSource(sectionPos, 0, 0); }
/**
* Creates an empty ColumnRenderSource.
*
* @param sectionPos the relative position of the container
* @param pos the relative position of the container
* @param maxVerticalSize the maximum vertical size of the container
*/
public ColumnRenderSource(DhSectionPos sectionPos, int maxVerticalSize, int yOffset)
private ColumnRenderSource(long pos, int maxVerticalSize, int yOffset)
{
this.verticalDataCount = maxVerticalSize;
this.renderDataContainer = new long[SECTION_SIZE * SECTION_SIZE * this.verticalDataCount];
this.renderDataContainer = new LongArrayList(new long[SECTION_SIZE * SECTION_SIZE * this.verticalDataCount]);
this.debugSourceFlags = new DebugSourceFlag[SECTION_SIZE * SECTION_SIZE];
this.sectionPos = sectionPos;
this.pos = pos;
this.yOffset = yOffset;
this.worldGenStep = EDhApiWorldGenerationStep.EMPTY;
}
/**
* Creates a new ColumnRenderSource from the parsedColumnData.
*
* @throws IOException if the DataInputStream's detail level isn't what was expected
*/
public ColumnRenderSource(DhSectionPos sectionPos, ColumnRenderSourceLoader.ParsedColumnData parsedColumnData, IDhLevel level) throws IOException
{
if (sectionPos.getDetailLevel() - SECTION_SIZE_OFFSET != parsedColumnData.detailLevel)
{
throw new IOException("Invalid data: detail level does not match");
}
this.sectionPos = sectionPos;
this.yOffset = level.getMinY();
this.verticalDataCount = parsedColumnData.verticalSize;
this.renderDataContainer = parsedColumnData.dataContainer;
this.worldGenStep = parsedColumnData.worldGenStep;
this.isEmpty = parsedColumnData.isEmpty;
this.debugSourceFlags = new DebugSourceFlag[SECTION_SIZE * SECTION_SIZE];
this.fillDebugFlag(0, 0, SECTION_SIZE, SECTION_SIZE, DebugSourceFlag.FILE);
}
@@ -139,65 +131,7 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
// datapoint manipulation //
//========================//
public void clearDataPoint(int posX, int posZ)
{
for (int verticalIndex = 0; verticalIndex < this.verticalDataCount; verticalIndex++)
{
this.renderDataContainer[posX * SECTION_SIZE * this.verticalDataCount + posZ * this.verticalDataCount + verticalIndex] = RenderDataPointUtil.EMPTY_DATA;
}
}
public boolean setDataPoint(long data, int posX, int posZ, int verticalIndex)
{
this.renderDataContainer[posX * SECTION_SIZE * this.verticalDataCount + posZ * this.verticalDataCount + verticalIndex] = data;
return true;
}
public boolean copyVerticalData(IColumnDataView newData, int posX, int posZ, boolean overwriteDataWithSameGenerationMode)
{
if (DO_SAFETY_CHECKS)
{
if (newData.size() != this.verticalDataCount)
throw new IllegalArgumentException("newData size not the same as this column's vertical size");
if (posX < 0 || posX >= SECTION_SIZE)
throw new IllegalArgumentException("X position is out of bounds");
if (posZ < 0 || posZ >= SECTION_SIZE)
throw new IllegalArgumentException("Z position is out of bounds");
}
int dataOffset = posX * SECTION_SIZE * this.verticalDataCount + posZ * this.verticalDataCount;
int compare = RenderDataPointUtil.compareDatapointPriority(newData.get(0), this.renderDataContainer[dataOffset]);
if (overwriteDataWithSameGenerationMode)
{
if (compare < 0)
{
return false;
}
}
else
{
if (compare <= 0)
{
return false;
}
}
// copy the newData into this column's data
newData.copyTo(this.renderDataContainer, dataOffset, newData.size());
return true;
}
public long getFirstDataPoint(int posX, int posZ) { return getDataPoint(posX, posZ, 0); }
public long getDataPoint(int posX, int posZ, int verticalIndex) { return this.renderDataContainer[posX * SECTION_SIZE * this.verticalDataCount + posZ * this.verticalDataCount + verticalIndex]; }
public long[] getVerticalDataPointArray(int posX, int posZ)
{
long[] result = new long[this.verticalDataCount];
int index = posX * SECTION_SIZE * this.verticalDataCount + posZ * this.verticalDataCount;
System.arraycopy(this.renderDataContainer, index, result, 0, this.verticalDataCount);
return result;
}
public long getDataPoint(int posX, int posZ, int verticalIndex) { return this.renderDataContainer.getLong(posX * SECTION_SIZE * this.verticalDataCount + posZ * this.verticalDataCount + verticalIndex); }
public ColumnArrayView getVerticalDataPointView(int posX, int posZ)
{
@@ -209,201 +143,70 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
public ColumnQuadView getFullQuadView() { return this.getQuadViewOverRange(0, 0, SECTION_SIZE, SECTION_SIZE); }
public ColumnQuadView getQuadViewOverRange(int quadX, int quadZ, int quadXSize, int quadZSize) { return new ColumnQuadView(this.renderDataContainer, SECTION_SIZE, this.verticalDataCount, quadX, quadZ, quadXSize, quadZSize); }
public int getVerticalSize() { return this.verticalDataCount; }
//========================//
// data update and output //
//========================//
//=============//
// data update //
//=============//
@Override
public void writeToStream(DhDataOutputStream outputStream, IDhClientLevel level) throws IOException { this.writeToStream(outputStream); }
public void writeToStream(DhDataOutputStream outputStream) throws IOException
public boolean update(FullDataSourceV2 inputFullDataSource, IDhClientLevel level)
{
outputStream.flush();
outputStream.writeByte(this.getDataDetailLevel());
outputStream.writeInt(this.verticalDataCount);
if (this.isEmpty)
{
// no data is present
outputStream.writeByte(NO_DATA_FLAG_BYTE);
}
else
{
// data is present
outputStream.writeByte(DATA_GUARD_BYTE);
outputStream.writeInt(this.yOffset);
// write the data for each column
for (int xz = 0; xz < SECTION_SIZE * SECTION_SIZE; xz++)
{
for (int y = 0; y < this.verticalDataCount; y++)
{
long currentDatapoint = this.renderDataContainer[xz * this.verticalDataCount + y];
outputStream.writeLong(Long.reverseBytes(currentDatapoint)); // the reverse bytes is necessary to ensure the data is read in correctly
}
}
}
outputStream.writeByte(DATA_GUARD_BYTE);
outputStream.writeByte(this.worldGenStep.value);
outputStream.flush();
}
/** Overrides any data that has not been written directly using write(). Skips empty source dataPoints. */
public void updateFromRenderSource(ColumnRenderSource renderSource)
{
// validate we are writing for the same location
LodUtil.assertTrue(renderSource.sectionPos.equals(this.sectionPos));
// change the vertical size if necessary (this can happen if the vertical quality was changed in the config)
this.clearAndChangeVerticalSize(renderSource.verticalDataCount);
// validate both objects have the same number of dataPoints
LodUtil.assertTrue(renderSource.verticalDataCount == this.verticalDataCount);
if (renderSource.isEmpty)
{
// the source is empty, don't attempt to update anything
return;
}
// the source isn't empty, this object won't be empty after the method finishes
this.isEmpty = false;
localVersion.incrementAndGet();
}
/**
* If the newVerticalSize is different than the current verticalSize,
* this will delete any data currently in this object and re-size it. <Br>
* Otherwise this method will do nothing.
*/
private void clearAndChangeVerticalSize(int newVerticalSize)
{
if (newVerticalSize != this.verticalDataCount)
{
this.verticalDataCount = newVerticalSize;
this.renderDataContainer = new long[SECTION_SIZE * SECTION_SIZE * this.verticalDataCount];
this.localVersion.incrementAndGet();
}
}
@Override
public void update(ChunkSizedFullDataAccessor chunkDataView, IDhClientLevel level)
{
final String errorMessagePrefix = "Unable to complete fastWrite for RenderSource pos: [" + this.sectionPos + "] and chunk pos: [" + chunkDataView.chunkPos + "]. Error:";
final DhSectionPos renderSourcePos = this.getSectionPos();
final int sourceBlockX = renderSourcePos.getMinCornerLodPos().getCornerBlockPos().x;
final int sourceBlockZ = renderSourcePos.getMinCornerLodPos().getCornerBlockPos().z;
// offset between the incoming chunk data and this render source
final int blockOffsetX = (chunkDataView.chunkPos.x * LodUtil.CHUNK_WIDTH) - sourceBlockX;
final int blockOffsetZ = (chunkDataView.chunkPos.z * LodUtil.CHUNK_WIDTH) - sourceBlockZ;
final int sourceDataPointBlockWidth = BitShiftUtil.powerOfTwo(this.getDataDetailLevel());
final String errorMessagePrefix = "Unable to complete update for RenderSource pos: [" + this.pos + "] and pos: [" + inputFullDataSource.getPos() + "]. Error:";
boolean dataChanged = false;
if (chunkDataView.detailLevel == this.getDataDetailLevel())
if (DhSectionPos.getDetailLevel(inputFullDataSource.getPos()) == DhSectionPos.getDetailLevel(this.pos))
{
this.markNotEmpty();
// confirm the render source contains this chunk
if (blockOffsetX < 0
|| blockOffsetX + LodUtil.CHUNK_WIDTH > this.getWidthInDataPoints()
|| blockOffsetZ < 0
|| blockOffsetZ + LodUtil.CHUNK_WIDTH > this.getWidthInDataPoints())
try
{
LOGGER.warn(errorMessagePrefix+"Data offset is out of bounds.");
return;
}
if (Thread.interrupted())
{
LOGGER.warn(errorMessagePrefix+"write interrupted.");
return;
}
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
if (Thread.interrupted())
{
ColumnArrayView columnArrayView = this.getVerticalDataPointView(blockOffsetX + x, blockOffsetZ + z);
int hash = columnArrayView.getDataHash();
SingleColumnFullDataAccessor fullArrayView = chunkDataView.get(x, z);
FullDataToRenderDataTransformer.convertColumnData(level,
sourceBlockX + sourceDataPointBlockWidth * (blockOffsetX + x),
sourceBlockZ + sourceDataPointBlockWidth * (blockOffsetZ + z),
columnArrayView, fullArrayView);
dataChanged |= hash != columnArrayView.getDataHash();
LOGGER.warn(errorMessagePrefix + "write interrupted.");
return false;
}
DhBlockPos2D centerBlockPos = DhSectionPos.getCenterBlockPos(inputFullDataSource.getPos());
int halfBlockWidth = DhSectionPos.getBlockWidth(inputFullDataSource.getPos()) / 2;
DhBlockPos2D minBlockPos = new DhBlockPos2D(centerBlockPos.x - halfBlockWidth, centerBlockPos.z - halfBlockWidth);
for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
{
for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
{
ColumnArrayView columnArrayView = this.getVerticalDataPointView(x, z);
int columnHash = columnArrayView.getDataHash();
LongArrayList dataColumn = inputFullDataSource.get(x, z);
EDhApiWorldGenerationStep worldGenStep = inputFullDataSource.getWorldGenStepAtRelativePos(x, z);
if (dataColumn != null && worldGenStep != EDhApiWorldGenerationStep.EMPTY)
{
FullDataToRenderDataTransformer.convertColumnData(
level, inputFullDataSource.mapping,
minBlockPos.x + x,
minBlockPos.z + z,
columnArrayView, dataColumn);
dataChanged |= columnHash != columnArrayView.getDataHash();
this.fillDebugFlag(x, z, 1, 1, ColumnRenderSource.DebugSourceFlag.DIRECT);
}
}
}
}
this.fillDebugFlag(blockOffsetX, blockOffsetZ, LodUtil.CHUNK_WIDTH, LodUtil.CHUNK_WIDTH, ColumnRenderSource.DebugSourceFlag.DIRECT);
}
else if (chunkDataView.detailLevel < this.getDataDetailLevel() && this.getDataDetailLevel() <= chunkDataView.getSectionPos().getDetailLevel())
{
this.markNotEmpty();
// multiple chunk data points converting to 1 column data point
DhLodPos dataCornerPos = chunkDataView.getSectionPos().getMinCornerLodPos(chunkDataView.detailLevel);
DhLodPos sourceCornerPos = renderSourcePos.getMinCornerLodPos(this.getDataDetailLevel());
DhLodPos sourceStartingChangePos = dataCornerPos.convertToDetailLevel(this.getDataDetailLevel());
int relStartX = Math.floorMod(sourceStartingChangePos.x, this.getWidthInDataPoints());
int relStartZ = Math.floorMod(sourceStartingChangePos.z, this.getWidthInDataPoints());
int dataToSourceScale = sourceCornerPos.getWidthAtDetail(chunkDataView.detailLevel);
int columnsInChunk = chunkDataView.getSectionPos().getWidthCountForLowerDetailedSection(this.getDataDetailLevel());
for (int xOffset = 0; xOffset < columnsInChunk; xOffset++)
catch (Exception e)
{
for (int zOffset = 0; zOffset < columnsInChunk; zOffset++)
{
int relSourceX = relStartX + xOffset;
int relSourceZ = relStartZ + zOffset;
ColumnArrayView columnArrayView = this.getVerticalDataPointView(relSourceX, relSourceZ);
int hash = columnArrayView.getDataHash();
SingleColumnFullDataAccessor fullArrayView = chunkDataView.get(xOffset * dataToSourceScale, zOffset * dataToSourceScale);
FullDataToRenderDataTransformer.convertColumnData(level,
sourceBlockX + sourceDataPointBlockWidth * relSourceX,
sourceBlockZ + sourceDataPointBlockWidth * relSourceZ,
columnArrayView, fullArrayView);
dataChanged |= hash != columnArrayView.getDataHash();
}
LOGGER.error(errorMessagePrefix + e.getMessage(), e);
}
this.fillDebugFlag(relStartX, relStartZ, columnsInChunk, columnsInChunk, ColumnRenderSource.DebugSourceFlag.DIRECT);
}
else if (chunkDataView.getSectionPos().getDetailLevel() < this.getDataDetailLevel())
{
// The entire chunk is being converted to a single column data point, possibly.
DhLodPos dataCornerPos = chunkDataView.getSectionPos().getMinCornerLodPos(chunkDataView.detailLevel);
DhLodPos sourceCornerPos = renderSourcePos.getMinCornerLodPos(this.getDataDetailLevel());
DhLodPos sourceStartingChangePos = dataCornerPos.convertToDetailLevel(this.getDataDetailLevel());
int chunksPerColumn = sourceStartingChangePos.getWidthAtDetail(chunkDataView.getSectionPos().getDetailLevel());
if (chunkDataView.getSectionPos().getX() % chunksPerColumn != 0 || chunkDataView.getSectionPos().getZ() % chunksPerColumn != 0)
{
return; // not a multiple of the column size, so no change
}
int relStartX = Math.floorMod(sourceStartingChangePos.x, this.getWidthInDataPoints());
int relStartZ = Math.floorMod(sourceStartingChangePos.z, this.getWidthInDataPoints());
ColumnArrayView columnArrayView = this.getVerticalDataPointView(relStartX, relStartZ);
int hash = columnArrayView.getDataHash();
SingleColumnFullDataAccessor fullArrayView = chunkDataView.get(0, 0);
FullDataToRenderDataTransformer.convertColumnData(level, dataCornerPos.x * sourceDataPointBlockWidth,
dataCornerPos.z * sourceDataPointBlockWidth,
columnArrayView, fullArrayView);
dataChanged = hash != columnArrayView.getDataHash();
this.fillDebugFlag(relStartX, relStartZ, 1, 1, ColumnRenderSource.DebugSourceFlag.DIRECT);
}
if (dataChanged)
{
this.localVersion.incrementAndGet();
this.markNotEmpty();
}
return dataChanged;
}
@@ -412,39 +215,11 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
// data helper methods //
//=====================//
public boolean doesDataPointExist(int posX, int posZ) { return RenderDataPointUtil.doesDataPointExist(this.getFirstDataPoint(posX, posZ)); }
public void generateData(ColumnRenderSource lowerDataContainer, int posX, int posZ)
{
ColumnArrayView outputView = this.getVerticalDataPointView(posX, posZ);
ColumnQuadView quadView = lowerDataContainer.getQuadViewOverRange(posX * 2, posZ * 2, 2, 2);
outputView.mergeMultiDataFrom(quadView);
}
public int getMaxLodCount() { return SECTION_SIZE * SECTION_SIZE * this.getVerticalSize(); }
public long getRoughRamUsageInBytes() { return (long) this.renderDataContainer.length * Long.BYTES; }
public DhSectionPos getSectionPos() { return this.sectionPos; }
public Long getPos() { return this.pos; }
@Override
public String getPrimaryKeyString() { return this.sectionPos.serialize(); }
public Long getKey() { return this.pos; }
public byte getDataDetailLevel() { return (byte) (this.sectionPos.getDetailLevel() - SECTION_SIZE_OFFSET); }
@Override
public EDhApiWorldGenerationStep getWorldGenStep() { return EDhApiWorldGenerationStep.EMPTY; }
/** @return how many data points wide this {@link ColumnRenderSource} is. */
public int getWidthInDataPoints() { return BitShiftUtil.powerOfTwo(this.getDetailOffset()); }
public byte getDetailOffset() { return SECTION_SIZE_OFFSET; }
public byte getRenderDataFormatVersion() { return DATA_FORMAT_VERSION; }
/**
* Whether this object is still valid. If not, a new one should be created.
* TODO this will be necessary for dedicated multiplayer support, if the server has newer data this section should no longer be valid
*/
public boolean isValid() { return true; }
public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - SECTION_SIZE_OFFSET); }
public boolean isEmpty() { return this.isEmpty; }
public void markNotEmpty() { this.isEmpty = false; }
@@ -493,7 +268,6 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
this.debugSourceFlags[x * SECTION_SIZE + z] = flag;
}
}
localVersion.incrementAndGet();
}
public DebugSourceFlag debugGetFlag(int ox, int oz) { return this.debugSourceFlags[ox * SECTION_SIZE + oz]; }
@@ -512,7 +286,7 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
String SUBDATA_DELIMITER = ",";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(this.sectionPos);
stringBuilder.append(this.pos);
stringBuilder.append(LINE_DELIMITER);
int size = 1;
@@ -538,6 +312,12 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
return stringBuilder.toString();
}
@Override
public void close() throws Exception
{
DATA_SOURCE_POOL.returnPooledDataSource(this);
}
//==============//
@@ -548,7 +328,6 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
{
FULL(ColorUtil.BLUE),
DIRECT(ColorUtil.WHITE),
SPARSE(ColorUtil.YELLOW),
FILE(ColorUtil.BROWN);
public final int color;
@@ -1,171 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.dataObjects.render;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.sql.DataSourceDto;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Handles loading and parsing {@link DataSourceDto}s to create {@link ColumnRenderSource}s. <br><br>
*
* Please see the {@link ColumnRenderSourceLoader#loadRenderSource} method to see what
* file versions this class can handle.
*/
public class ColumnRenderSourceLoader
{
public static ColumnRenderSourceLoader INSTANCE = new ColumnRenderSourceLoader();
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private ColumnRenderSourceLoader() { }
public ColumnRenderSource loadRenderSource(DataSourceDto dto, DhDataInputStream inputStream, IDhLevel level) throws IOException
{
int dataFileVersion = dto.binaryDataFormatVersion;
switch (dataFileVersion)
{
case 1:
ParsedColumnData parsedColumnData = readDataV1(inputStream, level.getMinY());
return new ColumnRenderSource(dto.pos, parsedColumnData, level);
default:
throw new IOException("Invalid Data: The data version [" + dataFileVersion + "] is not supported");
}
}
//========================//
// versioned file parsing //
//========================//
/**
* @param inputStream Expected format: 1st byte: detail level, 2nd byte: vertical size, 3rd byte on: column data
* @throws IOException if there was an issue reading the stream
*/
private static ParsedColumnData readDataV1(DhDataInputStream inputStream, int expectedYOffset) throws IOException
{
// TODO move into ColumnRenderSource
byte detailLevel = inputStream.readByte();
int verticalDataCount = inputStream.readInt();
if (verticalDataCount <= 0)
{
throw new IOException("Invalid data: vertical size must be 0 or greater");
}
int maxNumberOfDataPoints = ColumnRenderSource.SECTION_SIZE * ColumnRenderSource.SECTION_SIZE * verticalDataCount;
byte dataPresentFlag = inputStream.readByte();
if (dataPresentFlag != ColumnRenderSource.NO_DATA_FLAG_BYTE && dataPresentFlag != ColumnRenderSource.DATA_GUARD_BYTE)
{
throw new IOException("Incorrect render file format. Expected either: NO_DATA_FLAG_BYTE [" + ColumnRenderSource.NO_DATA_FLAG_BYTE + "] or DATA_GUARD_BYTE [" + ColumnRenderSource.DATA_GUARD_BYTE + "], Found: [" + dataPresentFlag + "]");
}
else if (dataPresentFlag == ColumnRenderSource.NO_DATA_FLAG_BYTE)
{
// no data is present
return new ParsedColumnData(detailLevel, verticalDataCount, EDhApiWorldGenerationStep.EMPTY, new long[maxNumberOfDataPoints], true);
}
else
{
// data is present
int fileYOffset = inputStream.readInt();
if (fileYOffset != expectedYOffset)
{
throw new IOException("Invalid data: yOffset is incorrect. Expected: [" + expectedYOffset + "], found: [" + fileYOffset + "].");
}
// read the column data
byte[] rawByteData = new byte[maxNumberOfDataPoints * Long.BYTES];
ByteBuffer columnDataByteBuffer = ByteBuffer.wrap(rawByteData).order(ByteOrder.LITTLE_ENDIAN);
inputStream.readFully(rawByteData);
// parse the column data
long[] dataPoints = new long[maxNumberOfDataPoints];
columnDataByteBuffer.asLongBuffer().get(dataPoints);
boolean isEmpty = true;
for (long dataPoint : dataPoints)
{
if (dataPoint != 0)
{
isEmpty = false;
break;
}
}
byte guardByteFlag = inputStream.readByte();
if (guardByteFlag != ColumnRenderSource.DATA_GUARD_BYTE)
{
throw new IOException("invalid world gen step end guard");
}
EDhApiWorldGenerationStep worldGenStep = EDhApiWorldGenerationStep.fromValue(inputStream.readByte());
if (worldGenStep == null)
{
LOGGER.warn("Missing WorldGenStep, defaulting to: " + EDhApiWorldGenerationStep.SURFACE.name());
worldGenStep = EDhApiWorldGenerationStep.SURFACE;
}
return new ParsedColumnData(detailLevel, verticalDataCount, worldGenStep, dataPoints, isEmpty);
}
}
public static class ParsedColumnData
{
public final byte detailLevel;
public final int verticalSize;
public final EDhApiWorldGenerationStep worldGenStep;
public final long[] dataContainer;
public final boolean isEmpty;
public ParsedColumnData(byte detailLevel, int verticalSize, EDhApiWorldGenerationStep worldGenStep, long[] dataContainer, boolean isEmpty)
{
this.detailLevel = detailLevel;
this.verticalSize = verticalSize;
this.worldGenStep = worldGenStep;
this.dataContainer = dataContainer;
this.isEmpty = isEmpty;
}
}
}
@@ -151,6 +151,7 @@ public final class BufferQuad
{
if (quad.hasError || this.hasError)
return false;
// only merge quads that are in the same direction
if (this.direction != quad.direction)
return false;
@@ -284,10 +285,17 @@ public final class BufferQuad
if (thisPerpendicularCompareStartPos < otherPerpendicularCompareStartPos + otherPerpendicularCompareWidth)
{
// these quads are overlapping, they can't be merged
//EVENT_LOGGER.warn("Overlapping quads detected!");
quad.hasError = true;
this.hasError = true;
// Overlapping quads appear to render correctly, why are we marking them as errored?
// Is it possible the wrong quad will be extended thus the wrong color is rendered?
// Or is that the height/depth might be wrong?
if (Config.Client.Advanced.Debugging.showOverlappingQuadErrors.get())
{
quad.hasError = true;
this.hasError = true;
}
}
return false;
}
@@ -22,7 +22,9 @@ package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
@@ -63,13 +65,12 @@ public class ColumnBox
// cave culling prevention
// prevents certain faces from being culled underground that should be allowed
if (builder.skipQuadsWithZeroSkylight
&& 0 == skyLight
&& builder.skyLightCullingBelow > maxY
&&
(
(RenderDataPointUtil.getAlpha(topData) < 255 && RenderDataPointUtil.getYMax(topData) >= builder.skyLightCullingBelow)
|| (RenderDataPointUtil.getYMin(topData) >= builder.skyLightCullingBelow)
|| !RenderDataPointUtil.doesDataPointExist(topData)
&& 0 == skyLight
&& builder.skyLightCullingBelow > maxY
&& (
(RenderDataPointUtil.getAlpha(topData) < 255 && RenderDataPointUtil.getYMax(topData) >= builder.skyLightCullingBelow)
|| (RenderDataPointUtil.getYMin(topData) >= builder.skyLightCullingBelow)
|| !RenderDataPointUtil.doesDataPointExist(topData)
)
)
{
@@ -124,7 +125,7 @@ public class ColumnBox
// add an adjacent face if this is opaque face or transparent over the void
if (!isTransparent || overVoid)
{
builder.addQuadAdj(EDhDirection.NORTH, x, minY, z, xSize, ySize, color, irisBlockMaterialId, (byte) 15, blockLight);
builder.addQuadAdj(EDhDirection.NORTH, x, minY, z, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
}
}
else if (adjDataNorth.length == 1)
@@ -151,7 +152,7 @@ public class ColumnBox
if (adjDataSouth == null)
{
if (!isTransparent || overVoid)
builder.addQuadAdj(EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize, color, irisBlockMaterialId, (byte) 15, blockLight);
builder.addQuadAdj(EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
}
else if (adjDataSouth.length == 1)
{
@@ -178,7 +179,7 @@ public class ColumnBox
if (adjDataWest == null)
{
if (!isTransparent || overVoid)
builder.addQuadAdj(EDhDirection.WEST, x, minY, z, zSize, ySize, color, irisBlockMaterialId, (byte) 15, blockLight);
builder.addQuadAdj(EDhDirection.WEST, x, minY, z, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
}
else if (adjDataWest.length == 1)
{
@@ -204,7 +205,7 @@ public class ColumnBox
if (adjData[EDhDirection.EAST.ordinal() - 2] == null)
{
if (!isTransparent || overVoid)
builder.addQuadAdj(EDhDirection.EAST, maxX, minY, z, zSize, ySize, color, irisBlockMaterialId, (byte) 15, blockLight);
builder.addQuadAdj(EDhDirection.EAST, maxX, minY, z, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
}
else if (adjDataEast.length == 1)
{
@@ -236,7 +237,7 @@ public class ColumnBox
if (adjColumnView == null || adjColumnView.size == 0 || RenderDataPointUtil.isVoid(adjColumnView.get(0)))
{
// there isn't any data adjacent to this LOD, add the vertical quad
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, (byte) 15, blockLight);
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
return;
}
@@ -393,7 +394,7 @@ public class ColumnBox
// The input face is completely inside the adj's face, don't render it
if (debugOverlapColor != 0)
{
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, debugOverlapColor, irisBlockMaterialId, (byte) 15, (byte) 15);
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
}
}
else
@@ -402,7 +403,7 @@ public class ColumnBox
if (adjYMax > yMin && debugOverlapColor != 0)
{
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (adjYMax - yMin), debugOverlapColor, irisBlockMaterialId, (byte) 15, (byte) 15);
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (adjYMax - yMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
}
// if this is the only face, use the yMax and break,
@@ -448,7 +449,7 @@ public class ColumnBox
// the adj data intersects the higher part of the current data
if (debugOverlapColor != 0)
{
builder.addQuadAdj(direction, x, adjYMin, z, horizontalWidth, (short) (yMax - adjYMin), debugOverlapColor, irisBlockMaterialId, (byte) 15, (byte) 15);
builder.addQuadAdj(direction, x, adjYMin, z, horizontalWidth, (short) (yMax - adjYMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
}
// we start the creation of a new face
@@ -459,7 +460,7 @@ public class ColumnBox
// _______&&: y < depth ______ < yMax
if (debugOverlapColor != 0)
{
builder.addQuadAdj(direction, x, adjYMin, z, horizontalWidth, (short) (adjYMax - adjYMin), debugOverlapColor, irisBlockMaterialId, (byte) 15, (byte) 15);
builder.addQuadAdj(direction, x, adjYMin, z, horizontalWidth, (short) (adjYMax - adjYMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
}
if (firstFace)
@@ -474,6 +475,12 @@ public class ColumnBox
throw new RuntimeException("Loop error");
if (previousAdjDepth > adjYMax)
{
if (irisBlockMaterialId == IBlockStateWrapper.IrisBlockMaterial.GRASS)
{
// this LOD is underneath another, grass will never show here
irisBlockMaterialId = IBlockStateWrapper.IrisBlockMaterial.DIRT;
}
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (previousAdjDepth - adjYMax), color, irisBlockMaterialId,
RenderDataPointUtil.getLightSky(adjPoint), blockLight);
}
@@ -25,14 +25,12 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EGLProxyContext;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.AbstractRenderBuffer;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.StatsMap;
import com.seibel.distanthorizons.api.enums.config.EGpuUploadMethod;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.util.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.Logger;
@@ -46,13 +44,19 @@ import java.util.concurrent.*;
*
* @see ColumnRenderBufferBuilder
*/
public class ColumnRenderBuffer extends AbstractRenderBuffer
public class ColumnRenderBuffer implements AutoCloseable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper minecraftClient = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 4; // TODO what does the 4 represent
public static final int MAX_QUADS_PER_BUFFER = (1024 * 1024 * 1) / QUADS_BYTE_SIZE; // TODO what do these multiples represent?
public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
public final DhBlockPos pos;
@@ -61,17 +65,15 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
private GLVertexBuffer[] vbos;
private GLVertexBuffer[] vbosTransparent;
private final DhSectionPos debugPos;
//==============//
// constructors //
//==============//
public ColumnRenderBuffer(DhBlockPos pos, DhSectionPos debugPos)
public ColumnRenderBuffer(DhBlockPos pos)
{
this.pos = pos;
this.debugPos = debugPos;
this.vbos = new GLVertexBuffer[0];
this.vbosTransparent = new GLVertexBuffer[0];
}
@@ -85,7 +87,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
//==================//
/** Should be run on a DH thread. */
public void uploadBuffer(LodQuadBuilder builder, EGpuUploadMethod gpuUploadMethod) throws InterruptedException
public void uploadBuffer(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException
{
LodUtil.assertTrue(Thread.currentThread().getName().startsWith(ThreadUtil.THREAD_NAME_PREFIX), "Buffer uploading needs to be done on a DH thread to prevent locking up any MC threads.");
@@ -144,7 +146,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
}
}
private void uploadBuffersUsingUploadMethod(LodQuadBuilder builder, EGpuUploadMethod gpuUploadMethod) throws InterruptedException
private void uploadBuffersUsingUploadMethod(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException
{
if (gpuUploadMethod.useEarlyMapping)
{
@@ -160,7 +162,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
private void uploadBuffersMapped(LodQuadBuilder builder, EGpuUploadMethod method)
private void uploadBuffersMapped(LodQuadBuilder builder, EDhApiGpuUploadMethod method)
{
// opaque vbos //
@@ -196,7 +198,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
}
}
private void uploadBuffersDirect(LodQuadBuilder builder, EGpuUploadMethod method) throws InterruptedException
private void uploadBuffersDirect(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException
{
this.vbos = ColumnRenderBufferBuilder.resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
uploadBuffersDirect(this.vbos, builder.makeOpaqueVertexBuffers(), method);
@@ -204,7 +206,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
this.vbosTransparent = ColumnRenderBufferBuilder.resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
uploadBuffersDirect(this.vbosTransparent, builder.makeTransparentVertexBuffers(), method);
}
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, Iterator<ByteBuffer> iter, EGpuUploadMethod method) throws InterruptedException
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, Iterator<ByteBuffer> iter, EDhApiGpuUploadMethod method) throws InterruptedException
{
long remainingMS = 0;
long MBPerMS = Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds.get();
@@ -275,7 +277,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
// render //
//========//
@Override
/** @return true if something was rendered, false otherwise */
public boolean renderOpaque(LodRenderer renderContext, DhApiRenderParam renderEventParam)
{
boolean hasRendered = false;
@@ -299,7 +301,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
return hasRendered;
}
@Override
/** @return true if something was rendered, false otherwise */
public boolean renderTransparent(LodRenderer renderContext, DhApiRenderParam renderEventParam)
{
boolean hasRendered = false;
@@ -341,20 +343,26 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
//==============//
/** can be used when debugging */
public boolean hasNonEmptyBuffers()
public boolean hasNonNullVbos() { return this.vbos != null || this.vbosTransparent != null; }
/** can be used when debugging */
public int vboBufferCount()
{
for (GLVertexBuffer vertexBuffer : this.vbos)
int count = 0;
if (this.vbos != null)
{
if (vertexBuffer != null && vertexBuffer.getSize() != 0)
{
return true;
}
count += this.vbos.length;
}
return false;
if (this.vbosTransparent != null)
{
count += this.vbosTransparent.length;
}
return count;
}
@Override
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RenderBuffers");
@@ -378,6 +386,12 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
}
}
/**
* This method is called when object is no longer in use.
* Called either after uploadBuffers() returned false (On buffer Upload
* thread), or by others when the object is not being used. (not in build,
* upload, or render state).
*/
@Override
public void close()
{
@@ -19,7 +19,7 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.enums.rendering.EDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
@@ -27,18 +27,19 @@ import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.util.objects.Reference;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.util.threading.ThreadPools;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
/**
@@ -63,12 +64,12 @@ public class ColumnRenderBufferBuilder
// vbo building //
//==============//
public static CompletableFuture<ColumnRenderBuffer> buildBuffersAsync(
IDhClientLevel clientLevel, Reference<ColumnRenderBuffer> renderBufferRef,
public static CompletableFuture<ColumnRenderBuffer> buildAndUploadBuffersAsync(
IDhClientLevel clientLevel,
ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
{
ThreadPoolExecutor bufferBuilderExecutor = ThreadPools.getBufferBuilderExecutor();
ThreadPoolExecutor bufferUploaderExecutor = ThreadPools.getBufferUploaderExecutor();
ThreadPoolExecutor bufferBuilderExecutor = ThreadPoolUtil.getBufferBuilderExecutor();
ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor();
if ((bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated()) ||
(bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated()))
{
@@ -78,15 +79,27 @@ public class ColumnRenderBufferBuilder
return future;
}
//LOGGER.info("RenderRegion startBuild @ "+renderSource.sectionPos);
return CompletableFuture.supplyAsync(() ->
try
{
return CompletableFuture.supplyAsync(() ->
{
try
{
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
EVENT_LOGGER.trace("RenderRegion start QuadBuild @ " + renderSource.sectionPos);
boolean enableSkyLightCulling = !clientLevel.getLevelWrapper().hasCeiling() && Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get();
//EVENT_LOGGER.trace("RenderRegion start QuadBuild @ " + renderSource.sectionPos);
boolean enableSkyLightCulling =
Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get()
&& (
// dimensions with a ceiling will be all caves so we don't want cave culling
!clientLevel.getLevelWrapper().hasCeiling()
// the end has a lot of overhangs with 0 lighting above the void, which look broken with
// the current cave culling logic (this could probably be improved, but just skipping it works best for now)
&& !clientLevel.getLevelWrapper().getDimensionType().isTheEnd()
// FIXME temporary fix
// Cave culling is currently broken for any detail level above 0
&& DhSectionPos.getDetailLevel(renderSource.pos) == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL
);
int skyLightCullingBelow = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get();
// FIXME: Clamp also to the max world height.
@@ -95,12 +108,12 @@ public class ColumnRenderBufferBuilder
long builderStartTime = System.currentTimeMillis();
LodQuadBuilder builder = new LodQuadBuilder(enableSkyLightCulling, (short) (skyLightCullingBelow - clientLevel.getMinY()), enableTransparency);
LodQuadBuilder builder = new LodQuadBuilder(enableSkyLightCulling, (short) (skyLightCullingBelow - clientLevel.getMinY()), enableTransparency, clientLevel.getClientLevelWrapper());
makeLodRenderData(builder, renderSource, adjData);
long builderEndTime = System.currentTimeMillis();
long buildMs = builderEndTime - builderStartTime;
LOGGER.debug("RenderRegion end QuadBuild @ " + renderSource.sectionPos + " took: " + buildMs);
LOGGER.debug("RenderRegion end QuadBuild @ " + renderSource.pos + " took: " + buildMs);
return builder;
}
@@ -118,19 +131,11 @@ public class ColumnRenderBufferBuilder
{
try
{
EVENT_LOGGER.trace("RenderRegion start Upload @ " + renderSource.sectionPos);
ColumnRenderBuffer buffer = renderBufferRef.swap(null);
if (buffer == null)
{
buffer = new ColumnRenderBuffer(new DhBlockPos(renderSource.sectionPos.getMinCornerLodPos().getCornerBlockPos(), clientLevel.getMinY()), renderSource.sectionPos);
}
ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(renderSource.pos), clientLevel.getMinY(), DhSectionPos.getMinCornerBlockZ(renderSource.pos)));
try
{
buffer.uploadBuffer(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
LodUtil.assertTrue(buffer.buffersUploaded);
EVENT_LOGGER.trace("RenderRegion end Upload @ " + renderSource.sectionPos);
return buffer;
}
catch (Exception e)
@@ -145,49 +150,33 @@ public class ColumnRenderBufferBuilder
}
catch (Throwable e3)
{
LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
LOGGER.error("LodNodeBufferBuilder was unable to upload buffer: " + e3.getMessage(), e3);
throw e3;
}
}, bufferUploaderExecutor)
.handle((columnRenderBuffer, ex) ->
{
//LOGGER.info("RenderRegion endBuild @ {}", renderSource.sectionPos);
if (ex != null)
{
LOGGER.warn("Buffer building failed: " + ex.getMessage(), ex);
if (!renderBufferRef.isEmpty())
{
ColumnRenderBuffer buffer = renderBufferRef.swap(null);
buffer.close();
}
return null;
}
else
{
if (columnRenderBuffer != null)
{
LodUtil.assertTrue(columnRenderBuffer.buffersUploaded);
}
return columnRenderBuffer;
}
});
}, bufferUploaderExecutor);
}
catch (RejectedExecutionException ignore)
{
// the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
}
private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, ColumnRenderSource[] adjRegions)
{
// Variable initialization
EDebugRendering debugMode = Config.Client.Advanced.Debugging.debugRendering.get();
EDhApiDebugRendering debugMode = Config.Client.Advanced.Debugging.debugRendering.get();
// can be used to limit which section positions are build and thus, rendered
// useful when debugging a specific section
boolean enableColumnBufferLimit = Config.Client.Advanced.Debugging.columnBuilderDebugEnable.get();
if (enableColumnBufferLimit)
{
if (renderSource.sectionPos.getDetailLevel() == Config.Client.Advanced.Debugging.columnBuilderDebugDetailLevel.get()
&& renderSource.sectionPos.getX() == Config.Client.Advanced.Debugging.columnBuilderDebugXPos.get()
&& renderSource.sectionPos.getZ() == Config.Client.Advanced.Debugging.columnBuilderDebugZPos.get())
if (DhSectionPos.getDetailLevel(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugDetailLevel.get()
&& DhSectionPos.getX(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugXPos.get()
&& DhSectionPos.getZ(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugZPos.get())
{
int test = 0;
}
@@ -312,8 +301,7 @@ public class ColumnRenderBufferBuilder
}
catch (RuntimeException e)
{
EVENT_LOGGER.warn("Failed to get adj data for [" + detailLevel + ":" + x + "," + z + "] at [" + lodDirection + "]");
EVENT_LOGGER.warn("Detail exception: ", e);
EVENT_LOGGER.warn("Failed to get adj data for [" + detailLevel + ":" + x + "," + z + "] at [" + lodDirection + "], Error: "+e.getMessage(), e);
}
} // for adjacent directions
@@ -25,7 +25,7 @@ import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.api.enums.rendering.EDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
@@ -42,7 +42,7 @@ public class CubicLodTemplate
public static void addLodToBuffer(
long data, long topData, long bottomData, ColumnArrayView[][] adjColumnViews,
byte detailLevel, int offsetPosX, int offsetOosZ, LodQuadBuilder quadBuilder,
EDebugRendering debugging, ColumnRenderSource.DebugSourceFlag debugSource)
EDhApiDebugRendering debugging, ColumnRenderSource.DebugSourceFlag debugSource)
{
DhLodPos blockOffsetPos = new DhLodPos(detailLevel, offsetPosX, offsetOosZ).convertToDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL);
@@ -88,7 +88,6 @@ public class CubicLodTemplate
break;
}
case SHOW_DETAIL:
case SHOW_GENMODE:
{
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel];
fullBright = true;
@@ -104,7 +103,7 @@ public class CubicLodTemplate
break;
case IBlockStateWrapper.IrisBlockMaterial.LEAVES:
color = ColorUtil.GREEN;
color = ColorUtil.DARK_GREEN;
break;
case IBlockStateWrapper.IrisBlockMaterial.STONE:
color = ColorUtil.GRAY;
@@ -139,6 +138,9 @@ public class CubicLodTemplate
case IBlockStateWrapper.IrisBlockMaterial.WATER:
color = ColorUtil.BLUE;
break;
case IBlockStateWrapper.IrisBlockMaterial.GRASS:
color = ColorUtil.GREEN;
break;
case IBlockStateWrapper.IrisBlockMaterial.ILLUMINATED:
color = ColorUtil.YELLOW;
break;
@@ -23,12 +23,18 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
import com.seibel.distanthorizons.api.enums.config.EDhApiGrassSideRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.AbstractRenderBuffer;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.api.enums.config.EGpuUploadMethod;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.apache.logging.log4j.Logger;
@@ -42,6 +48,7 @@ import org.apache.logging.log4j.Logger;
public class LodQuadBuilder
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public final boolean skipQuadsWithZeroSkylight;
public final short skyLightCullingBelow;
@@ -52,7 +59,10 @@ public class LodQuadBuilder
private final ArrayList<BufferQuad>[] transparentQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
private final boolean doTransparency;
private final IClientLevelWrapper clientLevelWrapper;
private final EDhApiDebugRendering debugRenderingMode;
private final EDhApiGrassSideRendering grassSideRenderingMode;
public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][]
@@ -112,7 +122,7 @@ public class LodQuadBuilder
// constructor //
//=============//
public LodQuadBuilder(boolean enableSkylightCulling, short skyLightCullingBelow, boolean doTransparency)
public LodQuadBuilder(boolean enableSkylightCulling, short skyLightCullingBelow, boolean doTransparency, IClientLevelWrapper clientLevelWrapper)
{
this.doTransparency = doTransparency;
for (int i = 0; i < 6; i++)
@@ -123,6 +133,10 @@ public class LodQuadBuilder
this.skipQuadsWithZeroSkylight = enableSkylightCulling;
this.skyLightCullingBelow = skyLightCullingBelow;
this.clientLevelWrapper = clientLevelWrapper;
this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRendering.get();
this.grassSideRenderingMode = Config.Client.Advanced.Graphics.AdvancedGraphics.grassSideRendering.get();
}
@@ -254,8 +268,41 @@ public class LodQuadBuilder
default:
throw new IllegalArgumentException("Invalid Axis enum: " + axis);
}
putVertex(bb, (short) (quad.x + dx), (short) (quad.y + dy), (short) (quad.z + dz),
quad.hasError ? ColorUtil.RED : quad.color, // TODO add debug config that allows toggling this
int color = quad.color;
// use custom side color logic for grass blocks
if (quad.irisBlockMaterialId == IBlockStateWrapper.IrisBlockMaterial.GRASS)
{
// only use dirt colors if debug rendering is disabled
if (this.debugRenderingMode == EDhApiDebugRendering.OFF)
{
// determine if any custom coloring logic should be used
if (this.grassSideRenderingMode != EDhApiGrassSideRendering.AS_GRASS)
{
// only change the vertex color if it's on the side or bottom
if (quad.direction.getAxis().isHorizontal() || quad.direction == EDhDirection.DOWN)
{
if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT
// if we want the color to fade, only apply the dirt color to the bottom vertices
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
// always render the bottom as dirt
|| quad.direction == EDhDirection.DOWN)
{
// for horizontal and bottom faces of grass blocks, use the dirt color to
// prevent green cliff walls
color = this.clientLevelWrapper.getDirtBlockColor();
color = ColorUtil.applyShade(color, MC.getShade(quad.direction));
}
}
}
}
}
this.putVertex(bb, (short) (quad.x + dx), (short) (quad.y + dy), (short) (quad.z + dz),
quad.hasError ? ColorUtil.RED : color,
quad.hasError ? 0 : normalIndex,
quad.hasError ? 0 : quad.irisBlockMaterialId,
quad.hasError ? 15 : quad.skyLight,
@@ -386,7 +433,7 @@ public class LodQuadBuilder
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(AbstractRenderBuffer.FULL_SIZED_BUFFER)
final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER)
.order(ByteOrder.nativeOrder());
int dir = skipEmpty(0);
int quad = 0;
@@ -414,7 +461,7 @@ public class LodQuadBuilder
return null;
}
bb.clear();
bb.limit(AbstractRenderBuffer.FULL_SIZED_BUFFER);
bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER);
while (bb.hasRemaining() && dir < 6)
{
writeData();
@@ -454,7 +501,7 @@ public class LodQuadBuilder
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(AbstractRenderBuffer.FULL_SIZED_BUFFER)
final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER)
.order(ByteOrder.nativeOrder());
int directionIndex = this.skipEmptyDirectionIndices(0);
int quad = 0;
@@ -483,7 +530,7 @@ public class LodQuadBuilder
}
this.bb.clear();
this.bb.limit(AbstractRenderBuffer.FULL_SIZED_BUFFER);
this.bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER);
while (this.bb.hasRemaining() && this.directionIndex < 6)
{
this.writeData();
@@ -525,7 +572,7 @@ public class LodQuadBuilder
}
public BufferFiller makeOpaqueBufferFiller(EGpuUploadMethod method)
public BufferFiller makeOpaqueBufferFiller(EDhApiGpuUploadMethod method)
{
return new BufferFiller()
{
@@ -541,19 +588,19 @@ public class LodQuadBuilder
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > AbstractRenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = AbstractRenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * AbstractRenderBuffer.QUADS_BYTE_SIZE, method,
AbstractRenderBuffer.FULL_SIZED_BUFFER);
ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method,
ColumnRenderBuffer.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * AbstractRenderBuffer.QUADS_BYTE_SIZE);
bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
@@ -605,7 +652,7 @@ public class LodQuadBuilder
};
}
public BufferFiller makeTransparentBufferFiller(EGpuUploadMethod method)
public BufferFiller makeTransparentBufferFiller(EDhApiGpuUploadMethod method)
{
return new BufferFiller()
{
@@ -621,19 +668,19 @@ public class LodQuadBuilder
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > AbstractRenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = AbstractRenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * AbstractRenderBuffer.QUADS_BYTE_SIZE, method,
AbstractRenderBuffer.FULL_SIZED_BUFFER);
ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method,
ColumnRenderBuffer.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * AbstractRenderBuffer.QUADS_BYTE_SIZE);
bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
@@ -718,7 +765,7 @@ public class LodQuadBuilder
}
/** Returns how many GpuBuffers will be needed to render opaque quads in this builder. */
public int getCurrentNeededOpaqueVertexBufferCount() { return MathUtil.ceilDiv(this.getCurrentOpaqueQuadsCount(), AbstractRenderBuffer.MAX_QUADS_PER_BUFFER); }
public int getCurrentNeededOpaqueVertexBufferCount() { return MathUtil.ceilDiv(this.getCurrentOpaqueQuadsCount(), ColumnRenderBuffer.MAX_QUADS_PER_BUFFER); }
/** Returns how many GpuBuffers will be needed to render transparent quads in this builder. */
public int getCurrentNeededTransparentVertexBufferCount()
{
@@ -727,7 +774,7 @@ public class LodQuadBuilder
return 0;
}
return MathUtil.ceilDiv(this.getCurrentTransparentQuadsCount(), AbstractRenderBuffer.MAX_QUADS_PER_BUFFER);
return MathUtil.ceilDiv(this.getCurrentTransparentQuadsCount(), ColumnRenderBuffer.MAX_QUADS_PER_BUFFER);
}
}
@@ -21,19 +21,21 @@ package com.seibel.distanthorizons.core.dataObjects.render.columnViews;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import java.util.Arrays;
public final class ColumnArrayView implements IColumnDataView
{
public final long[] data;
public final LongArrayList data;
public final int size;
public final int offset; // offset in longs
/** can be 0 if this column was created for an empty data source */
public final int vertSize; // vertical size in longs
public ColumnArrayView(long[] data, int size, int offset, int vertSize)
public ColumnArrayView(LongArrayList data, int size, int offset, int vertSize)
{
this.data = data;
this.size = size;
@@ -44,9 +46,9 @@ public final class ColumnArrayView implements IColumnDataView
@Override
public long get(int index) { return data[index + offset]; }
public long get(int index) { return data.getLong(index + offset); }
public void set(int index, long value) { data[index + offset] = value; }
public void set(int index, long value) { data.set(index + offset, value); }
@Override
public int size() { return size; }
@@ -55,7 +57,7 @@ public final class ColumnArrayView implements IColumnDataView
public int verticalSize() { return vertSize; }
@Override
public int dataCount() { return size / vertSize; }
public int dataCount() { return (this.vertSize != 0) ? (this.size / this.vertSize) : 0; }
@Override
public ColumnArrayView subView(int dataIndexStart, int dataCount)
@@ -63,7 +65,7 @@ public final class ColumnArrayView implements IColumnDataView
return new ColumnArrayView(data, dataCount * vertSize, offset + dataIndexStart * vertSize, vertSize);
}
public void fill(long value) { Arrays.fill(data, offset, offset + size, value); }
public void fill(long value) { Arrays.fill(data.elements(), offset, offset + size, value); }
public void copyFrom(IColumnDataView source) { copyFrom(source, 0); }
public void copyFrom(IColumnDataView source, int outputDataIndexOffset)
@@ -81,19 +83,19 @@ public final class ColumnArrayView implements IColumnDataView
for (int i = 0; i < source.dataCount(); i++)
{
int outputOffset = offset + outputDataIndexOffset * vertSize + i * vertSize;
source.subView(i, 1).copyTo(data, outputOffset, source.verticalSize());
Arrays.fill(data, outputOffset + source.verticalSize(),
source.subView(i, 1).copyTo(data.elements(), outputOffset, source.verticalSize());
Arrays.fill(data.elements(), outputOffset + source.verticalSize(),
outputOffset + vertSize, 0);
}
}
else
{
source.copyTo(data, offset + outputDataIndexOffset * vertSize, source.size());
source.copyTo(data.elements(), offset + outputDataIndexOffset * vertSize, source.size());
}
}
@Override
public void copyTo(long[] target, int offset, int size) { System.arraycopy(data, this.offset, target, offset, size); }
public void copyTo(long[] target, int offset, int size) { System.arraycopy(data.elements(), this.offset, target, offset, size); }
public boolean mergeWith(ColumnArrayView source, boolean override)
{
@@ -130,17 +132,18 @@ public final class ColumnArrayView implements IColumnDataView
public void changeVerticalSizeFrom(IColumnDataView source)
{
if (dataCount() != source.dataCount())
if (this.dataCount() != source.dataCount())
{
throw new IllegalArgumentException("Cannot copy and resize to views with different dataCounts");
}
if (vertSize >= source.verticalSize())
if (this.vertSize >= source.verticalSize())
{
copyFrom(source);
this.copyFrom(source);
}
else
{
for (int i = 0; i < dataCount(); i++)
for (int i = 0; i < this.dataCount(); i++)
{
RenderDataPointUtil.mergeMultiData(source.subView(i, 1), subView(i, 1));
}
@@ -168,7 +171,7 @@ public final class ColumnArrayView implements IColumnDataView
sb.append(" [");
for (int i = 0; i < size; i++)
{
sb.append(RenderDataPointUtil.toString(data[offset + i]));
sb.append(RenderDataPointUtil.toString(data.getLong(offset + i)));
if (i < size - 1)
{
sb.append(",\n");
@@ -184,15 +187,18 @@ public final class ColumnArrayView implements IColumnDataView
return arrayHash(data, offset, size);
}
private static int arrayHash(long[] a, int offset, int length)
private static int arrayHash(LongArrayList a, int offset, int length)
{
if (a == null)
{
return 0;
}
int result = 1;
int end = offset + length;
for (int i = offset; i < end; i++)
{
long element = a[i];
long element = a.getLong(i);
int elementHash = (int) (element ^ (element >>> 32));
result = 31 * result + elementHash;
}
@@ -19,19 +19,24 @@
package com.seibel.distanthorizons.core.dataObjects.render.columnViews;
import it.unimi.dsi.fastutil.longs.LongArrayList;
public class ColumnQuadView implements IColumnDataView
{
private final long[] data;
private final LongArrayList data;
private final int perColumnOffset; // per column (of columns of data) offset in longs
private final int xSize; // x size in datapoints
private final int zSize; // x size in datapoints
private final int offset; // offset in longs
private final int vertSize; // vertical size in longs
public ColumnQuadView(long[] data, int dataZWidth, int dataVertSize, int viewXOffset, int viewZOffset, int xSize, int zSize)
public ColumnQuadView(LongArrayList data, int dataZWidth, int dataVertSize, int viewXOffset, int viewZOffset, int xSize, int zSize)
{
if (viewXOffset + xSize > (data.length / (dataZWidth * dataVertSize)) || viewZOffset + zSize > dataZWidth)
if (viewXOffset + xSize > (data.size() / (dataZWidth * dataVertSize)) || viewZOffset + zSize > dataZWidth)
{
throw new IllegalArgumentException("View is out of bounds");
}
this.data = data;
this.xSize = xSize;
this.zSize = zSize;
@@ -39,7 +44,7 @@ public class ColumnQuadView implements IColumnDataView
this.perColumnOffset = dataZWidth * dataVertSize;
this.offset = viewXOffset * perColumnOffset + viewZOffset * dataVertSize;
}
private ColumnQuadView(long[] data, int perColumnOffset, int offset, int vertSize, int xSize, int zSize)
private ColumnQuadView(LongArrayList data, int perColumnOffset, int offset, int vertSize, int xSize, int zSize)
{
this.data = data;
this.perColumnOffset = perColumnOffset;
@@ -60,12 +65,12 @@ public class ColumnQuadView implements IColumnDataView
public long get(int x, int z, int v)
{
return data[offset + x * perColumnOffset + z * vertSize + v];
return data.getLong(offset + x * perColumnOffset + z * vertSize + v);
}
public long set(int x, int z, int v, long value)
{
return data[offset + x * perColumnOffset + z * vertSize + v] = value;
return data.set(offset + x * perColumnOffset + z * vertSize + v, value);
}
public ColumnArrayView get(int x, int z)
@@ -82,7 +87,7 @@ public class ColumnQuadView implements IColumnDataView
{
if (singleColumn.verticalSize() != vertSize) throw new IllegalArgumentException("Vertical size of singleColumn must be equal to vertSize");
if (singleColumn.dataCount() != 1) throw new IllegalArgumentException("SingleColumn must contain exactly one data point");
singleColumn.copyTo(data, offset + x * perColumnOffset + z * vertSize, singleColumn.size());
singleColumn.copyTo(data.elements(), offset + x * perColumnOffset + z * vertSize, singleColumn.size());
}
@Override
@@ -19,6 +19,8 @@
package com.seibel.distanthorizons.core.dataObjects.render.columnViews;
import it.unimi.dsi.fastutil.longs.LongIterator;
import java.util.Iterator;
public interface IColumnDataView
@@ -28,18 +30,18 @@ public interface IColumnDataView
// FIXME probably horizontal size in blocks?
int size();
default Iterator<Long> iterator()
default LongIterator iterator()
{
return new Iterator<Long>()
return new LongIterator()
{
private int index = 0;
private final int size = size();
private final int size = IColumnDataView.this.size();
@Override
public boolean hasNext() { return this.index < this.size; }
@Override
public Long next() { return get(this.index++); }
public long nextLong() { return IColumnDataView.this.get(this.index++); }
};
}
@@ -23,11 +23,11 @@ import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.threading.ThreadPools;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.LogManager;
@@ -57,7 +57,7 @@ public class ChunkToLodBuilder implements AutoCloseable
// data generation //
//=================//
public CompletableFuture<ChunkSizedFullDataAccessor> tryGenerateData(IChunkWrapper chunkWrapper)
public CompletableFuture<FullDataSourceV2> tryGenerateData(IChunkWrapper chunkWrapper)
{
if (chunkWrapper == null)
{
@@ -74,7 +74,7 @@ public class ChunkToLodBuilder implements AutoCloseable
}
// Otherwise, it means we're the first to do so. Let's submit our task to this entry.
CompletableFuture<ChunkSizedFullDataAccessor> future = new CompletableFuture<>();
CompletableFuture<FullDataSourceV2> future = new CompletableFuture<>();
this.concurrentTaskToBuildList.addLast(new Task(chunkWrapper.getChunkPos(), future));
return future;
}
@@ -82,7 +82,7 @@ public class ChunkToLodBuilder implements AutoCloseable
// TODO why on tick?
public void tick()
{
int threadCount = ThreadPools.getWorkerThreadCount();
int threadCount = ThreadPoolUtil.getWorkerThreadCount();
if (this.runningCount.get() >= threadCount)
{
return;
@@ -102,7 +102,7 @@ public class ChunkToLodBuilder implements AutoCloseable
return;
}
ThreadPoolExecutor lodBuilderExecutor = ThreadPools.getChunkToLodBuilderExecutor();
ThreadPoolExecutor lodBuilderExecutor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (lodBuilderExecutor == null)
{
return;
@@ -112,17 +112,21 @@ public class ChunkToLodBuilder implements AutoCloseable
for (int i = 0; i < threadCount; i++)
{
this.runningCount.incrementAndGet();
CompletableFuture.runAsync(() ->
try
{
try
CompletableFuture.runAsync(() ->
{
this.tickThreadTask();
}
finally
{
this.runningCount.decrementAndGet();
}
}, lodBuilderExecutor);
try
{
this.tickThreadTask();
}
finally
{
this.runningCount.decrementAndGet();
}
}, lodBuilderExecutor);
}
catch (RejectedExecutionException ignore) { /* the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back */ }
}
}
private void tickThreadTask()
@@ -158,10 +162,10 @@ public class ChunkToLodBuilder implements AutoCloseable
{
if (LodDataBuilder.canGenerateLodFromChunk(latestChunk))
{
ChunkSizedFullDataAccessor data = LodDataBuilder.createChunkData(latestChunk);
if (data != null)
FullDataSourceV2 dataSource = LodDataBuilder.createGeneratedDataSource(latestChunk);
if (dataSource != null)
{
task.future.complete(data);
task.future.complete(dataSource);
continue;
}
}
@@ -233,11 +237,11 @@ public class ChunkToLodBuilder implements AutoCloseable
private static class Task
{
public final DhChunkPos chunkPos;
public final CompletableFuture<ChunkSizedFullDataAccessor> future;
public final CompletableFuture<FullDataSourceV2> future;
/** This is tracked so impossible tasks can be removed from the queue */
public long generationAttemptExpirationTimeMs = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10);
Task(DhChunkPos chunkPos, CompletableFuture<ChunkSizedFullDataAccessor> future)
Task(DhChunkPos chunkPos, CompletableFuture<FullDataSourceV2> future)
{
this.chunkPos = chunkPos;
this.future = future;
@@ -19,14 +19,10 @@
package com.seibel.distanthorizons.core.dataObjects.transformers;
import com.seibel.distanthorizons.api.enums.config.EBlocksToAvoid;
import com.seibel.distanthorizons.api.enums.config.EDhApiBlocksToAvoid;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.SingleColumnFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
@@ -34,20 +30,21 @@ import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import org.apache.logging.log4j.Logger;
import java.util.HashSet;
/**
* Handles converting {@link ChunkSizedFullDataAccessor}, {@link IIncompleteFullDataSource},
* and {@link IFullDataSource}'s to {@link ColumnRenderSource}.
* Handles converting {@link FullDataSourceV2}'s to {@link ColumnRenderSource}.
*/
public class FullDataToRenderDataTransformer
{
@@ -56,19 +53,21 @@ public class FullDataToRenderDataTransformer
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final LongOpenHashSet brokenPos = new LongOpenHashSet();
//==============================//
// public transformer interface //
//==============================//
public static ColumnRenderSource transformFullDataToRenderSource(IFullDataSource fullDataSource, IDhClientLevel level)
public static ColumnRenderSource transformFullDataToRenderSource(FullDataSourceV2 fullDataSource, IDhClientLevel level)
{
if (fullDataSource == null)
{
return null;
}
else if (MC.getWrappedClientLevel() == null)
else if (level == null)
{
// if the client is no longer loaded in the world, render sources cannot be created
return null;
@@ -77,17 +76,7 @@ public class FullDataToRenderDataTransformer
try
{
if (fullDataSource instanceof CompleteFullDataSource)
{
return transformCompleteFullDataToColumnData(level, (CompleteFullDataSource) fullDataSource);
}
else if (fullDataSource instanceof IIncompleteFullDataSource)
{
return transformIncompleteFullDataToColumnData(level, (IIncompleteFullDataSource) fullDataSource);
}
LodUtil.assertNotReach("Unimplemented Full Data transformer for "+IFullDataSource.class.getSimpleName()+" of type ["+fullDataSource.getClass().getSimpleName()+"].");
return null;
return transformCompleteFullDataToColumnData(level, fullDataSource);
}
catch (InterruptedException e)
{
@@ -108,13 +97,13 @@ public class FullDataToRenderDataTransformer
* @throws InterruptedException Can be caused by interrupting the thread upstream.
* Generally thrown if the method is running after the client leaves the current world.
*/
private static ColumnRenderSource transformCompleteFullDataToColumnData(IDhClientLevel level, CompleteFullDataSource fullDataSource) throws InterruptedException
private static ColumnRenderSource transformCompleteFullDataToColumnData(IDhClientLevel level, FullDataSourceV2 fullDataSource) throws InterruptedException
{
final DhSectionPos pos = fullDataSource.getSectionPos();
final long pos = fullDataSource.getPos();
final byte dataDetail = fullDataSource.getDataDetailLevel();
final int vertSize = Config.Client.Advanced.Graphics.Quality.verticalQuality.get().calculateMaxVerticalData(fullDataSource.getDataDetailLevel());
final ColumnRenderSource columnSource = new ColumnRenderSource(pos, vertSize, level.getMinY());
if (fullDataSource.isEmpty())
final ColumnRenderSource columnSource = ColumnRenderSource.getPooledRenderSource(pos, vertSize, level.getMinY(), true);
if (fullDataSource.isEmpty)
{
return columnSource;
}
@@ -123,18 +112,18 @@ public class FullDataToRenderDataTransformer
if (dataDetail == columnSource.getDataDetailLevel())
{
int baseX = pos.getMinCornerLodPos().getCornerBlockPos().x;
int baseZ = pos.getMinCornerLodPos().getCornerBlockPos().z;
int baseX = DhSectionPos.getMinCornerBlockX(pos);
int baseZ = DhSectionPos.getMinCornerBlockZ(pos);
for (int x = 0; x < pos.getWidthCountForLowerDetailedSection(dataDetail); x++)
for (int x = 0; x < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); x++)
{
for (int z = 0; z < pos.getWidthCountForLowerDetailedSection(dataDetail); z++)
for (int z = 0; z < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); z++)
{
throwIfThreadInterrupted();
ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z);
SingleColumnFullDataAccessor fullArrayView = fullDataSource.get(x, z);
convertColumnData(level, baseX + x, baseZ + z, columnArrayView, fullArrayView);
LongArrayList dataColumn = fullDataSource.get(x, z);
convertColumnData(level, fullDataSource.mapping, baseX + x, baseZ + z, columnArrayView, dataColumn);
}
}
@@ -149,56 +138,6 @@ public class FullDataToRenderDataTransformer
return columnSource;
}
/**
* @throws InterruptedException Can be caused by interrupting the thread upstream.
* Generally thrown if the method is running after the client leaves the current world.
*/
private static ColumnRenderSource transformIncompleteFullDataToColumnData(IDhClientLevel level, IIncompleteFullDataSource data) throws InterruptedException
{
final DhSectionPos pos = data.getSectionPos();
final byte dataDetail = data.getDataDetailLevel();
final int vertSize = Config.Client.Advanced.Graphics.Quality.verticalQuality.get().calculateMaxVerticalData(data.getDataDetailLevel());
final ColumnRenderSource columnSource = new ColumnRenderSource(pos, vertSize, level.getMinY());
if (data.isEmpty())
{
return columnSource;
}
columnSource.markNotEmpty();
if (dataDetail == columnSource.getDataDetailLevel())
{
int baseX = pos.getMinCornerLodPos().getCornerBlockPos().x;
int baseZ = pos.getMinCornerLodPos().getCornerBlockPos().z;
int width = pos.getWidthCountForLowerDetailedSection(dataDetail);
for (int x = 0; x < width; x++)
{
for (int z = 0; z < width; z++)
{
throwIfThreadInterrupted();
SingleColumnFullDataAccessor fullArrayView = data.tryGet(x, z);
if (fullArrayView == null)
{
continue;
}
ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z);
convertColumnData(level, baseX + x, baseZ + z, columnArrayView, fullArrayView);
columnSource.fillDebugFlag(x, z, 1, 1, ColumnRenderSource.DebugSourceFlag.SPARSE);
}
}
}
else
{
throw new UnsupportedOperationException("To be implemented");
//FIXME: Implement different size creation of renderData
}
return columnSource;
}
//================//
@@ -218,30 +157,35 @@ public class FullDataToRenderDataTransformer
}
}
private static HashSet<DhSectionPos> brokenPos = new HashSet<>();
// TODO what does this mean?
private static void iterateAndConvert(IDhClientLevel level, int blockX, int blockZ, ColumnArrayView column, SingleColumnFullDataAccessor data)
private static void iterateAndConvert(
IDhClientLevel level, FullDataPointIdMap fullDataMapping,
int blockX, int blockZ,
ColumnArrayView renderColumnData, LongArrayList fullColumnData)
{
boolean avoidSolidBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EBlocksToAvoid.NON_COLLIDING);
boolean avoidSolidBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING);
boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get();
FullDataPointIdMap fullDataMapping = data.getMapping();
HashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(level.getLevelWrapper());
boolean isVoid = true;
int colorToApplyToNextBlock = -1;
int lastColor = 0;
int lastBottom = -10000;
int skylightToApplyToNextBlock = -1;
int blocklightToApplyToNextBlock = -1;
int columnOffset = 0;
// goes from the top down
for (int i = 0; i < data.getSingleLength(); i++)
for (int i = 0; i < fullColumnData.size(); i++)
{
long fullData = data.getSingle(i);
long fullData = fullColumnData.getLong(i);
int bottomY = FullDataPointUtil.getBottomY(fullData);
int blockHeight = FullDataPointUtil.getHeight(fullData);
int id = FullDataPointUtil.getId(fullData);
int light = FullDataPointUtil.getLight(fullData);
int blockLight = FullDataPointUtil.getBlockLight(fullData);
int skyLight = FullDataPointUtil.getSkyLight(fullData);
// TODO how should corrupted data be handled?
// TODO why is the full data corrupted in the first place? FullDataPointUtil hasn't been changed in a long time, could one of the full data point objects be corrupted?
@@ -296,7 +240,16 @@ public class FullDataToRenderDataTransformer
{
if (colorBelowWithAvoidedBlocks)
{
colorToApplyToNextBlock = level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block);
int tempColor = level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block);
if (ColorUtil.getAlpha(tempColor) == 0)
{
//make sure to not transfer the color when alpha is 0
continue;
}
//mare sure to not trnasfer alpha if for some reason grass is semi transparent
colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor,255);
skylightToApplyToNextBlock = skyLight;
blocklightToApplyToNextBlock = blockLight;
}
// don't add this block
@@ -315,46 +268,56 @@ public class FullDataToRenderDataTransformer
// use the previous block's color
color = colorToApplyToNextBlock;
colorToApplyToNextBlock = -1;
skyLight = skylightToApplyToNextBlock;
blockLight = blocklightToApplyToNextBlock;
}
// add the block
isVoid = false;
long columnData = RenderDataPointUtil.createDataPoint(bottomY + blockHeight, bottomY, color, light, block.getIrisBlockMaterialId());
column.set(columnOffset, columnData);
columnOffset++;
//check if they share a top-bottom face and if they have same collor
if (color == lastColor && bottomY + blockHeight == lastBottom && columnOffset > 0)
{
//replace the previus block with new bottom
long columnData = renderColumnData.get(columnOffset - 1);
columnData = RenderDataPointUtil.setYMin(columnData, bottomY);
renderColumnData.set(columnOffset - 1, columnData);
}
else
{
// add the block
isVoid = false;
long columnData = RenderDataPointUtil.createDataPoint(bottomY + blockHeight, bottomY, color, skyLight, blockLight, block.getIrisBlockMaterialId());
renderColumnData.set(columnOffset, columnData);
columnOffset++;
}
lastBottom = bottomY;
lastColor = color;
}
if (isVoid)
{
column.set(0, RenderDataPointUtil.createVoidDataPoint());
renderColumnData.set(0, RenderDataPointUtil.createVoidDataPoint());
}
}
// TODO what does this mean?
public static void convertColumnData(IDhClientLevel level, int blockX, int blockZ, ColumnArrayView columnArrayView, SingleColumnFullDataAccessor fullArrayView)
public static void convertColumnData(IDhClientLevel level, FullDataPointIdMap fullDataMapping, int blockX, int blockZ, ColumnArrayView columnArrayView, LongArrayList fullDataColumn)
{
if (!fullArrayView.doesColumnExist())
{
return;
}
int dataTotalLength = fullArrayView.getSingleLength();
if (dataTotalLength == 0)
if (fullDataColumn == null || fullDataColumn.size() == 0)
{
return;
}
int dataTotalLength = fullDataColumn.size();
if (dataTotalLength > columnArrayView.verticalSize())
{
ColumnArrayView totalColumnData = new ColumnArrayView(new long[dataTotalLength], dataTotalLength, 0, dataTotalLength);
iterateAndConvert(level, blockX, blockZ, totalColumnData, fullArrayView);
ColumnArrayView totalColumnData = new ColumnArrayView(new LongArrayList(new long[dataTotalLength]), dataTotalLength, 0, dataTotalLength);
iterateAndConvert(level, fullDataMapping, blockX, blockZ, totalColumnData, fullDataColumn);
columnArrayView.changeVerticalSizeFrom(totalColumnData);
}
else
{
iterateAndConvert(level, blockX, blockZ, columnArrayView, fullArrayView); //Directly use the arrayView since it fits.
iterateAndConvert(level, fullDataMapping, blockX, blockZ, columnArrayView, fullDataColumn); //Directly use the arrayView since it fits.
}
}
@@ -21,14 +21,21 @@ package com.seibel.distanthorizons.core.dataObjects.transformers;
import java.util.List;
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
@@ -40,6 +47,8 @@ public class LodDataBuilder
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IBlockStateWrapper AIR = SingletonInjector.INSTANCE.get(IWrapperFactory.class).getAirBlockStateWrapper();
/** how many chunks wide the {@link FullDataSourceV2} is. */
private static final int NUMB_OF_CHUNKS_WIDE = FullDataSourceV2.WIDTH / LodUtil.CHUNK_WIDTH;
private static boolean getTopErrorLogged = false;
@@ -49,7 +58,7 @@ public class LodDataBuilder
// converters //
//============//
public static ChunkSizedFullDataAccessor createChunkData(IChunkWrapper chunkWrapper)
public static FullDataSourceV2 createGeneratedDataSource(IChunkWrapper chunkWrapper)
{
if (!canGenerateLodFromChunk(chunkWrapper))
{
@@ -57,84 +66,244 @@ public class LodDataBuilder
}
ChunkSizedFullDataAccessor chunkData = new ChunkSizedFullDataAccessor(chunkWrapper.getChunkPos());
int minBuildHeight = chunkWrapper.getMinNonEmptyHeight();
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
// get the section position
int sectionPosX = chunkWrapper.getChunkPos().x;
// negative positions start at -1 so the logic there is slightly different
sectionPosX = (sectionPosX < 0) ? ((sectionPosX + 1) / NUMB_OF_CHUNKS_WIDE) - 1 : (sectionPosX / NUMB_OF_CHUNKS_WIDE);
int sectionPosZ = chunkWrapper.getChunkPos().z;
sectionPosZ = (sectionPosZ < 0) ? ((sectionPosZ + 1) / NUMB_OF_CHUNKS_WIDE) - 1 : (sectionPosZ / NUMB_OF_CHUNKS_WIDE);
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
dataSource.isEmpty = false;
// compute the chunk dataSource offset
// this offset is used to determine where in the dataSource this chunk's data should go
int chunkOffsetX = chunkWrapper.getChunkPos().x;
if (chunkWrapper.getChunkPos().x < 0)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
// expected offset positions:
// chunkPos -> offset
// 5 -> 1
// 4 -> 0 ---
// 3 -> 3
// 2 -> 2
// 1 -> 1
// 0 -> 0 ===
// -1 -> 3
// -2 -> 2
// -3 -> 1
// -4 -> 0 ---
// -5 -> 3
chunkOffsetX = ((chunkOffsetX) % NUMB_OF_CHUNKS_WIDE);
if (chunkOffsetX != 0)
{
LongArrayList longs = new LongArrayList(chunkWrapper.getHeight() / 4);
int lastY = chunkWrapper.getMaxBuildHeight();
IBiomeWrapper biome = chunkWrapper.getBiome(x, lastY, z);
IBlockStateWrapper blockState = AIR;
int mappedId = chunkData.getMapping().addIfNotPresentAndGetId(biome, blockState);
// FIXME: The +1 offset to reproduce the old behavior. Remove this when we get per-face lighting
byte light = (byte) ((chunkWrapper.getBlockLight(x, lastY + 1, z) << 4) + chunkWrapper.getSkyLight(x, lastY + 1, z));
// determine the starting Y Pos
int y = chunkWrapper.getLightBlockingHeightMapValue(x,z);
// go up until we reach open air or the world limit
IBlockStateWrapper topBlockState = chunkWrapper.getBlockState(x, y, z);
while (!topBlockState.isAir() && y < chunkWrapper.getMaxBuildHeight())
{
try
{
// This is necessary in some edge cases with snow layers and some other blocks that may not appear in the height map but do block light.
// Interestingly this doesn't appear to be the case in the DhLightingEngine, if this same logic is added there the lighting breaks for the affected blocks.
y++;
topBlockState = chunkWrapper.getBlockState(x, y, z);
}
catch (Exception e)
{
if (!getTopErrorLogged)
{
LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + chunkWrapper.getMaxBuildHeight() + "] had issue getting block at pos [" + x + "," + y + "," + z + "] error: " + e.getMessage(), e);
getTopErrorLogged = true;
}
y--;
break;
}
}
for (; y >= minBuildHeight; y--)
{
IBiomeWrapper newBiome = chunkWrapper.getBiome(x, y, z);
IBlockStateWrapper newBlockState = chunkWrapper.getBlockState(x, y, z);
byte newLight = (byte) ((chunkWrapper.getBlockLight(x, y + 1, z) << 4) + chunkWrapper.getSkyLight(x, y + 1, z));
if (!newBiome.equals(biome) || !newBlockState.equals(blockState))
{
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), light));
biome = newBiome;
blockState = newBlockState;
mappedId = chunkData.getMapping().addIfNotPresentAndGetId(biome, blockState);
light = newLight;
lastY = y;
}
// else if (newLight != light) {
// longs.add(FullFormat.encode(mappedId, lastY-y, y+1 - chunk.getMinBuildHeight(), light));
// light = newLight;
// lastY = y;
// }
}
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), light));
chunkData.setSingleColumn(longs.toLongArray(), x, z);
chunkOffsetX += NUMB_OF_CHUNKS_WIDE;
}
}
if (!canGenerateLodFromChunk(chunkWrapper)) return null;
LodUtil.assertTrue(chunkData.emptyCount() == 0);
return chunkData;
else
{
chunkOffsetX %= NUMB_OF_CHUNKS_WIDE;
}
chunkOffsetX *= LodUtil.CHUNK_WIDTH;
int chunkOffsetZ = chunkWrapper.getChunkPos().z;
if (chunkWrapper.getChunkPos().z < 0)
{
chunkOffsetZ = ((chunkOffsetZ) % NUMB_OF_CHUNKS_WIDE);
if (chunkOffsetZ != 0)
{
chunkOffsetZ += NUMB_OF_CHUNKS_WIDE;
}
}
else
{
chunkOffsetZ %= NUMB_OF_CHUNKS_WIDE;
}
chunkOffsetZ *= LodUtil.CHUNK_WIDTH;
//==========================//
// populate the data source //
//==========================//
EDhApiWorldCompressionMode worldCompressionMode = Config.Client.Advanced.LodBuilding.worldCompression.get();
boolean ignoreHiddenBlocks = (worldCompressionMode != EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
try
{
int minBuildHeight = chunkWrapper.getMinNonEmptyHeight();
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
{
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
{
LongArrayList longs = new LongArrayList(chunkWrapper.getHeight() / 4);
int lastY = chunkWrapper.getMaxBuildHeight();
IBiomeWrapper biome = chunkWrapper.getBiome(relBlockX, lastY, relBlockZ);
IBlockStateWrapper blockState = AIR;
int mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
byte blockLight;
byte skyLight;
if (lastY < chunkWrapper.getMaxBuildHeight())
{
// FIXME: The lastY +1 offset is to reproduce the old behavior. Remove this when we get per-face lighting
blockLight = (byte) chunkWrapper.getBlockLight(relBlockX, lastY + 1, relBlockZ);
skyLight = (byte) chunkWrapper.getSkyLight(relBlockX, lastY + 1, relBlockZ);
}
else
{
//we are at the height limit. There are no torches here, and sky is not obscured.
blockLight = 0;
skyLight = 15;
}
// determine the starting Y Pos
int y = chunkWrapper.getLightBlockingHeightMapValue(relBlockX, relBlockZ);
// go up until we reach open air or the world limit
IBlockStateWrapper topBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
while (!topBlockState.isAir() && y < chunkWrapper.getMaxBuildHeight())
{
try
{
// This is necessary in some edge cases with snow layers and some other blocks that may not appear in the height map but do block light.
// Interestingly this doesn't appear to be the case in the DhLightingEngine, if this same logic is added there the lighting breaks for the affected blocks.
y++;
topBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
}
catch (Exception e)
{
if (!getTopErrorLogged)
{
LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + chunkWrapper.getMaxBuildHeight() + "] had issue getting block at pos [" + relBlockX + "," + y + "," + relBlockZ + "] error: " + e.getMessage(), e);
getTopErrorLogged = true;
}
y--;
break;
}
}
for (; y >= minBuildHeight; y--)
{
IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ);
IBlockStateWrapper newBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
byte newBlockLight = (byte) chunkWrapper.getBlockLight(relBlockX, y + 1, relBlockZ);
byte newSkyLight = (byte) chunkWrapper.getSkyLight(relBlockX, y + 1, relBlockZ);
// save the biome/block change
if (!newBiome.equals(biome) || !newBlockState.equals(blockState))
{
// if we ignore hidden blocks, don't save this biome/block change
// wait until the block is visible and then save the new datapoint
if (!ignoreHiddenBlocks
// if the last block is air, this block will always be visible
|| blockState.isAir()
// check if this block is visible from any direction
|| blockVisible(chunkWrapper, relBlockX, y, relBlockZ))
{
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
biome = newBiome;
blockState = newBlockState;
mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
blockLight = newBlockLight;
skyLight = newSkyLight;
lastY = y;
}
}
}
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
dataSource.setSingleColumn(longs,
relBlockX + chunkOffsetX,
relBlockZ + chunkOffsetZ,
EDhApiWorldGenerationStep.LIGHT,
worldCompressionMode);
}
}
}
catch (DataCorruptedException e)
{
LOGGER.error("Unable to convert chunk at pos ["+chunkWrapper.getChunkPos()+"] to an LOD. Error: "+e.getMessage(), e);
return null;
}
LodUtil.assertTrue(!dataSource.isEmpty);
return dataSource;
}
private static boolean blockVisible(IChunkWrapper chunkWrapper, int relBlockX, int blockY, int relBlockZ)
{
DhBlockPos originalBlockPos = new DhBlockPos(relBlockX,blockY,relBlockZ);
DhBlockPos testBlockPos = new DhBlockPos(relBlockX,blockY,relBlockZ);
// up/down
if (blockInDirectionVisible(chunkWrapper, EDhDirection.UP, originalBlockPos, testBlockPos))
{
return true;
}
if (blockInDirectionVisible(chunkWrapper, EDhDirection.DOWN, originalBlockPos, testBlockPos))
{
return true;
}
// north/south
if (blockInDirectionVisible(chunkWrapper, EDhDirection.NORTH, originalBlockPos, testBlockPos))
{
return true;
}
if (blockInDirectionVisible(chunkWrapper, EDhDirection.SOUTH, originalBlockPos, testBlockPos))
{
return true;
}
// east/west
if (blockInDirectionVisible(chunkWrapper, EDhDirection.EAST, originalBlockPos, testBlockPos))
{
return true;
}
if (blockInDirectionVisible(chunkWrapper, EDhDirection.WEST, originalBlockPos, testBlockPos))
{
return true;
}
return false;
}
private static boolean blockInDirectionVisible(IChunkWrapper chunkWrapper, EDhDirection direction, DhBlockPos originalBlockPos, DhBlockPos testBlockPos)
{
originalBlockPos.mutateOffset(direction, testBlockPos);
// if the block is next to the border of a chunk, assume it's visible
if (testBlockPos.x < 0 || testBlockPos.x >= LodUtil.CHUNK_WIDTH)
{
return true;
}
if (testBlockPos.z < 0 || testBlockPos.z >= LodUtil.CHUNK_WIDTH)
{
return true;
}
if (testBlockPos.y < chunkWrapper.getMinBuildHeight() || testBlockPos.y > chunkWrapper.getMaxBuildHeight())
{
return true;
}
// this block isn't on a chunk boundary, check if it is next to a transparent/air block
IBlockStateWrapper blockState = chunkWrapper.getBlockState(testBlockPos);
return blockState.isAir() || blockState.getOpacity() != IBlockStateWrapper.FULLY_OPAQUE;
}
/** @throws ClassCastException if an API user returns the wrong object type(s) */
public static ChunkSizedFullDataAccessor createApiChunkData(DhApiChunk dataPoints) throws ClassCastException
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk dataPoints) throws ClassCastException, DataCorruptedException
{
ChunkSizedFullDataAccessor accessor = new ChunkSizedFullDataAccessor(new DhChunkPos(dataPoints.chunkPosX, dataPoints.chunkPosZ));
FullDataSourceV2 accessor = FullDataSourceV2.createEmpty(DhSectionPos.encode(new DhChunkPos(dataPoints.chunkPosX, dataPoints.chunkPosZ)));
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++)
@@ -148,31 +317,34 @@ public class LodDataBuilder
// AND the below loop won't run.
int size = (columnDataPoints != null) ? columnDataPoints.size() : 0;
long[] packedDataPoints = new long[size];
LongArrayList packedDataPoints = new LongArrayList(new long[size]);
for (int index = 0; index < size; index++)
{
DhApiTerrainDataPoint dataPoint = columnDataPoints.get(index);
int id = accessor.getMapping().addIfNotPresentAndGetId(
int id = accessor.mapping.addIfNotPresentAndGetId(
(IBiomeWrapper) (dataPoint.biomeWrapper),
(IBlockStateWrapper) (dataPoint.blockStateWrapper)
);
);
packedDataPoints[index] = FullDataPointUtil.encode(
packedDataPoints.set(index, FullDataPointUtil.encode(
id,
dataPoint.topYBlockPos - dataPoint.bottomYBlockPos,
dataPoint.bottomYBlockPos - dataPoints.topYBlockPos,
(byte) (dataPoint.lightLevel)
);
(byte) (dataPoint.blockLightLevel),
(byte) (dataPoint.skyLightLevel)
));
}
accessor.setSingleColumn(packedDataPoints, relX, relZ);
// TODO add the ability for API users to define a different compression mode
// or add a "unkown" compression mode
accessor.setSingleColumn(packedDataPoints, relX, relZ, EDhApiWorldGenerationStep.LIGHT, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
}
}
return accessor;
}
//================//

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