Compare commits
430 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b2f98bdf63 | |||
| fd0a4c55ac | |||
| b0aa211464 | |||
| 14e7918ea0 | |||
| 524ea77ce5 | |||
| 9f763bcd6a | |||
| 166875283c | |||
| abb264bdf0 | |||
| 70aa5724a9 | |||
| 520d37f410 | |||
| 29c2756e75 | |||
| 6c278ea3b1 | |||
| b18460b825 | |||
| d0b50d9633 | |||
| c051b3584f | |||
| d4cad8f718 | |||
| f7bf05b62f | |||
| 840b0a7fe2 | |||
| 5369bf628a | |||
| 79d2269218 | |||
| f21c791269 | |||
| 11e58eecda | |||
| fbf13833a0 | |||
| 2528f4a725 | |||
| 6d6cbd8a44 | |||
| 2fe3c261b0 | |||
| 5086f40d03 | |||
| 7766c49cbd | |||
| c6d86cfa3b | |||
| 935cfec3d4 | |||
| ce2e64dc7e | |||
| 15774ffe2a | |||
| 218cb04696 | |||
| 45fc36543b | |||
| 554bb89690 | |||
| 2aa048b0cb | |||
| 570619b114 | |||
| fb3e47ec3f | |||
| 0f27dd79d7 | |||
| abe0e284aa | |||
| 7a97b9dcbf | |||
| 23c98e2253 | |||
| 06cce40ac6 | |||
| 7cd1a37914 | |||
| 8ca2052748 | |||
| ce4259d98f | |||
| d96ba5ae54 | |||
| 576d0f5666 | |||
| 877588ebed | |||
| 3bee25053f | |||
| a75d3ec5b0 | |||
| d5222ed20f | |||
| 875b5fffcc | |||
| d03a887620 | |||
| 6293cc8c27 | |||
| 847cfa3ca9 | |||
| 0361e5c69b | |||
| 654355c991 | |||
| 4712a77d00 | |||
| 57c5b2d5fc | |||
| a2949b8124 | |||
| 50c5701836 | |||
| 6fe0477ca7 | |||
| 18e075538d | |||
| b00e8a08e9 | |||
| ac4ab11a74 | |||
| c26631db57 | |||
| 1daa06fff4 | |||
| f3ef6f25f4 | |||
| ec012d9fd6 | |||
| fc90cf3377 | |||
| e1e42d1caf | |||
| 95ce29e355 | |||
| 0fd818b077 | |||
| ba59daf747 | |||
| b7d94c2ed1 | |||
| 7a057a8d53 | |||
| 49c6ab97a9 | |||
| ed0d80b37e | |||
| 9768728c92 | |||
| 0c68544f2f | |||
| 048b36f80d | |||
| 40f902e2f5 | |||
| b1f154a0ea | |||
| 628c9b071f | |||
| ed39b6181f | |||
| 1d6d712483 | |||
| 62ddb46674 | |||
| 73c4f0ffcd | |||
| 375cd44cbd | |||
| ce057525d3 | |||
| 01c879951c | |||
| 4f19f05f3b | |||
| 47f68c7ed3 | |||
| 39db421aa1 | |||
| f4b0c08822 | |||
| f17c3fa267 | |||
| 48d1005be6 | |||
| e71e8d1966 | |||
| 489b3d3ae1 | |||
| 81e6f55dbf | |||
| 1b162f10e6 | |||
| 301cce3d11 | |||
| e68d0d5c45 | |||
| b5e2019d28 | |||
| 2cf952fb76 | |||
| c7c5ab17bc | |||
| a98955530f | |||
| c63a509f9e | |||
| 84dca85447 | |||
| dac51a9eea | |||
| 8c3e8136be | |||
| 606c157958 | |||
| ba3677b641 | |||
| 8d78a1ad74 | |||
| 7c11bb4258 | |||
| 0d6ec3d836 | |||
| dee13a4ec4 | |||
| 9449433fe8 | |||
| 2c976c9fb1 | |||
| 90fdfbbe61 | |||
| a8df13fdd2 | |||
| f05eac2637 | |||
| 71e4cd6272 | |||
| 82c5de7dfe | |||
| 4ae30b3d47 | |||
| 8abefdcfd5 | |||
| 801a126de0 | |||
| 377d0fbe12 | |||
| 1ecf968668 | |||
| 9d11733444 | |||
| c43b985f4e | |||
| b7fea64925 | |||
| e62c6a5c55 | |||
| 116d7f4a3f | |||
| 0c36dc03a5 | |||
| c658269eb7 | |||
| defaea86f0 | |||
| f1564cc90b | |||
| 66feb0b9c2 | |||
| 93b57ae2e1 | |||
| 79bfa21115 | |||
| 5588b0d2fa | |||
| e9788acb46 | |||
| 8056a5b8bf | |||
| e5033a0c0f | |||
| e0ad956e34 | |||
| 32abe15b09 | |||
| 2b65e33aa7 | |||
| 9546f9cbc8 | |||
| bc6ab6c840 | |||
| 9834b20a9f | |||
| 752008e8ac | |||
| fbe81021c0 | |||
| ccbb071704 | |||
| 882c5399bd | |||
| dbe0461d5f | |||
| d3d166dd02 | |||
| d0dd1f38ff | |||
| 53300a3028 | |||
| 56303dd82a | |||
| d26cb41048 | |||
| a15aaa573d | |||
| 19d8c89bd8 | |||
| adba3e4c15 | |||
| 479ce8093e | |||
| 1ed6c619d9 | |||
| 825f439ffb | |||
| b1b487e63f | |||
| 2763a7ca75 | |||
| b4b1a2a549 | |||
| 5865317394 | |||
| b70c090e94 | |||
| a9f6b924c2 | |||
| 9c25a6450a | |||
| 448982fbaf | |||
| 2ee06c59f1 | |||
| d38711ca4b | |||
| c9b650fb7f | |||
| 3cef8b9a4f | |||
| d9d9f3dad8 | |||
| 44fe1eafb1 | |||
| 36fcc46445 | |||
| 64895ba521 | |||
| 589340f2db | |||
| de7d22766a | |||
| 568ff40df6 | |||
| 83c1a2fd63 | |||
| 17b5ba0ae1 | |||
| 4d4eeacbdd | |||
| 99dc644adf | |||
| f442ab56c0 | |||
| 9147b139c7 | |||
| 0e904a388c | |||
| 3723137fea | |||
| 71e54cc9ab | |||
| b4cf962a85 | |||
| 1a30f240ef | |||
| b3b5ac63c8 | |||
| 16abc0faa1 | |||
| 778c2f894e | |||
| 2617cd294d | |||
| 43b421042d | |||
| af69678545 | |||
| 85341f7a6d | |||
| 9e0edd0cf3 | |||
| 84e90a7a9b | |||
| 48a8cdc365 | |||
| 171e6b9bcd | |||
| 7e48c49e33 | |||
| 1b48d61d3f | |||
| 47541fa99c | |||
| 13638cedee | |||
| 4cdf31cfe8 | |||
| 3ff9a93066 | |||
| 083a036666 | |||
| a0efe44e8d | |||
| 8d110e22dd | |||
| 4d6e11fdeb | |||
| 454b15ff60 | |||
| d2f4972693 | |||
| acb299530d | |||
| 7c705015e6 | |||
| bcb21be848 | |||
| 562594de2f | |||
| 03a00bb7ca | |||
| dc3aa939db | |||
| 39391b944f | |||
| a6fc8f1702 | |||
| b0777789fd | |||
| 24a2a48deb | |||
| e08faa7943 | |||
| a000afbc60 | |||
| a3e34c9738 | |||
| 1f63bdf124 | |||
| 44205664b5 | |||
| 40d019d7e8 | |||
| 96755e6174 | |||
| bd5866787f | |||
| d9651bbd27 | |||
| a1f4442a53 | |||
| e6b140245b | |||
| 08336027b2 | |||
| 543b5ed49a | |||
| 3d86c5c5ee | |||
| 8e1009ab3f | |||
| 551a5f1a72 | |||
| 2664f96e0f | |||
| dde2bcc947 | |||
| 3e3e97385b | |||
| 05fba12038 | |||
| c1f2803c56 | |||
| 7006c669f0 | |||
| 7cd0c956f6 | |||
| 7c33dda11b | |||
| 5f9f0f23b8 | |||
| 284191a904 | |||
| 0ad60cd5d9 | |||
| 8485d1585a | |||
| 1cd2f75dd2 | |||
| bdc4fa4477 | |||
| ff7720a8d6 | |||
| 6829709123 | |||
| 0d355f675a | |||
| af2f0e8582 | |||
| 3362570123 | |||
| e008eb8a13 | |||
| 1f438d8f87 | |||
| 278ae04532 | |||
| 2d4f033891 | |||
| 1fd85f2249 | |||
| d966697ecb | |||
| 2e6c0ba356 | |||
| ebb47990f4 | |||
| 15fe39c1bb | |||
| 7bb0ec3148 | |||
| 6fcfacd346 | |||
| 48212ba746 | |||
| 7bf9ce141a | |||
| 2353bd7545 | |||
| 02ddf5773c | |||
| 3e37e9ee08 | |||
| b1b907bfe5 | |||
| d347dbd222 | |||
| 44527630da | |||
| df6ac6d740 | |||
| 2ce08f8957 | |||
| 1baa666d23 | |||
| e5e4551038 | |||
| 0cfbe09558 | |||
| 7d72e82325 | |||
| 3f823c58cc | |||
| 3932ea21c2 | |||
| c9d426e581 | |||
| 61c516df1d | |||
| 7e2019abd4 | |||
| f0f3614b9d | |||
| 573a284580 | |||
| 310c890474 | |||
| 8a309b56d9 | |||
| adc3f15c97 | |||
| 962f523af9 | |||
| dbc1ad4cb7 | |||
| 8b0f6a4414 | |||
| 502aaf6a8a | |||
| 5c644fbf5b | |||
| da60ca7560 | |||
| a5a56c7eb7 | |||
| ff8b1f24d0 | |||
| 904c1a7d71 | |||
| 02aca6f044 | |||
| b4ea632b93 | |||
| a2cc1c2913 | |||
| 522a799516 | |||
| 04e43ebec8 | |||
| 077d72c23b | |||
| cb95c45d76 | |||
| a9de079132 | |||
| 166cc55e3c | |||
| f36bffa4b7 | |||
| 6da1e75dad | |||
| 82d66ca392 | |||
| e98cf1f2b5 | |||
| 77bd333fff | |||
| b791a185a2 | |||
| c64d7fedd2 | |||
| d2f5c02238 | |||
| 9cfcf37fb3 | |||
| 84d36df388 | |||
| 880abd0124 | |||
| 72274bfd7d | |||
| 9640169be9 | |||
| 9daf0c7317 | |||
| a4df5a8ed8 | |||
| df3d20f94e | |||
| f3b40f51a2 | |||
| e3d7598501 | |||
| 596b822a5d | |||
| e9e7ac48b1 | |||
| 2e2254fbbd | |||
| 8bda3351b8 | |||
| 532f3adba8 | |||
| cc8b97f2fe | |||
| db31c54309 | |||
| 375e81b17d | |||
| 4fb9126d49 | |||
| ed2fca240e | |||
| f5549f2ebe | |||
| 18e9cfe2a5 | |||
| e4e6753a02 | |||
| 8e7855405d | |||
| 381cae7307 | |||
| e94d67916d | |||
| c98f9476cb | |||
| 6c36f3c7e9 | |||
| 877a89d17a | |||
| d325a69e3f | |||
| 08704aad2c | |||
| a80bb082b3 | |||
| fccd197cd5 | |||
| 2cfc2c81c8 | |||
| 6f4e105542 | |||
| 633b3f2033 | |||
| f0a62c813a | |||
| 21f03526f8 | |||
| 0e7a96d308 | |||
| 87572246a6 | |||
| 025484d5b4 | |||
| 15e4b1316e | |||
| e02eddc60e | |||
| 1e1ddd505b | |||
| 415f16507b | |||
| cb95978502 | |||
| 1b729f3fe7 | |||
| d84e097fa2 | |||
| 51ad901206 | |||
| 517925a207 | |||
| c5abc22c58 | |||
| 2330377212 | |||
| 9db56bbf87 | |||
| 52a90fec6c | |||
| 3630dc724d | |||
| 627bfbc007 | |||
| d9283e938b | |||
| ce4d50654d | |||
| 7568ab56f6 | |||
| bf58654a10 | |||
| e4576c7d51 | |||
| a1f07c075e | |||
| 84c4ad6cdd | |||
| 47aef1f349 | |||
| 1e7a25046a | |||
| 87a9e93278 | |||
| 2d3062008e | |||
| 8dcec7a1bd | |||
| d839b6e4bd | |||
| 0c155ac8cd | |||
| 271b193543 | |||
| e4ac25f4ce | |||
| 52f9e3e9e4 | |||
| 6bf32ff85c | |||
| 9bd432ad7f | |||
| 04fc4aa676 | |||
| 8ee4b8b4c9 | |||
| 5767668efa | |||
| 76b226b865 | |||
| 1788c18d59 | |||
| 2251cd4c25 | |||
| 781b588980 | |||
| 74ac4fe64f | |||
| 6d1f9803ce | |||
| 95d721e1a3 | |||
| d2f2a3b8aa | |||
| 768ce5b8fd | |||
| 3e9f741d62 | |||
| 133f007bde | |||
| a623a3bfab | |||
| 4157a39589 | |||
| 10e2873497 | |||
| d7ef6c8a72 | |||
| d29ba9d423 | |||
| 0c60395426 | |||
| 4ac1c0d4b3 | |||
| 9a0c9e9b7d | |||
| 79408c081a | |||
| 218411902e | |||
| a42f8367a4 | |||
| 02acfaa3ed | |||
| 9a2799e83b | |||
| 73e6ce75b0 |
@@ -0,0 +1 @@
|
|||||||
|
Distant Horizons logos © 2024 by Pankakes are licensed under CC BY-SA 4.0
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Distant Horizons
|
# <img src="https://gitlab.com/jeseibel/distant-horizons-core/-/raw/main/_Misc%20Files%2Flogo%20files%2Fnew%2FSVG%2FDistant-Horizons-Core.svg" height="128px">
|
||||||
|
|
||||||
This repo is for the Distant Horizons mod.
|
This repo is for the Distant Horizons mod.
|
||||||
The purpose of this submodule is to isolate code that isn't tied to a specific version of minecraft. This prevents us from having duplicate code; reducing errors and potentially helping us port to different versions faster and easier.
|
The purpose of this submodule is to isolate code that isn't tied to a specific version of minecraft. This prevents us from having duplicate code; reducing errors and helping us port to different versions faster and easier.
|
||||||
|
|
||||||
Check out the mod's main GitLab page here:
|
Check out the mod's main GitLab page here:
|
||||||
https://gitlab.com/jeseibel/distant-horizons
|
https://gitlab.com/jeseibel/distant-horizons
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Distant Horizons brand guidelines
|
||||||
|
|
||||||
|
To keep our look consistent and recognizable, we’ve created some simple guidelines for using our logos. We have a Primary logo, Core and API logos. Keep them cool 😎
|
||||||
|
|
||||||
|
|
||||||
|
## Logos
|
||||||
|
Please do not edit, change, distort, recolour, or reconfigure our logos.
|
||||||
|
|  |  |  |
|
||||||
|
|--|--|--|
|
||||||
|
|Primary [.png](/PNG/Distant-Horizons.png) [.svg](./SVG/Distant-Horizons-Logo.svg)| Core [.png](/PNG/Distant-Horizons-Core.png) [.svg](./SVG/Distant-Horizons-Core.svg) | API [.png](./PNG/Distant-Horizons-API.png) [.svg](./SVG/Distant-Horizons-API.svg) |
|
||||||
|
|
||||||
|
|
||||||
|
## Marks
|
||||||
|
Use these only when the Distant Horizons brand is clearly visible or has been well established elsewhere on the page or in the design. (When in doubt, use the other ones.)
|
||||||
|
|
||||||
|
> *Keep in mind the Distant Horizons API mark needs to be optically centered to align.*
|
||||||
|
|
||||||
|
<!-- |  |  |  |
|
||||||
|
|--|--|--|
|
||||||
|
|Primary [.png](/PNG/Distant-Horizons-Mark.png.png) [.svg](./SVG/Distant-Horizons-Mark.svg.svg)| Core [.png](/PNG/Distant-Horizons-Core-Mark.png.png) [.svg](./SVG/Distant-Horizons-Core-Mark.svg.svg) | API [.png](./PNG/Distant-Horizons-API-Mark.png.png) [.svg](./SVG/Distant-Horizons-API-Mark.svg.svg) | -->
|
||||||
|
|
||||||
|
| <img src="./PNG/Distant-Horizons-Mark.png" width="100"/> | <img src="./PNG/Distant-Horizons-Core-Mark.png" width="100"/> | <img src="./PNG/Distant-Horizons-API-Mark.png" width="100"/> |
|
||||||
|
|--|--|--|
|
||||||
|
|Primary [.png](/PNG/Distant-Horizons-Mark.png.png) [.svg](./SVG/Distant-Horizons-Mark.svg.svg)| Core [.png](/PNG/Distant-Horizons-Core-Mark.png.png) [.svg](./SVG/Distant-Horizons-Core-Mark.svg.svg) | API [.png](./PNG/Distant-Horizons-API-Mark.png.png) [.svg](./SVG/Distant-Horizons-API-Mark.svg.svg) |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Spacing
|
||||||
|
Every logo needs room to breathe.
|
||||||
|
Ours needs the free space of the height and width of the letter **"H"** in Distant **H**orizons.
|
||||||
|
|
||||||
|
> *Some leeway is allowed for the Distant Horizons API mark due to it's shape.*
|
||||||
|
|
||||||
|
|  |  |
|
||||||
|
|--|--|
|
||||||
|
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
The logotype we are using in our logos is a modified [Karmatic Arcade font by Vic Fieger](https://www.dafont.com/karmatic-arcade.font?fpp=100&psize=s)
|
||||||
|
|
||||||
|
This branding guideline was influenced by [Discord's own](https://discord.com/branding)
|
||||||
|
|
||||||
|
Distant Horizons logos © 2024 by Pankakes are licensed under CC BY-SA 4.0
|
||||||
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 78 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 306.5 359.13"><defs><style>.cls-1{fill:#7ec138;}.cls-2{fill:#12af68;}.cls-3{fill:#fff;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Logo_RGB_White" data-name="Logo RGB White"><path id="Outline" d="M175.21,356.1l123.72-71.92A15.22,15.22,0,0,0,306.5,271V126.5a22.32,22.32,0,0,0-11.1-19.29L173.18,36.13a18.26,18.26,0,0,0-18.36,0L32.59,107.21A22.31,22.31,0,0,0,21.5,126.5V271a15.22,15.22,0,0,0,7.56,13.15L152.78,356.1A22.29,22.29,0,0,0,175.21,356.1Z"/><g id="Right_Path_" data-name="Right <Path>"><path class="cls-1" d="M224.5,254.1v51.61a8,8,0,0,1-4,6.92l-44,25.4a4,4,0,0,1-6-3.46V283a8,8,0,0,1,4-6.92l44-25.41A4,4,0,0,1,224.5,254.1Z"/></g><g id="_Path_" data-name="<Path>"><path class="cls-2" d="M113.82,173.07l-11-6.36a4,4,0,0,0-6,3.46v51.72a8,8,0,0,0,4,6.92l11,6.35a4,4,0,0,0,6-3.46V180A8,8,0,0,0,113.82,173.07Z"/></g><g id="_Path_2" data-name="<Path>"><path class="cls-2" d="M129.26,150.11l11.19-6.46a4,4,0,0,0,0-6.93l-11.19-6.46a8,8,0,0,0-8,0l-11.2,6.46a4,4,0,0,0,0,6.93l11.2,6.46A8,8,0,0,0,129.26,150.11Z"/></g><g id="Right_Path_2" data-name="Right <Path>"><path class="cls-2" d="M170.5,244v12.74a4,4,0,0,0,6,3.46l44-25.4a8,8,0,0,0,4-6.92V215.15a4,4,0,0,0-6-3.46l-44,25.4A8,8,0,0,0,170.5,244Z"/></g><g id="Right_Path_3" data-name="Right <Path>"><path class="cls-1" d="M243.5,299.35l45-26a8,8,0,0,0,4-6.92V136.92a4,4,0,0,0-6-3.46l-45,26a8,8,0,0,0-4,6.92V295.89A4,4,0,0,0,243.5,299.35Z"/></g><g id="Left_Path_" data-name="Left <Path>"><path class="cls-2" d="M157.5,244v12.74a4,4,0,0,1-6,3.46l-11-6.35a8,8,0,0,1-4-6.92V234.2a4,4,0,0,1,6-3.46l11,6.35A8,8,0,0,1,157.5,244Z"/></g><g id="Left_Path_2" data-name="Left <Path>"><path class="cls-1" d="M153.5,276.08l-112-64.66a4,4,0,0,0-6,3.46v51.57a8,8,0,0,0,4,6.92L151.5,338a4,4,0,0,0,6-3.46V283A8,8,0,0,0,153.5,276.08Z"/></g><g id="_Path_3" data-name="<Path>"><path class="cls-3" d="M157.5,205v12.74a4,4,0,0,1-6,3.46l-11-6.36a8,8,0,0,1-4-6.92V195.23a4,4,0,0,1,6-3.46l11,6.35A8,8,0,0,1,157.5,205Z"/></g><path d="M91.15,171.19V235a16,16,0,0,1-24,13.85L11.59,216.81A23.27,23.27,0,0,1,0,196.73V132.89A16,16,0,0,1,24,119l55.56,32.07A23.24,23.24,0,0,1,91.15,171.19Z"/><g id="_Path_4" data-name="<Path>"><path class="cls-1" d="M14,138.09v58.64a9.17,9.17,0,0,0,4.6,8l51.05,29.48a5,5,0,0,0,7.5-4.33V171.19a9.18,9.18,0,0,0-4.59-8L21.5,133.76A5,5,0,0,0,14,138.09Z"/></g><g id="_Path_5" data-name="<Path>"><path class="cls-3" d="M170.5,217.78V205.05a8,8,0,0,1,4-6.93l11-6.35a4,4,0,0,1,6,3.46V208a8,8,0,0,1-4,6.93l-11,6.35A4,4,0,0,1,170.5,217.78Z"/></g><g id="_Path_6" data-name="<Path>"><path class="cls-2" d="M209.5,202.19l11-6.36a8,8,0,0,0,4-6.92V176.18a4,4,0,0,0-6-3.47l-11,6.36a8,8,0,0,0-4,6.93v12.72A4,4,0,0,0,209.5,202.19Z"/></g><path d="M300.65,84.93a15.94,15.94,0,0,1-7.92,13.73L245.5,125.91a20,20,0,0,1-20.06,0L164,90.44l-61.45,35.48a20.11,20.11,0,0,1-20.06,0L35.27,98.66a15.84,15.84,0,0,1,0-27.44L154,2.69a20,20,0,0,1,20.06,0l118.7,68.53A15.89,15.89,0,0,1,300.65,84.93Z"/><g id="_Path_7" data-name="<Path>"><path class="cls-2" d="M169,117.4l-11,6.34a4,4,0,0,0,0,6.93l44.93,26a8,8,0,0,0,8,0l11-6.35a4,4,0,0,0,0-6.93L177,117.4A8,8,0,0,0,169,117.4Z"/></g><g id="_Path_8" data-name="<Path>"><path class="cls-3" d="M168,187.21l11.19-6.46a4,4,0,0,0,0-6.93L168,167.36a8,8,0,0,0-8,0l-11.2,6.46a4,4,0,0,0,0,6.93l11.2,6.46A8,8,0,0,0,168,187.21Z"/></g><g id="_Path_9" data-name="<Path>"><path class="cls-1" d="M280.42,87.31l-44.73,25.82a8,8,0,0,1-8,0L168,78.66a8,8,0,0,0-8,0l-59.7,34.47a8,8,0,0,1-8,0L47.58,87.31a4,4,0,0,1,0-6.93L160,15.47a8,8,0,0,1,8,0L280.42,80.38A4,4,0,0,1,280.42,87.31Z"/></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 285 325.47"><defs><style>.cls-1{fill:#e13e1e;}.cls-2{fill:#f7a612;}.cls-3{fill:#fff;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Logo_RGB_White" data-name="Logo RGB White"><path id="Outline" d="M153.72,322.45l123.71-71.93A15.22,15.22,0,0,0,285,237.36V92.84a22.32,22.32,0,0,0-11.1-19.29L151.68,2.47a18.31,18.31,0,0,0-18.36,0L11.1,73.55A22.32,22.32,0,0,0,0,92.84V237.35A15.2,15.2,0,0,0,7.57,250.5l123.71,71.94A22.31,22.31,0,0,0,153.72,322.45Z"/><g id="Right_Path_" data-name="Right <Path>"><path class="cls-1" d="M203,220.44v51.61a8,8,0,0,1-4,6.93l-44,25.39a4,4,0,0,1-6-3.46v-51.6a8,8,0,0,1,4-6.92L197,217A4,4,0,0,1,203,220.44Z"/></g><g id="Right_Path_2" data-name="Right <Path>"><path class="cls-2" d="M149,210.36v12.73a4,4,0,0,0,6,3.46l44-25.39a8,8,0,0,0,4-6.93V181.49a4,4,0,0,0-6-3.46l-44,25.4A8,8,0,0,0,149,210.36Z"/></g><g id="Right_Path_3" data-name="Right <Path>"><path class="cls-1" d="M222,265.69l45-26a8,8,0,0,0,4-6.93V103.26a4,4,0,0,0-6-3.46l-45,26a8,8,0,0,0-4,6.93V262.23A4,4,0,0,0,222,265.69Z"/></g><g id="Left_Path_" data-name="Left <Path>"><path class="cls-2" d="M136,210.36v12.73a4,4,0,0,1-6,3.46l-11-6.34a8,8,0,0,1-4-6.93V200.54a4,4,0,0,1,6-3.46l11,6.35A8,8,0,0,1,136,210.36Z"/></g><g id="Left_Path_2" data-name="Left <Path>"><path class="cls-1" d="M132,242.42,20,177.76a4,4,0,0,0-6,3.46v51.57a8,8,0,0,0,4,6.93l112,64.65a4,4,0,0,0,6-3.46V249.35A8,8,0,0,0,132,242.42Z"/></g><g id="_Path_" data-name="<Path>"><path class="cls-3" d="M136,171.39v12.73a4,4,0,0,1-6,3.46l-11-6.35a8,8,0,0,1-4-6.93V161.57a4,4,0,0,1,6-3.46l11,6.35A8,8,0,0,1,136,171.39Z"/></g><g id="_Path_2" data-name="<Path>"><path class="cls-2" d="M99,145.41l-11-6.35a4,4,0,0,0-6,3.46v51.71a8,8,0,0,0,4,6.93l11,6.34a4,4,0,0,0,6-3.46v-51.7A8,8,0,0,0,99,145.41Z"/></g><g id="_Path_3" data-name="<Path>"><path class="cls-1" d="M14.33,103.26V155a8,8,0,0,0,4,6.93l45,26a4,4,0,0,0,6-3.46v-51.7a8,8,0,0,0-4-6.93l-45-26A4,4,0,0,0,14.33,103.26Z"/></g><g id="_Path_4" data-name="<Path>"><path class="cls-3" d="M149,184.12V171.39a8,8,0,0,1,4-6.93l11-6.35a4,4,0,0,1,6,3.46V174.3a8,8,0,0,1-4,6.93l-11,6.35A4,4,0,0,1,149,184.12Z"/></g><g id="_Path_5" data-name="<Path>"><path class="cls-2" d="M188,168.53l11-6.35a8,8,0,0,0,4-6.93V142.52a4,4,0,0,0-6-3.47l-11,6.36a8,8,0,0,0-4,6.93v12.72A4,4,0,0,0,188,168.53Z"/></g><g id="_Path_6" data-name="<Path>"><path class="cls-1" d="M258.92,88.65l-44.73,25.82a8,8,0,0,1-8,0L146.5,80a8,8,0,0,0-8,0L78.8,114.47a8,8,0,0,1-8,0L26.08,88.65a4,4,0,0,1,0-6.93L138.5,16.82a8,8,0,0,1,8,0l112.42,64.9A4,4,0,0,1,258.92,88.65Z"/></g><g id="_Path_7" data-name="<Path>"><path class="cls-2" d="M138.5,94.74l-11,6.35a4,4,0,0,0,0,6.92l44.94,26a8,8,0,0,0,8,0l11-6.35a4,4,0,0,0,0-6.93L146.5,94.74A8,8,0,0,0,138.5,94.74Z"/></g><g id="_Path_8" data-name="<Path>"><path class="cls-2" d="M112.76,134.08,124,127.61a4,4,0,0,0,0-6.93l-11.19-6.46a8,8,0,0,0-8,0l-11.2,6.46a4,4,0,0,0,0,6.93l11.2,6.47A8,8,0,0,0,112.76,134.08Z"/></g><g id="_Path_9" data-name="<Path>"><path class="cls-3" d="M146.5,153.56l11.19-6.47a4,4,0,0,0,0-6.93L146.5,133.7a8,8,0,0,0-8,0l-11.19,6.46a4,4,0,0,0,0,6.93l11.19,6.47A8,8,0,0,0,146.5,153.56Z"/></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 285 325.47"><defs><style>.cls-1{fill:#38c138;}.cls-2{fill:#f6d182;}.cls-3{fill:#fff;}.cls-4{fill:#00a9f4;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Logo_RGB_White" data-name="Logo RGB White"><path id="Outline" d="M153.72,322.45l123.71-71.93A15.22,15.22,0,0,0,285,237.36V92.84a22.32,22.32,0,0,0-11.1-19.29L151.68,2.47a18.31,18.31,0,0,0-18.36,0L11.1,73.55A22.32,22.32,0,0,0,0,92.84V237.35A15.2,15.2,0,0,0,7.57,250.5l123.71,71.94A22.31,22.31,0,0,0,153.72,322.45Z"/><path id="Green" class="cls-1" d="M203,220.44v51.61a8,8,0,0,1-4,6.93l-44,25.39a4,4,0,0,1-6-3.46v-51.6a8,8,0,0,1,4-6.92L197,217A4,4,0,0,1,203,220.44Zm-54-10.08v12.73a4,4,0,0,0,6,3.46l44-25.39a8,8,0,0,0,4-6.93V181.49a4,4,0,0,0-6-3.46l-44,25.4A8,8,0,0,0,149,210.36Zm73,55.33,45-26a8,8,0,0,0,4-6.93V103.26a4,4,0,0,0-6-3.46l-45,26a8,8,0,0,0-4,6.93V262.23A4,4,0,0,0,222,265.69Z"/><path id="Yellow" class="cls-2" d="M136,210.36v12.73a4,4,0,0,1-6,3.46l-11-6.34a8,8,0,0,1-4-6.93V200.54a4,4,0,0,1,6-3.46l11,6.35A8,8,0,0,1,136,210.36Zm-4,32.06L20,177.76a4,4,0,0,0-6,3.46v51.57a8,8,0,0,0,4,6.93l112,64.65a4,4,0,0,0,6-3.46V249.35A8,8,0,0,0,132,242.42Z"/><g id="_Path_" data-name="<Path>"><path class="cls-3" d="M136,171.39v12.73a4,4,0,0,1-6,3.46l-11-6.35a8,8,0,0,1-4-6.93V161.57a4,4,0,0,1,6-3.46l11,6.35A8,8,0,0,1,136,171.39Z"/></g><g id="_Path_2" data-name="<Path>"><path class="cls-4" d="M99,145.41l-11-6.35a4,4,0,0,0-6,3.46v51.71a8,8,0,0,0,4,6.93l11,6.34a4,4,0,0,0,6-3.46v-51.7A8,8,0,0,0,99,145.41Z"/></g><g id="_Path_3" data-name="<Path>"><path class="cls-4" d="M14,103.26V155a8,8,0,0,0,4,6.93l45,26a4,4,0,0,0,6-3.46v-51.7a8,8,0,0,0-4-6.93l-45-26A4,4,0,0,0,14,103.26Z"/></g><g id="_Path_4" data-name="<Path>"><path class="cls-3" d="M149,184.12V171.39a8,8,0,0,1,4-6.93l11-6.35a4,4,0,0,1,6,3.46V174.3a8,8,0,0,1-4,6.93l-11,6.35A4,4,0,0,1,149,184.12Z"/></g><g id="_Path_5" data-name="<Path>"><path class="cls-4" d="M188,168.53l11-6.35a8,8,0,0,0,4-6.93V142.52a4,4,0,0,0-6-3.47l-11,6.36a8,8,0,0,0-4,6.93v12.72A4,4,0,0,0,188,168.53Z"/></g><g id="_Path_6" data-name="<Path>"><path class="cls-4" d="M258.92,88.65l-44.73,25.82a8,8,0,0,1-8,0L146.5,80a8,8,0,0,0-8,0L78.8,114.47a8,8,0,0,1-8,0L26.08,88.65a4,4,0,0,1,0-6.93L138.5,16.82a8,8,0,0,1,8,0l112.42,64.9A4,4,0,0,1,258.92,88.65Z"/></g><g id="_Path_7" data-name="<Path>"><path class="cls-4" d="M138.5,94.74l-11,6.35a4,4,0,0,0,0,6.92l44.94,26a8,8,0,0,0,8,0l11-6.35a4,4,0,0,0,0-6.93L146.5,94.74A8,8,0,0,0,138.5,94.74Z"/></g><g id="_Path_8" data-name="<Path>"><path class="cls-4" d="M112.76,134.08,124,127.61a4,4,0,0,0,0-6.93l-11.19-6.46a8,8,0,0,0-8,0l-11.2,6.46a4,4,0,0,0,0,6.93l11.2,6.47A8,8,0,0,0,112.76,134.08Z"/></g><g id="_Path_9" data-name="<Path>"><path class="cls-3" d="M146.5,153.56l11.19-6.47a4,4,0,0,0,0-6.93L146.5,133.7a8,8,0,0,0-8,0l-11.19,6.46a4,4,0,0,0,0,6.93l11.19,6.47A8,8,0,0,0,146.5,153.56Z"/></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 191 KiB |
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 174 KiB |
@@ -180,7 +180,7 @@ public class DhApi
|
|||||||
* This version should be updated whenever non-breaking fixes are added to the Distant Horizons API.
|
* This version should be updated whenever non-breaking fixes are added to the Distant Horizons API.
|
||||||
* @since API 1.0.0
|
* @since API 1.0.0
|
||||||
*/
|
*/
|
||||||
public static int getApiPatchVersion() { return ModInfo.API_PATH_VERSION; }
|
public static int getApiPatchVersion() { return ModInfo.API_PATCH_VERSION; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the mod's semantic version number in the format: Major.Minor.Patch
|
* Returns the mod's semantic version number in the format: Major.Minor.Patch
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ package com.seibel.distanthorizons.api.enums;
|
|||||||
* CHUNK - Detail Level: 4, width 16 block, <br>
|
* CHUNK - Detail Level: 4, width 16 block, <br>
|
||||||
* REGION - Detail Level: 9, width 512 block <br> <br>
|
* REGION - Detail Level: 9, width 512 block <br> <br>
|
||||||
*
|
*
|
||||||
* Detail levels in Distant Horizons represent how large a section (of either LODs or MC chunks)
|
* Detail levels in Distant Horizons represent how large a LOD
|
||||||
* is, with the smallest being 0 (1 block wide). <br>
|
* is, with the smallest being 0 (1 block wide). <br>
|
||||||
* The width of a detail level can be calculated by putting the detail level to the power of 2. <br>
|
* The width of a detail level can be calculated by putting the detail level to the power of 2. <br>
|
||||||
* Example for the chunk detail level (4): 2^4 = 16 blocks wide <Br><br>
|
* Example for the chunk detail level (4): 2^4 = 16 blocks wide <Br><br>
|
||||||
|
|||||||
@@ -23,13 +23,12 @@ package com.seibel.distanthorizons.api.enums.config;
|
|||||||
* AUTO, <br>
|
* AUTO, <br>
|
||||||
* BUFFER_STORAGE, <br>
|
* BUFFER_STORAGE, <br>
|
||||||
* SUB_DATA, <br>
|
* SUB_DATA, <br>
|
||||||
* BUFFER_MAPPING, <br>
|
|
||||||
* DATA <br>
|
* DATA <br>
|
||||||
*
|
*
|
||||||
* @author Leetom
|
* @author Leetom
|
||||||
* @author James Seibel
|
* @author James Seibel
|
||||||
* @version 2024-4-6
|
* @version 2024-4-6
|
||||||
* @since API 2.0.0
|
* @since API 3.0.0
|
||||||
*/
|
*/
|
||||||
public enum EDhApiGpuUploadMethod
|
public enum EDhApiGpuUploadMethod
|
||||||
{
|
{
|
||||||
@@ -49,7 +48,10 @@ public enum EDhApiGpuUploadMethod
|
|||||||
* May end up storing buffers in System memory. <br>
|
* May end up storing buffers in System memory. <br>
|
||||||
* Fast rending if in GPU memory, slow if in system memory, <br>
|
* Fast rending if in GPU memory, slow if in system memory, <br>
|
||||||
* but won't stutter when uploading.
|
* but won't stutter when uploading.
|
||||||
|
*
|
||||||
|
* @deprecated not currently supported
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
BUFFER_MAPPING(true, false),
|
BUFFER_MAPPING(true, false),
|
||||||
|
|
||||||
/** Fast rendering but may stutter when uploading. */
|
/** Fast rendering but may stutter when uploading. */
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.api.enums.rendering;
|
|||||||
* AIR, <br>
|
* AIR, <br>
|
||||||
* ILLUMINATED, <br>
|
* ILLUMINATED, <br>
|
||||||
*
|
*
|
||||||
|
* @author IMS
|
||||||
* @author James Seibel
|
* @author James Seibel
|
||||||
* @since API 3.0.0
|
* @since API 3.0.0
|
||||||
* @version 2024-7-11
|
* @version 2024-7-11
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.api.interfaces.block;
|
package com.seibel.distanthorizons.api.interfaces.block;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
|
||||||
import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
|
import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +45,11 @@ public interface IDhApiBlockStateWrapper extends IDhApiUnsafeWrapper
|
|||||||
* @since API 3.0.0
|
* @since API 3.0.0
|
||||||
*/
|
*/
|
||||||
String getSerialString();
|
String getSerialString();
|
||||||
/** @since API 3.0.0 */
|
/**
|
||||||
|
* Returns the byte value representing the {@link EDhApiBlockMaterial} enum.
|
||||||
|
* @see EDhApiBlockMaterial
|
||||||
|
* @since API 3.0.0
|
||||||
|
*/
|
||||||
byte getMaterialId();
|
byte getMaterialId();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ public interface IDhApiConfig
|
|||||||
IDhApiWorldGenerationConfig worldGenerator();
|
IDhApiWorldGenerationConfig worldGenerator();
|
||||||
IDhApiMultiplayerConfig multiplayer();
|
IDhApiMultiplayerConfig multiplayer();
|
||||||
IDhApiMultiThreadingConfig multiThreading();
|
IDhApiMultiThreadingConfig multiThreading();
|
||||||
IDhApiGpuBuffersConfig gpuBuffers();
|
|
||||||
// note: DON'T add the Auto Updater to this API. We only want the user's to have the ability to control when things are downloaded to their machines.
|
// note: DON'T add the Auto Updater to this API. We only want the user's to have the ability to control when things are downloaded to their machines.
|
||||||
//IDhApiLoggingConfig logging(); // TODO implement
|
//IDhApiLoggingConfig logging(); // TODO implement
|
||||||
IDhApiDebuggingConfig debugging();
|
IDhApiDebuggingConfig debugging();
|
||||||
|
|||||||
@@ -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.interfaces.config.client;
|
|
||||||
|
|
||||||
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
|
|
||||||
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
|
|
||||||
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Distant Horizons' OpenGL buffer configuration.
|
|
||||||
*
|
|
||||||
* @author James Seibel
|
|
||||||
* @version 2023-6-14
|
|
||||||
* @since API 1.0.0
|
|
||||||
*/
|
|
||||||
public interface IDhApiGpuBuffersConfig extends IDhApiConfigGroup
|
|
||||||
{
|
|
||||||
|
|
||||||
/** Defines how geometry data is uploaded to the GPU. */
|
|
||||||
IDhApiConfigValue<EDhApiGpuUploadMethod> gpuUploadMethod();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines how long we should wait after uploading one
|
|
||||||
* Megabyte of geometry data to the GPU before uploading
|
|
||||||
* the next Megabyte of data. <br>
|
|
||||||
* This can be set to a non-zero number to reduce stuttering caused by
|
|
||||||
* uploading buffers to the GPU.
|
|
||||||
*/
|
|
||||||
IDhApiConfigValue<Integer> gpuUploadPerMegabyteInMilliseconds();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -79,11 +79,34 @@ public interface IDhApiWrapperFactory
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
///**
|
/**
|
||||||
// * Specifically designed to be used with the API.
|
* Constructs a {@link IDhApiBiomeWrapper} for use by other DhApi methods.
|
||||||
// *
|
*
|
||||||
// * @throws ClassCastException with instructions on expected objects if the object couldn't be cast
|
* @param resourceLocationString example: "minecraft:plains"
|
||||||
// */
|
*
|
||||||
//IChunkWrapper createChunkWrapper(Object[] objectArray) throws ClassCastException;
|
* @param levelWrapper Expects a {@link IDhApiLevelWrapper} returned by one of DH's {@link DhApi.Delayed#worldProxy} methods. <br>
|
||||||
|
* A custom implementation of {@link IDhApiLevelWrapper} will not be accepted.
|
||||||
|
*
|
||||||
|
* @throws IOException if the resourceLocationString wasn't able to be parsed or converted into a valid {@link IDhApiBiomeWrapper}
|
||||||
|
* @throws ClassCastException if the wrong levelWrapper type was given
|
||||||
|
*
|
||||||
|
* @since API 3.0.0
|
||||||
|
*/
|
||||||
|
IDhApiBiomeWrapper getBiomeWrapper(String resourceLocationString, IDhApiLevelWrapper levelWrapper) throws IOException, ClassCastException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@link IDhApiBlockStateWrapper} for use by other DhApi methods.
|
||||||
|
* This returns the default blockstate for the given resource location.
|
||||||
|
*
|
||||||
|
* @param resourceLocationString examples: "minecraft:bedrock", "minecraft:stone", "minecraft:grass_block"
|
||||||
|
* @param levelWrapper Expects a {@link IDhApiBlockStateWrapper} returned by one of DH's {@link DhApi.Delayed#worldProxy} methods. <br>
|
||||||
|
* A custom implementation of {@link IDhApiBlockStateWrapper} will not be accepted.
|
||||||
|
*
|
||||||
|
* @throws IOException if the resourceLocationString wasn't able to be parsed or converted into a valid {@link IDhApiBlockStateWrapper}
|
||||||
|
* @throws ClassCastException if the wrong levelWrapper type was given
|
||||||
|
*
|
||||||
|
* @since API 3.0.0
|
||||||
|
*/
|
||||||
|
IDhApiBlockStateWrapper getDefaultBlockStateWrapper(String resourceLocationString, IDhApiLevelWrapper levelWrapper) throws IOException, ClassCastException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ package com.seibel.distanthorizons.api.interfaces.override.worldGenerator;
|
|||||||
import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
|
import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
|
||||||
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
|
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
|
||||||
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
|
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
|
||||||
|
import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
|
||||||
|
import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint;
|
||||||
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
@@ -77,13 +79,41 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh
|
|||||||
}, worldGeneratorThreadPool);
|
}, worldGeneratorThreadPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final CompletableFuture<Void> generateApiChunks(
|
||||||
|
int chunkPosMinX,
|
||||||
|
int chunkPosMinZ,
|
||||||
|
byte granularity,
|
||||||
|
byte targetDataDetail,
|
||||||
|
EDhApiDistantGeneratorMode generatorMode,
|
||||||
|
ExecutorService worldGeneratorThreadPool,
|
||||||
|
Consumer<DhApiChunk> resultConsumer
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return CompletableFuture.runAsync(() ->
|
||||||
|
{
|
||||||
|
// TODO what does this mean?
|
||||||
|
int genChunkWidth = BitShiftUtil.powerOfTwo(granularity - 4);
|
||||||
|
|
||||||
|
for (int chunkX = chunkPosMinX; chunkX < chunkPosMinX + genChunkWidth; chunkX++)
|
||||||
|
{
|
||||||
|
for (int chunkZ = chunkPosMinZ; chunkZ < chunkPosMinZ + genChunkWidth; chunkZ++)
|
||||||
|
{
|
||||||
|
DhApiChunk apiChunk = this.generateApiChunk(chunkX, chunkZ, generatorMode);
|
||||||
|
resultConsumer.accept(apiChunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, worldGeneratorThreadPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called to generate terrain over a given area
|
* This method is called to generate terrain over a given area
|
||||||
* from a thread defined by Distant Horizons. <br><br>
|
* from a thread defined by Distant Horizons. <br><br>
|
||||||
*
|
*
|
||||||
* @param chunkPosX the chunk X position in the level (not to be confused with the chunk's BlockPos in the level)
|
* @param chunkPosX the chunk X position in the level (not to be confused with the chunk's BlockPos in the level)
|
||||||
* @param chunkPosZ the chunk Z position in the level (not to be confused with the chunk's BlockPos in the level)
|
* @param chunkPosZ the chunk Z position in the level (not to be confused with the chunk's BlockPos in the level)
|
||||||
* @param generatorMode how far into the world gen pipeline this method run. See {@link EDhApiDistantGeneratorMode} for additional documentation.
|
* @param generatorMode how far into the world gen pipeline this method should run. See {@link EDhApiDistantGeneratorMode} for additional documentation.
|
||||||
*
|
*
|
||||||
* @return See {@link IDhApiWorldGenerator#generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer) IDhApiWorldGenerator.generateChunks}
|
* @return See {@link IDhApiWorldGenerator#generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer) IDhApiWorldGenerator.generateChunks}
|
||||||
* for the list of Object's this method should return along with additional documentation.
|
* for the list of Object's this method should return along with additional documentation.
|
||||||
@@ -92,4 +122,21 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh
|
|||||||
*/
|
*/
|
||||||
public abstract Object[] generateChunk(int chunkPosX, int chunkPosZ, EDhApiDistantGeneratorMode generatorMode);
|
public abstract Object[] generateChunk(int chunkPosX, int chunkPosZ, EDhApiDistantGeneratorMode generatorMode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called to generate terrain over a given area
|
||||||
|
* from a thread defined by Distant Horizons. <br><br>
|
||||||
|
*
|
||||||
|
* @param chunkPosX the chunk X position in the level (not to be confused with the chunk's BlockPos in the level)
|
||||||
|
* @param chunkPosZ the chunk Z position in the level (not to be confused with the chunk's BlockPos in the level)
|
||||||
|
* @param generatorMode how far into the world gen pipeline this method should run. See {@link EDhApiDistantGeneratorMode} for additional documentation.
|
||||||
|
*
|
||||||
|
* @return A {@link DhApiChunk} with the generated {@link DhApiTerrainDataPoint} including air blocks.
|
||||||
|
* Note: if air blocks aren't included with the proper lighting, lower detail levels will appear as black/unlit.
|
||||||
|
*
|
||||||
|
* @see IDhApiWorldGenerator#generateApiChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)
|
||||||
|
*
|
||||||
|
* @since API 3.0.0
|
||||||
|
*/
|
||||||
|
public abstract DhApiChunk generateApiChunk(int chunkPosX, int chunkPosZ, EDhApiDistantGeneratorMode generatorMode);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,11 +90,29 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
|||||||
*/
|
*/
|
||||||
default byte getMaxGenerationGranularity() { return (byte) (EDhApiDetailLevel.CHUNK.detailLevel + 2); }
|
default byte getMaxGenerationGranularity() { return (byte) (EDhApiDetailLevel.CHUNK.detailLevel + 2); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the generator is unable to accept new generation requests.
|
* Starting in API 3.0.0 DH now handles future queuing/management internally. <br><br>
|
||||||
|
*
|
||||||
|
* Previous description: <br>
|
||||||
|
* true if the generator is unable to accept new generation requests. <br>
|
||||||
|
*
|
||||||
* @since API 1.0.0
|
* @since API 1.0.0
|
||||||
|
* @deprecated API 3.0.0
|
||||||
*/
|
*/
|
||||||
boolean isBusy();
|
@Deprecated
|
||||||
|
default boolean isBusy() { return false; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only used if {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}. <Br>
|
||||||
|
* If true DH will run additional validation on the {@link DhApiChunk}'s returned. <Br>
|
||||||
|
* This should be disabled during release but should be enabled during development to help spot issues with your data format.
|
||||||
|
*
|
||||||
|
* @see #getReturnType()
|
||||||
|
* @see DhApiChunk
|
||||||
|
* @see EDhApiWorldGeneratorReturnType#API_CHUNKS
|
||||||
|
* @since API 3.0.0
|
||||||
|
*/
|
||||||
|
default boolean runApiChunkValidation() { return true; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -154,6 +172,7 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
|||||||
*
|
*
|
||||||
* After the {@link DhApiChunk} has been generated, it should be passed into the
|
* After the {@link DhApiChunk} has been generated, it should be passed into the
|
||||||
* resultConsumer's {@link Consumer#accept(Object)} method.
|
* resultConsumer's {@link Consumer#accept(Object)} method.
|
||||||
|
* Note: if air blocks aren't included in the with the {@link DhApiChunk} with proper lighting, lower detail levels will appear as black/unlit.
|
||||||
*
|
*
|
||||||
* @implNote the default implementation of this method throws an {@link UnsupportedOperationException},
|
* @implNote the default implementation of this method throws an {@link UnsupportedOperationException},
|
||||||
* and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}.
|
* and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}.
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import com.seibel.distanthorizons.api.objects.DhApiResult;
|
|||||||
* Used to interact with Distant Horizons' rendering system.
|
* Used to interact with Distant Horizons' rendering system.
|
||||||
*
|
*
|
||||||
* @author James Seibel
|
* @author James Seibel
|
||||||
* @version 2023-10-13
|
* @version 2024-7-27
|
||||||
* @since API 1.0.0
|
* @since API 1.0.0
|
||||||
*/
|
*/
|
||||||
public interface IDhApiRenderProxy
|
public interface IDhApiRenderProxy
|
||||||
@@ -39,10 +39,8 @@ public interface IDhApiRenderProxy
|
|||||||
* If this is called on a dedicated server it won't do anything and will return {@link DhApiResult#success} = false <Br><Br>
|
* If this is called on a dedicated server it won't do anything and will return {@link DhApiResult#success} = false <Br><Br>
|
||||||
*
|
*
|
||||||
* Background: <Br>
|
* Background: <Br>
|
||||||
* Distant Horizons has two different file formats: Full data and Render data. <Br>
|
* When rendering Distant Horizons bakes each block's color into the geometry that's rendered. <Br>
|
||||||
* - Full data files store the block, biome, etc. information and is the result of loading or generating new chunks. <Br>
|
* This improves rendering speed and VRAM size, but prevents dynamically changing LOD colors. <Br>
|
||||||
* - Render data files store LOD colors and are created using the Full data and currently loaded resource packs. <Br>
|
|
||||||
* This is the data cleared by this method.
|
|
||||||
*/
|
*/
|
||||||
DhApiResult<Boolean> clearRenderDataCache();
|
DhApiResult<Boolean> clearRenderDataCache();
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
|
|||||||
*/
|
*/
|
||||||
public interface IDhApiDimensionTypeWrapper extends IDhApiUnsafeWrapper
|
public interface IDhApiDimensionTypeWrapper extends IDhApiUnsafeWrapper
|
||||||
{
|
{
|
||||||
String getDimensionName();
|
|
||||||
|
|
||||||
boolean hasCeiling();
|
boolean hasCeiling();
|
||||||
|
|
||||||
boolean hasSkyLight();
|
boolean hasSkyLight();
|
||||||
|
|||||||
@@ -28,20 +28,34 @@ import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegist
|
|||||||
* A level is equivalent to a dimension in vanilla Minecraft.
|
* A level is equivalent to a dimension in vanilla Minecraft.
|
||||||
*
|
*
|
||||||
* @author James Seibel
|
* @author James Seibel
|
||||||
* @version 2022-7-14
|
* @version 2024-7-28
|
||||||
* @since API 1.0.0
|
* @since API 1.0.0
|
||||||
*/
|
*/
|
||||||
public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
|
public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
|
||||||
{
|
{
|
||||||
IDhApiDimensionTypeWrapper getDimensionType();
|
IDhApiDimensionTypeWrapper getDimensionType();
|
||||||
|
|
||||||
|
/** @since API 4.0.0 */
|
||||||
|
String getDimensionName();
|
||||||
|
|
||||||
EDhApiLevelType getLevelType();
|
EDhApiLevelType getLevelType();
|
||||||
|
|
||||||
boolean hasCeiling();
|
boolean hasCeiling();
|
||||||
|
|
||||||
boolean hasSkyLight();
|
boolean hasSkyLight();
|
||||||
|
|
||||||
/** Returns the max block height of the level(?) */
|
/**
|
||||||
|
* Deprecated, use {@link IDhApiLevelWrapper#getMaxHeight} instead. <br>
|
||||||
|
* Returns the max block height of the level.
|
||||||
|
*
|
||||||
|
* @see IDhApiLevelWrapper#getMaxHeight
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
default int getHeight() { return this.getMaxHeight(); }
|
||||||
|
/**
|
||||||
|
* Returns the max block height of the level
|
||||||
|
* @since API 3.0.0
|
||||||
|
*/
|
||||||
int getMaxHeight();
|
int getMaxHeight();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,6 +67,8 @@ public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
|
|||||||
/**
|
/**
|
||||||
* Will return null if called on the server,
|
* Will return null if called on the server,
|
||||||
* or if called before the renderer has been set up.
|
* or if called before the renderer has been set up.
|
||||||
|
*
|
||||||
|
* @since API 3.0.0
|
||||||
*/
|
*/
|
||||||
IDhApiCustomRenderRegister getRenderRegister();
|
IDhApiCustomRenderRegister getRenderRegister();
|
||||||
|
|
||||||
|
|||||||
@@ -21,15 +21,17 @@ package com.seibel.distanthorizons.api.objects.data;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.api.interfaces.factories.IDhApiWrapperFactory;
|
import com.seibel.distanthorizons.api.interfaces.factories.IDhApiWrapperFactory;
|
||||||
import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
|
import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains a list of {@link DhApiTerrainDataPoint} representing the blocks in a Minecraft chunk.
|
* Contains a list of {@link DhApiTerrainDataPoint} representing the blocks in a Minecraft chunk.
|
||||||
*
|
*
|
||||||
* @author Builderb0y, James Seibel
|
* @author Builderb0y, James Seibel
|
||||||
* @version 2023-12-21
|
* @version 2024-7-21
|
||||||
* @since API 2.0.0
|
* @since API 2.0.0
|
||||||
*
|
*
|
||||||
* @see IDhApiWrapperFactory
|
* @see IDhApiWrapperFactory
|
||||||
@@ -41,8 +43,8 @@ public class DhApiChunk
|
|||||||
public final int chunkPosX;
|
public final int chunkPosX;
|
||||||
public final int chunkPosZ;
|
public final int chunkPosZ;
|
||||||
|
|
||||||
public final int topYBlockPos;
|
|
||||||
public final int bottomYBlockPos;
|
public final int bottomYBlockPos;
|
||||||
|
public final int topYBlockPos;
|
||||||
|
|
||||||
private final List<List<DhApiTerrainDataPoint>> dataPoints;
|
private final List<List<DhApiTerrainDataPoint>> dataPoints;
|
||||||
|
|
||||||
@@ -52,12 +54,32 @@ public class DhApiChunk
|
|||||||
// constructors //
|
// constructors //
|
||||||
//==============//
|
//==============//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated due to the topYBlockPos and bottomYBlockPos variables being put in the wrong order.
|
||||||
|
* They should have been in bottom -> top order.
|
||||||
|
*
|
||||||
|
* @see DhApiChunk#create(int, int, int, int)
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public DhApiChunk(int chunkPosX, int chunkPosZ, int topYBlockPos, int bottomYBlockPos)
|
public DhApiChunk(int chunkPosX, int chunkPosZ, int topYBlockPos, int bottomYBlockPos)
|
||||||
|
{ this(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos, false); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since API 3.0.0
|
||||||
|
*/
|
||||||
|
public static DhApiChunk create(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos)
|
||||||
|
{ return new DhApiChunk(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos, false); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only visible to internal DH methods
|
||||||
|
* @param ignoredParameter is only present to differentiate the two constructors and isn't actually used
|
||||||
|
*/
|
||||||
|
private DhApiChunk(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos, boolean ignoredParameter)
|
||||||
{
|
{
|
||||||
this.chunkPosX = chunkPosX;
|
this.chunkPosX = chunkPosX;
|
||||||
this.chunkPosZ = chunkPosZ;
|
this.chunkPosZ = chunkPosZ;
|
||||||
this.topYBlockPos = topYBlockPos;
|
|
||||||
this.bottomYBlockPos = bottomYBlockPos;
|
this.bottomYBlockPos = bottomYBlockPos;
|
||||||
|
this.topYBlockPos = topYBlockPos;
|
||||||
|
|
||||||
// populate the array to prevent null pointers
|
// populate the array to prevent null pointers
|
||||||
this.dataPoints = new ArrayList<>(16 * 16); // 256
|
this.dataPoints = new ArrayList<>(16 * 16); // 256
|
||||||
@@ -94,27 +116,34 @@ public class DhApiChunk
|
|||||||
*/
|
*/
|
||||||
public void setDataPoints(int relX, int relZ, List<DhApiTerrainDataPoint> dataPoints) throws IndexOutOfBoundsException, IllegalArgumentException
|
public void setDataPoints(int relX, int relZ, List<DhApiTerrainDataPoint> dataPoints) throws IndexOutOfBoundsException, IllegalArgumentException
|
||||||
{
|
{
|
||||||
|
//==================//
|
||||||
|
// basic validation //
|
||||||
|
//==================//
|
||||||
|
|
||||||
|
// heavier validation is done in the world generator if requested
|
||||||
|
|
||||||
|
int internalArrayIndex = (relZ << 4) | relX;
|
||||||
throwIfRelativePosOutOfBounds(relX, relZ);
|
throwIfRelativePosOutOfBounds(relX, relZ);
|
||||||
|
|
||||||
// validate the incoming datapoints
|
if (dataPoints == null)
|
||||||
if (dataPoints != null)
|
|
||||||
{
|
{
|
||||||
for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation
|
// we don't allow null columns
|
||||||
{
|
throw new IllegalArgumentException("Null columns aren't allowed. If you want to remove all data from a column please clear the list or pass in an empty list.");
|
||||||
DhApiTerrainDataPoint dataPoint = dataPoints.get(i);
|
|
||||||
if (dataPoint == null)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataPoint.detailLevel != 0)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("DhApiTerrainDataPoints has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0].");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dataPoints.set((relZ << 4) | relX, dataPoints);
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// set datapoints //
|
||||||
|
//================//
|
||||||
|
|
||||||
|
List<DhApiTerrainDataPoint> column = this.dataPoints.get(internalArrayIndex);
|
||||||
|
if (column == null)
|
||||||
|
{
|
||||||
|
column = new ArrayList<>();
|
||||||
|
this.dataPoints.set(internalArrayIndex, column);
|
||||||
|
}
|
||||||
|
column.addAll(dataPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,14 +19,17 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.api.objects.data;
|
package com.seibel.distanthorizons.api.objects.data;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
|
||||||
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper;
|
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper;
|
||||||
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
|
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds a single datapoint of terrain data.
|
* Holds a single datapoint of terrain data.
|
||||||
*
|
*
|
||||||
* @author James Seibel
|
* @author James Seibel
|
||||||
* @version 2022-11-13
|
* @version 2024-7-20
|
||||||
* @since API 1.0.0
|
* @since API 1.0.0
|
||||||
*/
|
*/
|
||||||
public class DhApiTerrainDataPoint
|
public class DhApiTerrainDataPoint
|
||||||
@@ -37,27 +40,79 @@ public class DhApiTerrainDataPoint
|
|||||||
* 2 = 4x4 blocks <br>
|
* 2 = 4x4 blocks <br>
|
||||||
* 4 = chunk (16x16 blocks) <br>
|
* 4 = chunk (16x16 blocks) <br>
|
||||||
* 9 = region (512x512 blocks) <br>
|
* 9 = region (512x512 blocks) <br>
|
||||||
|
*
|
||||||
|
* @see EDhApiDetailLevel
|
||||||
*/
|
*/
|
||||||
public final byte detailLevel;
|
public final byte detailLevel;
|
||||||
|
|
||||||
public final int blockLightLevel;
|
public final int blockLightLevel;
|
||||||
public final int skyLightLevel;
|
public final int skyLightLevel;
|
||||||
public final int topYBlockPos;
|
|
||||||
public final int bottomYBlockPos;
|
public final int bottomYBlockPos;
|
||||||
|
public final int topYBlockPos;
|
||||||
|
|
||||||
public final IDhApiBlockStateWrapper blockStateWrapper;
|
public final IDhApiBlockStateWrapper blockStateWrapper;
|
||||||
public final IDhApiBiomeWrapper biomeWrapper;
|
public final IDhApiBiomeWrapper biomeWrapper;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public DhApiTerrainDataPoint(byte detailLevel, int blockLightLevel, int skyLightLevel, int topYBlockPos, int bottomYBlockPos, IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper)
|
//==============//
|
||||||
|
// constructors //
|
||||||
|
//==============//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated due to the topYBlockPos and bottomYBlockPos variables being put in the wrong order.
|
||||||
|
* They should have been in bottom -> top order.
|
||||||
|
*
|
||||||
|
* @see DhApiTerrainDataPoint#create(byte, int, int, int, int, IDhApiBlockStateWrapper, IDhApiBiomeWrapper)
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public DhApiTerrainDataPoint(
|
||||||
|
byte detailLevel,
|
||||||
|
int blockLightLevel, int skyLightLevel,
|
||||||
|
int topYBlockPos, int bottomYBlockPos,
|
||||||
|
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper)
|
||||||
|
{
|
||||||
|
this(detailLevel, blockLightLevel, skyLightLevel,
|
||||||
|
bottomYBlockPos, topYBlockPos,
|
||||||
|
blockStateWrapper, biomeWrapper,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since API 3.0.0
|
||||||
|
*/
|
||||||
|
public static DhApiTerrainDataPoint create(
|
||||||
|
byte detailLevel,
|
||||||
|
int blockLightLevel, int skyLightLevel,
|
||||||
|
int bottomYBlockPos, int topYBlockPos,
|
||||||
|
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return new DhApiTerrainDataPoint(
|
||||||
|
detailLevel, blockLightLevel, skyLightLevel,
|
||||||
|
bottomYBlockPos, topYBlockPos,
|
||||||
|
blockStateWrapper, biomeWrapper,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only visible to internal DH methods
|
||||||
|
* @param ignoredParameter is only present to differentiate the two constructors and isn't actually used
|
||||||
|
*/
|
||||||
|
private DhApiTerrainDataPoint(
|
||||||
|
byte detailLevel,
|
||||||
|
int blockLightLevel, int skyLightLevel,
|
||||||
|
int bottomYBlockPos, int topYBlockPos,
|
||||||
|
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper,
|
||||||
|
boolean ignoredParameter
|
||||||
|
)
|
||||||
{
|
{
|
||||||
this.detailLevel = detailLevel;
|
this.detailLevel = detailLevel;
|
||||||
|
|
||||||
this.blockLightLevel = blockLightLevel;
|
this.blockLightLevel = blockLightLevel;
|
||||||
this.skyLightLevel = skyLightLevel;
|
this.skyLightLevel = skyLightLevel;
|
||||||
this.topYBlockPos = topYBlockPos;
|
|
||||||
this.bottomYBlockPos = bottomYBlockPos;
|
this.bottomYBlockPos = bottomYBlockPos;
|
||||||
|
this.topYBlockPos = topYBlockPos;
|
||||||
|
|
||||||
this.blockStateWrapper = blockStateWrapper;
|
this.blockStateWrapper = blockStateWrapper;
|
||||||
this.biomeWrapper = biomeWrapper;
|
this.biomeWrapper = biomeWrapper;
|
||||||
|
|||||||
@@ -26,27 +26,28 @@ package com.seibel.distanthorizons.coreapi;
|
|||||||
public final class ModInfo
|
public final class ModInfo
|
||||||
{
|
{
|
||||||
public static final String ID = "distanthorizons";
|
public static final String ID = "distanthorizons";
|
||||||
/** The internal protocol version used for networking */
|
|
||||||
public static final int PROTOCOL_VERSION = 1;
|
public static final String RESOURCE_NAMESPACE = "distant_horizons";
|
||||||
/** The protocol version used for multiverse networking */
|
public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial";
|
||||||
public static final int MULTIVERSE_PLUGIN_PROTOCOL_VERSION = 1;
|
|
||||||
|
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */
|
||||||
|
public static final int PROTOCOL_VERSION = 4;
|
||||||
|
public static final String WRAPPER_PACKET_PATH = "message";
|
||||||
|
|
||||||
/** The internal mod name */
|
/** The internal mod name */
|
||||||
public static final String NAME = "DistantHorizons";
|
public static final String NAME = "DistantHorizons";
|
||||||
/** Human-readable version of NAME */
|
/** Human-readable version of NAME */
|
||||||
public static final String READABLE_NAME = "Distant Horizons";
|
public static final String READABLE_NAME = "Distant Horizons";
|
||||||
public static final String VERSION = "2.1.3-a-dev";
|
public static final String VERSION = "2.3.0-a-dev";
|
||||||
/** Returns true if the current build is an unstable developer build, false otherwise. */
|
/** Returns true if the current build is an unstable developer build, false otherwise. */
|
||||||
public static boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
|
public static boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
|
||||||
|
|
||||||
/** This version should only be updated when breaking changes are introduced to the DH API */
|
/** This version should only be updated when breaking changes are introduced to the DH API */
|
||||||
public static final int API_MAJOR_VERSION = 3;
|
public static final int API_MAJOR_VERSION = 4;
|
||||||
/** This version should be updated whenever new methods are added to the DH API */
|
/** This version should be updated whenever new methods are added to the DH API */
|
||||||
public static final int API_MINOR_VERSION = 0;
|
public static final int API_MINOR_VERSION = 0;
|
||||||
/** This version should be updated whenever non-breaking fixes are added to the DH API */
|
/** This version should be updated whenever non-breaking fixes are added to the DH API */
|
||||||
public static final int API_PATH_VERSION = 0;
|
public static final int API_PATCH_VERSION = 0;
|
||||||
|
|
||||||
public static final String NETWORKING_RESOURCE_NAMESPACE = "distant_horizons";
|
|
||||||
public static final String MULTIVERSE_PLUGIN_NAMESPACE = "world_control";
|
|
||||||
|
|
||||||
/** All DH owned threads should start with this string to allow for easier debugging and profiling. */
|
/** All DH owned threads should start with this string to allow for easier debugging and profiling. */
|
||||||
public static final String THREAD_NAME_PREFIX = "DH-";
|
public static final String THREAD_NAME_PREFIX = "DH-";
|
||||||
|
|||||||
@@ -67,10 +67,11 @@ public interface IConfigEntry<T>
|
|||||||
* Checks if the option is valid
|
* Checks if the option is valid
|
||||||
*
|
*
|
||||||
* 0 == valid
|
* 0 == valid
|
||||||
|
* 2 == invalid
|
||||||
* 1 == number too high
|
* 1 == number too high
|
||||||
* -1 == number too low
|
* -1 == number too low
|
||||||
*/
|
*/
|
||||||
byte isValid();
|
byte isValid(); // TODO replace with an enum
|
||||||
/** Checks if a value is valid */
|
/** Checks if a value is valid */
|
||||||
byte isValid(T value);
|
byte isValid(T value);
|
||||||
|
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Miscellaneous string helper functions.
|
* Miscellaneous string helper functions.
|
||||||
*
|
|
||||||
* @author James Seibel
|
|
||||||
* @version 2022-7-19
|
|
||||||
*/
|
*/
|
||||||
public class StringUtil
|
public class StringUtil
|
||||||
{
|
{
|
||||||
|
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the n-th index of the given string. <br> <br>
|
* Returns the n-th index of the given string. <br> <br>
|
||||||
*
|
*
|
||||||
@@ -67,8 +67,6 @@ public class StringUtil
|
|||||||
return stringBuilder.toString();
|
return stringBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
|
||||||
/**
|
/**
|
||||||
* Converts the given byte array into a hex string representation. <br>
|
* Converts the given byte array into a hex string representation. <br>
|
||||||
* source: https://stackoverflow.com/a/9855338
|
* source: https://stackoverflow.com/a/9855338
|
||||||
@@ -85,4 +83,20 @@ public class StringUtil
|
|||||||
return new String(hexChars);
|
return new String(hexChars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a shortened version of the given string that is no longer than maxLength. <br>
|
||||||
|
* If null returns the empty string.
|
||||||
|
*/
|
||||||
|
public static String shortenString(String str, int maxLength)
|
||||||
|
{
|
||||||
|
if (str == null)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return str.substring(0, Math.min(str.length(), maxLength));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
|||||||
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
|
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
|
||||||
import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
|
import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
|
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.config.DhApiConfig;
|
||||||
@@ -32,14 +33,19 @@ import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
|
|||||||
import net.jpountz.lz4.LZ4FrameOutputStream;
|
import net.jpountz.lz4.LZ4FrameOutputStream;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.sqlite.SQLiteJDBCLoader;
|
||||||
|
import org.sqlite.util.OSInfo;
|
||||||
import org.tukaani.xz.XZOutputStream;
|
import org.tukaani.xz.XZOutputStream;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
/** Handles first time Core setup. */
|
/** Handles first time Core setup. */
|
||||||
public class Initializer
|
public class Initializer
|
||||||
{
|
{
|
||||||
private static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME + "-" + Initializer.class.getSimpleName());
|
private static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME + "-" + Initializer.class.getSimpleName());
|
||||||
|
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
|
|
||||||
|
|
||||||
public static void init()
|
public static void init()
|
||||||
{
|
{
|
||||||
@@ -54,12 +60,44 @@ public class Initializer
|
|||||||
Class<?> config = 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<?> 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
|
//Class<?> newFastUtil = it.unimi.dsi.fastutil.ints.IntUnaryOperator.class; // available in 8.5.13
|
||||||
|
Class<?> sqliteJava = org.sqlite.SQLiteConnection.class;
|
||||||
|
Class<?> sqliteNative = org.sqlite.core.NativeDB.class;
|
||||||
|
|
||||||
|
//// maybe these lines are needed to shade SQLite, James isn't sure.
|
||||||
|
//// Although they never seemed to fail, which is a bit odd.
|
||||||
|
//try
|
||||||
|
//{
|
||||||
|
// // needed by Forge to load the Java database connection
|
||||||
|
// Class.forName("org.sqlite.JDBC");
|
||||||
|
// LOGGER.info("loaded normal SQLITE");
|
||||||
|
//}
|
||||||
|
//catch (ClassNotFoundException e)
|
||||||
|
//{
|
||||||
|
// LOGGER.warn("normal: " + e.getMessage(), e);
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//try
|
||||||
|
//{
|
||||||
|
// // needed by Forge to load the Java database connection
|
||||||
|
// Class.forName("DistantHorizons.libraries.sqlite.JDBC");
|
||||||
|
// LOGGER.info("loaded shaded SQLITE");
|
||||||
|
//}
|
||||||
|
//catch (ClassNotFoundException e)
|
||||||
|
//{
|
||||||
|
// LOGGER.warn("shaded: " + e.getMessage(), e);
|
||||||
|
//}
|
||||||
|
|
||||||
|
boolean sqliteLoaded = SQLiteJDBCLoader.initialize();
|
||||||
|
if (!sqliteLoaded)
|
||||||
|
{
|
||||||
|
throw new RuntimeException("Failed to load SQLite native library. Hopefully SQLite logged a reason for this failure.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
{
|
{
|
||||||
LOGGER.fatal("Critical programmer error: One or more libraries aren't present. Error: [" + e.getMessage() + "].");
|
LOGGER.fatal("Critical programmer error: One or more libraries aren't present. Error: [" + e.getMessage() + "].", e);
|
||||||
// throwing here should crash the game, notifying the developer that something is wrong
|
// throwing here should crash the game, notifying the developer that something is wrong
|
||||||
throw e;
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// confirm the resource directory is present
|
// confirm the resource directory is present
|
||||||
@@ -77,16 +115,18 @@ public class Initializer
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (MC_CLIENT != null)
|
||||||
// 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.");
|
// attempt to set up Swing so we can display dialogs (popup windows)
|
||||||
}
|
System.setProperty("java.awt.headless", "false");
|
||||||
else
|
if (GraphicsEnvironment.isHeadless())
|
||||||
{
|
{
|
||||||
LOGGER.info("Java.awt.headless set to true. Distant Horizons can correctly display error and info dialog windows.");
|
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
|
// link Core's config to the API
|
||||||
|
|||||||
@@ -42,8 +42,6 @@ public class DhApiConfig implements IDhApiConfig
|
|||||||
@Override
|
@Override
|
||||||
public IDhApiMultiThreadingConfig multiThreading() { return DhApiMultiThreadingConfig.INSTANCE; }
|
public IDhApiMultiThreadingConfig multiThreading() { return DhApiMultiThreadingConfig.INSTANCE; }
|
||||||
@Override
|
@Override
|
||||||
public IDhApiGpuBuffersConfig gpuBuffers() { return DhApiGpuBuffersConfig.INSTANCE; }
|
|
||||||
@Override
|
|
||||||
public IDhApiDebuggingConfig debugging() { return DhApiDebuggingConfig.INSTANCE; }
|
public IDhApiDebuggingConfig debugging() { return DhApiDebuggingConfig.INSTANCE; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of the Distant Horizons mod
|
|
||||||
* licensed under the GNU LGPL v3 License.
|
|
||||||
*
|
|
||||||
* Copyright (C) 2020-2023 James Seibel
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.seibel.distanthorizons.core.api.external.methods.config.client;
|
|
||||||
|
|
||||||
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.EDhApiGpuUploadMethod;
|
|
||||||
|
|
||||||
public class DhApiGpuBuffersConfig implements IDhApiGpuBuffersConfig
|
|
||||||
{
|
|
||||||
public static DhApiGpuBuffersConfig INSTANCE = new DhApiGpuBuffersConfig();
|
|
||||||
|
|
||||||
private DhApiGpuBuffersConfig() { }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public IDhApiConfigValue<EDhApiGpuUploadMethod> gpuUploadMethod()
|
|
||||||
{ return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadMethod); }
|
|
||||||
|
|
||||||
public IDhApiConfigValue<Integer> gpuUploadPerMegabyteInMilliseconds()
|
|
||||||
{ return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds); }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -326,9 +326,10 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
|
|||||||
int height = FullDataPointUtil.getHeight(dataPoint);
|
int height = FullDataPointUtil.getHeight(dataPoint);
|
||||||
int topY = bottomY + height;
|
int topY = bottomY + height;
|
||||||
|
|
||||||
return new DhApiTerrainDataPoint(detailLevel,
|
return DhApiTerrainDataPoint.create(
|
||||||
|
detailLevel,
|
||||||
FullDataPointUtil.getBlockLight(dataPoint), FullDataPointUtil.getSkyLight(dataPoint),
|
FullDataPointUtil.getBlockLight(dataPoint), FullDataPointUtil.getSkyLight(dataPoint),
|
||||||
topY, bottomY,
|
bottomY, topY,
|
||||||
blockState, biomeWrapper);
|
blockState, biomeWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,18 +23,21 @@ import com.seibel.distanthorizons.api.DhApi;
|
|||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
||||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
|
||||||
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||||
import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager;
|
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
|
||||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||||
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
|
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
|
||||||
|
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||||
import com.seibel.distanthorizons.core.util.objects.Pair;
|
import com.seibel.distanthorizons.core.util.objects.Pair;
|
||||||
import com.seibel.distanthorizons.core.world.*;
|
|
||||||
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
|
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
||||||
|
import com.seibel.distanthorizons.core.network.session.NetworkSession;
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
|
||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
|
||||||
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
|
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
|
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.SpamReducedLogger;
|
import com.seibel.distanthorizons.core.logging.SpamReducedLogger;
|
||||||
@@ -42,20 +45,21 @@ import com.seibel.distanthorizons.core.util.math.Mat4f;
|
|||||||
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.TestRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.TestRenderer;
|
||||||
import com.seibel.distanthorizons.core.util.RenderUtil;
|
import com.seibel.distanthorizons.core.util.RenderUtil;
|
||||||
|
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
|
||||||
|
import com.seibel.distanthorizons.core.world.DhClientServerWorld;
|
||||||
|
import com.seibel.distanthorizons.core.world.DhClientWorld;
|
||||||
|
import com.seibel.distanthorizons.core.world.IDhClientWorld;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
//import io.netty.buffer.ByteBuf;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.io.File;
|
||||||
import java.util.HashSet;
|
import java.util.*;
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -73,9 +77,7 @@ public class ClientApi
|
|||||||
public static final ClientApi INSTANCE = new ClientApi();
|
public static final ClientApi INSTANCE = new ClientApi();
|
||||||
public static final TestRenderer TEST_RENDERER = new TestRenderer();
|
public static final TestRenderer TEST_RENDERER = new TestRenderer();
|
||||||
|
|
||||||
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
|
||||||
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
|
|
||||||
|
|
||||||
public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
|
public static final long SPAM_LOGGER_FLUSH_NS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
|
||||||
|
|
||||||
@@ -88,10 +90,11 @@ public class ClientApi
|
|||||||
|
|
||||||
private long lastFlushNanoTime = 0;
|
private long lastFlushNanoTime = 0;
|
||||||
|
|
||||||
private boolean isServerCommunicationEnabled = false;
|
private final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi(this::clientLevelLoadEvent, this::clientLevelUnloadEvent);
|
||||||
|
|
||||||
/** set to true if any unexpected responses are received from the server */
|
/** Delay loading the first level to give the server some time to respond with level to actually load */
|
||||||
private boolean serverNetworkingIsMalformed = false;
|
private Timer firstLevelLoadTimer;
|
||||||
|
private static final long FIRST_LEVEL_LOAD_DELAY_IN_MS = 1_000;
|
||||||
|
|
||||||
/** Holds any levels that were loaded before the {@link ClientApi#onClientOnlyConnected} was fired. */
|
/** Holds any levels that were loaded before the {@link ClientApi#onClientOnlyConnected} was fired. */
|
||||||
public final HashSet<IClientLevelWrapper> waitingClientLevels = new HashSet<>();
|
public final HashSet<IClientLevelWrapper> waitingClientLevels = new HashSet<>();
|
||||||
@@ -125,14 +128,36 @@ public class ClientApi
|
|||||||
public synchronized void onClientOnlyConnected()
|
public synchronized void onClientOnlyConnected()
|
||||||
{
|
{
|
||||||
// only continue if the client is connected to a different server
|
// only continue if the client is connected to a different server
|
||||||
if (MC.clientConnectedToDedicatedServer())
|
boolean connectedToServer = MC_CLIENT.clientConnectedToDedicatedServer();
|
||||||
|
boolean connectedToReplay = MC_CLIENT.connectedToReplay();
|
||||||
|
if (connectedToServer || connectedToReplay)
|
||||||
{
|
{
|
||||||
LOGGER.info("Client on ClientOnly mode connecting.");
|
if (connectedToServer)
|
||||||
|
{
|
||||||
|
LOGGER.info("Client on ClientOnly mode connecting.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOGGER.info("Replay on ClientServer mode connecting.");
|
||||||
|
|
||||||
|
if (Config.Client.Advanced.Logging.showReplayWarningOnStartup.get())
|
||||||
|
{
|
||||||
|
MC_CLIENT.sendChatMessage("\u00A76" + "Distant Horizons: Replay detected." + "\u00A7r"); // gold color
|
||||||
|
MC_CLIENT.sendChatMessage("DH may behave strangely or have missing functionality.");
|
||||||
|
MC_CLIENT.sendChatMessage("In order to use pre-generated LODs, put your DH database(s) in:");
|
||||||
|
MC_CLIENT.sendChatMessage("\u00A77"+".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+"\u00A7r"); // light gray color
|
||||||
|
MC_CLIENT.sendChatMessage("This can be disabled in DH's config under Advanced -> Logging.");
|
||||||
|
MC_CLIENT.sendChatMessage("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// firing after clientLevelLoadEvent
|
// firing after clientLevelLoadEvent
|
||||||
// TODO if level has prepped to load it should fire level load event
|
// TODO if level has prepped to load it should fire level load event
|
||||||
SharedApi.setDhWorld(new DhClientWorld());
|
DhClientWorld world = new DhClientWorld();
|
||||||
|
SharedApi.setDhWorld(world);
|
||||||
|
|
||||||
|
this.pluginChannelApi.onJoinServer(world.networkState.getSession());
|
||||||
|
world.networkState.sendConfigMessage();
|
||||||
|
|
||||||
LOGGER.info("Loading [" + this.waitingClientLevels.size() + "] waiting client level wrappers.");
|
LOGGER.info("Loading [" + this.waitingClientLevels.size() + "] waiting client level wrappers.");
|
||||||
for (IClientLevelWrapper level : this.waitingClientLevels)
|
for (IClientLevelWrapper level : this.waitingClientLevels)
|
||||||
@@ -147,6 +172,13 @@ public class ClientApi
|
|||||||
/** Synchronized to prevent a rare issue where multiple disconnect events are triggered on top of each other. */
|
/** Synchronized to prevent a rare issue where multiple disconnect events are triggered on top of each other. */
|
||||||
public synchronized void onClientOnlyDisconnected()
|
public synchronized void onClientOnlyDisconnected()
|
||||||
{
|
{
|
||||||
|
// clear the first time timer
|
||||||
|
if (this.firstLevelLoadTimer != null)
|
||||||
|
{
|
||||||
|
this.firstLevelLoadTimer.cancel();
|
||||||
|
this.firstLevelLoadTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
||||||
if (world != null)
|
if (world != null)
|
||||||
{
|
{
|
||||||
@@ -156,11 +188,7 @@ public class ClientApi
|
|||||||
SharedApi.setDhWorld(null);
|
SharedApi.setDhWorld(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear the previous server's information
|
this.pluginChannelApi.reset();
|
||||||
this.isServerCommunicationEnabled = false;
|
|
||||||
this.serverNetworkingIsMalformed = false;
|
|
||||||
KEYED_CLIENT_LEVEL_MANAGER.setUseOverrideWrapper(false);
|
|
||||||
KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(null);
|
|
||||||
|
|
||||||
// remove any waiting items
|
// remove any waiting items
|
||||||
this.waitingChunkByClientLevelAndPos.clear();
|
this.waitingChunkByClientLevelAndPos.clear();
|
||||||
@@ -173,16 +201,16 @@ public class ClientApi
|
|||||||
// level events //
|
// level events //
|
||||||
//==============//
|
//==============//
|
||||||
|
|
||||||
public void clientLevelUnloadEvent(@Nullable IClientLevelWrapper level)
|
public void clientLevelUnloadEvent(IClientLevelWrapper level)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (level == null)
|
LOGGER.info("Unloading client level [" + level + "]-["+level.getDimensionName()+"].");
|
||||||
|
|
||||||
|
if (level instanceof IServerKeyedClientLevel)
|
||||||
{
|
{
|
||||||
// can happen on certain multiverse servers
|
this.pluginChannelApi.onClientLevelUnload();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
LOGGER.info("Unloading client level [" + level + "]-["+level.getDimensionType().getDimensionName()+"].");
|
|
||||||
|
|
||||||
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
||||||
if (world != null)
|
if (world != null)
|
||||||
@@ -202,30 +230,42 @@ public class ClientApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clientLevelLoadEvent(@Nullable IClientLevelWrapper level) { this.clientLevelLoadEvent(level, false); }
|
public void clientLevelLoadEvent(IClientLevelWrapper level)
|
||||||
public void multiverseClientLevelLoadEvent(@Nullable IClientLevelWrapper level) { this.clientLevelLoadEvent(level, true); }
|
|
||||||
private void clientLevelLoadEvent(@Nullable IClientLevelWrapper level, boolean isServerCommunication)
|
|
||||||
{
|
{
|
||||||
|
// wait a moment before loading the level to give the server a chance to handle the client's login request
|
||||||
|
if (MC_CLIENT.clientConnectedToDedicatedServer())
|
||||||
|
{
|
||||||
|
if (this.firstLevelLoadTimer == null)
|
||||||
|
{
|
||||||
|
this.firstLevelLoadTimer = TimerUtil.CreateTimer("FirstLevelLoadTimer");
|
||||||
|
this.firstLevelLoadTimer.schedule(new TimerTask()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run() { ClientApi.this.clientLevelLoadEvent(level); }
|
||||||
|
}, FIRST_LEVEL_LOAD_DELAY_IN_MS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.firstLevelLoadTimer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (this.isServerCommunicationEnabled && !isServerCommunication)
|
LOGGER.info("Loading client level [" + level + "]-["+level.getDimensionName()+"].");
|
||||||
{
|
|
||||||
LOGGER.info("Server supports communication, deferring loading.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (level == null)
|
|
||||||
{
|
|
||||||
// can happen on certain multiverse servers
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
LOGGER.info("Loading " + (isServerCommunication ? "Multiverse" : "") + " client level [" + level + "]-["+level.getDimensionType().getDimensionName()+"].");
|
|
||||||
|
|
||||||
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
||||||
if (world != null)
|
if (world != null)
|
||||||
{
|
{
|
||||||
|
if (!this.pluginChannelApi.allowLevelLoading(level))
|
||||||
|
{
|
||||||
|
LOGGER.info("Levels in this connection are managed by the server, skipping auto-load.");
|
||||||
|
|
||||||
|
// Instead of attempting to load themselves, send the config and wait for a server provided level key.
|
||||||
|
((DhClientWorld) world).networkState.sendConfigMessage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
world.getOrLoadLevel(level);
|
world.getOrLoadLevel(level);
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level));
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level));
|
||||||
|
|
||||||
@@ -274,7 +314,7 @@ public class ClientApi
|
|||||||
{
|
{
|
||||||
LOGGER.info("Renderer shutting down.");
|
LOGGER.info("Renderer shutting down.");
|
||||||
|
|
||||||
IProfilerWrapper profiler = MC.getProfiler();
|
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
|
||||||
profiler.push("DH-RendererShutdown");
|
profiler.push("DH-RendererShutdown");
|
||||||
|
|
||||||
profiler.pop();
|
profiler.pop();
|
||||||
@@ -284,7 +324,7 @@ public class ClientApi
|
|||||||
{
|
{
|
||||||
LOGGER.info("Renderer starting up.");
|
LOGGER.info("Renderer starting up.");
|
||||||
|
|
||||||
IProfilerWrapper profiler = MC.getProfiler();
|
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
|
||||||
profiler.push("DH-RendererStartup");
|
profiler.push("DH-RendererStartup");
|
||||||
|
|
||||||
// make sure the GLProxy is created before the LodBufferBuilder needs it
|
// make sure the GLProxy is created before the LodBufferBuilder needs it
|
||||||
@@ -294,7 +334,7 @@ public class ClientApi
|
|||||||
|
|
||||||
public void clientTickEvent()
|
public void clientTickEvent()
|
||||||
{
|
{
|
||||||
IProfilerWrapper profiler = MC.getProfiler();
|
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
|
||||||
profiler.push("DH-ClientTick");
|
profiler.push("DH-ClientTick");
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -316,7 +356,7 @@ public class ClientApi
|
|||||||
// Ignore local world gen, as it's managed by server ticking
|
// Ignore local world gen, as it's managed by server ticking
|
||||||
if (!(clientWorld instanceof DhClientServerWorld))
|
if (!(clientWorld instanceof DhClientServerWorld))
|
||||||
{
|
{
|
||||||
SharedApi.worldGenTick(clientWorld::doWorldGen);
|
SharedApi.worldGenTick(clientWorld::worldGenTick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,124 +374,15 @@ public class ClientApi
|
|||||||
//============//
|
//============//
|
||||||
// networking //
|
// networking //
|
||||||
//============//
|
//============//
|
||||||
|
|
||||||
// /** @param byteBuf is Netty's {@link ByteBuffer} wrapper. */
|
public void pluginMessageReceived(@NotNull AbstractNetworkMessage message)
|
||||||
// public void serverMessageReceived(ByteBuf byteBuf)
|
{
|
||||||
// {
|
NetworkSession networkSession = this.pluginChannelApi.networkSession;
|
||||||
// if (!Config.Client.Advanced.Multiplayer.enableMultiverseNetworking.get())
|
if (networkSession != null)
|
||||||
// {
|
{
|
||||||
// // multiverse networking disabled, ignore anything sent from the server
|
networkSession.tryHandleMessage(message);
|
||||||
// return;
|
}
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// // either value can be set to true to debug the received byte stream
|
|
||||||
// boolean stopAndDisplayInputAsByteArray = false;
|
|
||||||
// boolean stopAndDisplayInputAsString = false;
|
|
||||||
// if (stopAndDisplayInputAsByteArray || stopAndDisplayInputAsString)
|
|
||||||
// {
|
|
||||||
// String messageString = "";
|
|
||||||
// if (stopAndDisplayInputAsByteArray)
|
|
||||||
// {
|
|
||||||
// int byteCount = byteBuf.readableBytes();
|
|
||||||
// byte[] arr = new byte[byteCount];
|
|
||||||
// StringBuilder stringBuilder = new StringBuilder("Server message received: [");
|
|
||||||
// for (int i = 0; i < byteCount; i++)
|
|
||||||
// {
|
|
||||||
// arr[i] = byteBuf.readByte();
|
|
||||||
// stringBuilder.append(arr[i]);
|
|
||||||
// }
|
|
||||||
// stringBuilder.append("]");
|
|
||||||
//
|
|
||||||
// messageString = stringBuilder.toString();
|
|
||||||
// }
|
|
||||||
// else if (stopAndDisplayInputAsString)
|
|
||||||
// {
|
|
||||||
// messageString = byteBuf.toString(StandardCharsets.UTF_8);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // this is logged as an error so it is easier to see in an Intellij log
|
|
||||||
// LOGGER.error(messageString);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// // It is important to ensure malicious server input is ignored.
|
|
||||||
// if (this.serverNetworkingIsMalformed)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // check that the incoming message is within the expected size
|
|
||||||
// short commandLength = byteBuf.readShort();
|
|
||||||
// if (commandLength < 1 || commandLength > 32)
|
|
||||||
// {
|
|
||||||
// LOGGER.error("Server command length ["+commandLength+"] outside the expected range of 1 to 32 (inclusive).");
|
|
||||||
// ClientApi.INSTANCE.serverNetworkingIsMalformed = true;
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // parse the command
|
|
||||||
// String eventType;
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// eventType = byteBuf.readCharSequence(commandLength, StandardCharsets.UTF_8).toString();
|
|
||||||
// }
|
|
||||||
// catch (Exception e)
|
|
||||||
// {
|
|
||||||
// LOGGER.error("Server sent un-parsable command. Error: "+e.getMessage());
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// switch (eventType)
|
|
||||||
// {
|
|
||||||
// case "ServerCommsEnabled":
|
|
||||||
// LOGGER.info("Server supports DH multiverse protocol.");
|
|
||||||
// ClientApi.INSTANCE.isServerCommunicationEnabled = true;
|
|
||||||
// KEYED_CLIENT_LEVEL_MANAGER.setUseOverrideWrapper(true);
|
|
||||||
// MC.executeOnRenderThread(() ->
|
|
||||||
// {
|
|
||||||
// // Unload the current world, since it may be wrong.
|
|
||||||
// // A followup WorldChanged event should be received from the server soon after this.
|
|
||||||
// LOGGER.info("Unloading current client level so the server can define the correct multiverse level.");
|
|
||||||
// this.clientLevelUnloadEvent((IClientLevelWrapper) MC.getWrappedClientWorld());
|
|
||||||
// });
|
|
||||||
// break;
|
|
||||||
//
|
|
||||||
// case "LevelChanged":
|
|
||||||
// short levelKeyLength = byteBuf.readShort();
|
|
||||||
// if (levelKeyLength < 1 || levelKeyLength > 128) // TODO 128 should be put into a constant somewhere
|
|
||||||
// {
|
|
||||||
// LOGGER.error("Server [LevelChanged] command length ["+commandLength+"] outside the expected range of 1 to 128 (inclusive).");
|
|
||||||
// this.serverNetworkingIsMalformed = true;
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// String levelKey = byteBuf.readCharSequence(levelKeyLength, StandardCharsets.UTF_8).toString();
|
|
||||||
// if (!levelKey.matches("[a-zA-Z0-9_]+"))
|
|
||||||
// {
|
|
||||||
// LOGGER.error("Server sent invalid world key name, and is being ignored.");
|
|
||||||
// this.isServerCommunicationEnabled = false;
|
|
||||||
// this.serverNetworkingIsMalformed = true;
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// LOGGER.info("Server level change event received, changing the level to ["+levelKey+"].");
|
|
||||||
// MC.executeOnRenderThread(() -> {
|
|
||||||
// if (MC.getWrappedClientWorld() != null)
|
|
||||||
// {
|
|
||||||
// this.clientLevelUnloadEvent((IClientLevelWrapper) MC.getWrappedClientWorld());
|
|
||||||
// }
|
|
||||||
// IServerKeyedClientLevel clientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(MC.getWrappedClientWorld(), levelKey);
|
|
||||||
// KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel);
|
|
||||||
// this.multiverseClientLevelLoadEvent(clientLevel);
|
|
||||||
// });
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -476,9 +407,9 @@ public class ClientApi
|
|||||||
{
|
{
|
||||||
// logging //
|
// logging //
|
||||||
|
|
||||||
this.sendChatMessagesNow();
|
this.sendQueuedChatMessages();
|
||||||
|
|
||||||
IProfilerWrapper profiler = MC.getProfiler();
|
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
|
||||||
profiler.pop(); // get out of "terrain"
|
profiler.pop(); // get out of "terrain"
|
||||||
profiler.push("DH-RenderLevel");
|
profiler.push("DH-RenderLevel");
|
||||||
|
|
||||||
@@ -529,10 +460,24 @@ public class ClientApi
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
IDhClientLevel level = dhClientWorld.getOrLoadClientLevel(levelWrapper);
|
|
||||||
|
IDhClientLevel level = (IDhClientLevel) dhClientWorld.getLevel(levelWrapper);
|
||||||
|
if (level == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (this.rendererDisabledBecauseOfExceptions)
|
if (this.rendererDisabledBecauseOfExceptions)
|
||||||
{
|
{
|
||||||
|
// re-enable rendering if the user toggles DH rendering
|
||||||
|
if (!Config.Client.quickEnableRendering.get())
|
||||||
|
{
|
||||||
|
LOGGER.info("DH Renderer re-enabled after exception. Some rendering issues may occur. Please reboot Minecraft if you see any rendering issues.");
|
||||||
|
this.rendererDisabledBecauseOfExceptions = false;
|
||||||
|
Config.Client.quickEnableRendering.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,10 +527,10 @@ public class ClientApi
|
|||||||
this.rendererDisabledBecauseOfExceptions = true;
|
this.rendererDisabledBecauseOfExceptions = true;
|
||||||
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
|
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
|
||||||
|
|
||||||
MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons renderer has encountered an exception!");
|
MC_CLIENT.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons renderer has encountered an exception!");
|
||||||
MC.sendChatMessage("\u00A74Renderer is now disabled to prevent further issues.");
|
MC_CLIENT.sendChatMessage("\u00A74Renderer disabled to try preventing GL state corruption.");
|
||||||
MC.sendChatMessage("\u00A74Please restart your game to re-enable Distant Horizons' LOD rendering.");
|
MC_CLIENT.sendChatMessage("\u00A74Toggle DH rendering via the config UI to re-activate DH rendering.");
|
||||||
MC.sendChatMessage("\u00A74Exception detail: " + e);
|
MC_CLIENT.sendChatMessage("\u00A74Error: " + e);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -625,32 +570,34 @@ public class ClientApi
|
|||||||
if (glfwKey == GLFW.GLFW_KEY_F8)
|
if (glfwKey == GLFW.GLFW_KEY_F8)
|
||||||
{
|
{
|
||||||
Config.Client.Advanced.Debugging.debugRendering.set(EDhApiDebugRendering.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());
|
MC_CLIENT.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRendering.get());
|
||||||
}
|
}
|
||||||
else if (glfwKey == GLFW.GLFW_KEY_F6)
|
else if (glfwKey == GLFW.GLFW_KEY_F6)
|
||||||
{
|
{
|
||||||
Config.Client.Advanced.Debugging.rendererMode.set(EDhApiRendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get()));
|
Config.Client.Advanced.Debugging.rendererMode.set(EDhApiRendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get()));
|
||||||
MC.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererMode.get());
|
MC_CLIENT.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererMode.get());
|
||||||
}
|
}
|
||||||
else if (glfwKey == GLFW.GLFW_KEY_P)
|
else if (glfwKey == GLFW.GLFW_KEY_P)
|
||||||
{
|
{
|
||||||
prefLoggerEnabled = !prefLoggerEnabled;
|
prefLoggerEnabled = !prefLoggerEnabled;
|
||||||
MC.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
|
MC_CLIENT.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendChatMessagesNow()
|
private void sendQueuedChatMessages()
|
||||||
{
|
{
|
||||||
// dev build
|
// dev build
|
||||||
if (ModInfo.IS_DEV_BUILD && !this.configOverrideReminderPrinted && MC.playerExists())
|
if (ModInfo.IS_DEV_BUILD && !this.configOverrideReminderPrinted && MC_CLIENT.playerExists())
|
||||||
{
|
{
|
||||||
this.configOverrideReminderPrinted = true;
|
this.configOverrideReminderPrinted = true;
|
||||||
|
|
||||||
// remind the user that this is a development build
|
// remind the user that this is a development build
|
||||||
MC.sendChatMessage("\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r");
|
String message =
|
||||||
MC.sendChatMessage("Issues may occur with this version.");
|
// green text
|
||||||
MC.sendChatMessage("Here be dragons!");
|
"\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r\n" +
|
||||||
MC.sendChatMessage("");
|
"Issues may occur with this version.\n" +
|
||||||
|
"Here be dragons!\n";
|
||||||
|
MC_CLIENT.sendChatMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// memory
|
// memory
|
||||||
@@ -665,11 +612,13 @@ public class ClientApi
|
|||||||
long maxMemoryInBytes = Runtime.getRuntime().maxMemory();
|
long maxMemoryInBytes = Runtime.getRuntime().maxMemory();
|
||||||
if (maxMemoryInBytes < minimumRecommendedMemoryInBytes)
|
if (maxMemoryInBytes < minimumRecommendedMemoryInBytes)
|
||||||
{
|
{
|
||||||
MC.sendChatMessage("\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r");
|
String message =
|
||||||
MC.sendChatMessage("Stuttering or low FPS may occur.");
|
// orange text
|
||||||
MC.sendChatMessage("Please increase Minecraft's available memory to 4 gigabytes.");
|
"\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r \n" +
|
||||||
MC.sendChatMessage("This warning can be disabled in DH's config under Advanced -> Logging.");
|
"Stuttering or low FPS may occur. \n" +
|
||||||
MC.sendChatMessage("");
|
"Please increase Minecraft's available memory to 4 gigabytes. \n" +
|
||||||
|
"This warning can be disabled in DH's config under Advanced -> Logging. \n";
|
||||||
|
MC_CLIENT.sendChatMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,7 +631,7 @@ public class ClientApi
|
|||||||
// done to prevent potential null pointers
|
// done to prevent potential null pointers
|
||||||
message = "";
|
message = "";
|
||||||
}
|
}
|
||||||
MC.sendChatMessage(message);
|
MC_CLIENT.sendChatMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package com.seibel.distanthorizons.core.api.internal;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.level.IKeyedClientLevelManager;
|
||||||
|
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
|
||||||
|
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
|
||||||
|
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
|
||||||
|
import com.seibel.distanthorizons.core.network.messages.base.CurrentLevelKeyMessage;
|
||||||
|
import com.seibel.distanthorizons.core.network.session.NetworkSession;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to manage the level keys.
|
||||||
|
*/
|
||||||
|
public class ClientPluginChannelApi
|
||||||
|
{
|
||||||
|
private static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
|
||||||
|
() -> Config.Client.Advanced.Logging.logNetworkEvent.get());
|
||||||
|
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
|
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
|
||||||
|
|
||||||
|
private final Consumer<IServerKeyedClientLevel> levelLoadHandler;
|
||||||
|
private final Consumer<IClientLevelWrapper> levelUnloadHandler;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public NetworkSession networkSession;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
|
||||||
|
public ClientPluginChannelApi(Consumer<IServerKeyedClientLevel> levelLoadHandler, Consumer<IClientLevelWrapper> levelUnloadHandler)
|
||||||
|
{
|
||||||
|
this.levelLoadHandler = levelLoadHandler;
|
||||||
|
this.levelUnloadHandler = levelUnloadHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//============//
|
||||||
|
// properties //
|
||||||
|
//============//
|
||||||
|
|
||||||
|
/** @return true if the level loading is handled by the server */
|
||||||
|
public boolean allowLevelLoading(IClientLevelWrapper level)
|
||||||
|
{
|
||||||
|
return (KEYED_CLIENT_LEVEL_MANAGER.hasLevelSet() && level instanceof IServerKeyedClientLevel)
|
||||||
|
|| !KEYED_CLIENT_LEVEL_MANAGER.hasLevelSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// network events //
|
||||||
|
//================//
|
||||||
|
|
||||||
|
/** fired when this client connects to a server with DH support */
|
||||||
|
public void onJoinServer(@NonNull NetworkSession networkSession)
|
||||||
|
{
|
||||||
|
Objects.requireNonNull(networkSession);
|
||||||
|
this.networkSession = networkSession;
|
||||||
|
this.networkSession.registerHandler(CurrentLevelKeyMessage.class, this::onCurrentLevelKeyMessage);
|
||||||
|
this.networkSession.registerHandler(CloseInternalEvent.class, this::onClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCurrentLevelKeyMessage(CurrentLevelKeyMessage msg)
|
||||||
|
{
|
||||||
|
// prefix@namespace:path
|
||||||
|
// 1-50 characters in total, all parts except namespace can be omitted
|
||||||
|
if (!msg.levelKey.matches("^(?=.{1,50}$)([a-zA-Z0-9-_]+@)?[a-zA-Z0-9-_]+(:[a-zA-Z0-9-_]+)?$"))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Server sent invalid level key.");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Server level key received: [" + msg.levelKey + "].");
|
||||||
|
|
||||||
|
MC.executeOnRenderThread(() ->
|
||||||
|
{
|
||||||
|
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
|
||||||
|
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
|
||||||
|
|
||||||
|
if (existingKeyedClientLevel != null)
|
||||||
|
{
|
||||||
|
if (!existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
|
||||||
|
{
|
||||||
|
LOGGER.info("Unloading previous level with key: [" + existingKeyedClientLevel.getServerLevelKey() + "].");
|
||||||
|
this.levelUnloadHandler.accept(existingKeyedClientLevel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOGGER.info("Level key matches the previous level key, ignoring the message.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOGGER.info("Unloading non-keyed level: [" + clientLevel.getDimensionName() + "].");
|
||||||
|
this.levelUnloadHandler.accept(clientLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingKeyedClientLevel == null || !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
|
||||||
|
{
|
||||||
|
LOGGER.info("Loading level with key: [" + msg.levelKey + "].");
|
||||||
|
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.levelKey);
|
||||||
|
this.levelLoadHandler.accept(keyedLevel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClientLevelUnload() { KEYED_CLIENT_LEVEL_MANAGER.clearKeyedLevel(); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==========//
|
||||||
|
// shutdown //
|
||||||
|
//==========//
|
||||||
|
|
||||||
|
private void onClose(CloseInternalEvent event) { this.reset(); }
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
this.networkSession = null;
|
||||||
|
KEYED_CLIENT_LEVEL_MANAGER.clearKeyedLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -21,13 +21,9 @@ package com.seibel.distanthorizons.core.api.internal;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
|
||||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
||||||
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
|
||||||
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
|
||||||
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
|
||||||
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
|
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
|
||||||
import com.seibel.distanthorizons.core.world.DhClientServerWorld;
|
import com.seibel.distanthorizons.core.world.DhClientServerWorld;
|
||||||
import com.seibel.distanthorizons.core.world.DhServerWorld;
|
import com.seibel.distanthorizons.core.world.DhServerWorld;
|
||||||
@@ -37,6 +33,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This holds the methods that should be called by the host mod loader (Fabric,
|
* This holds the methods that should be called by the host mod loader (Fabric,
|
||||||
@@ -70,7 +67,7 @@ public class ServerApi
|
|||||||
if (serverWorld != null)
|
if (serverWorld != null)
|
||||||
{
|
{
|
||||||
serverWorld.serverTick();
|
serverWorld.serverTick();
|
||||||
SharedApi.worldGenTick(serverWorld::doWorldGen);
|
SharedApi.worldGenTick(serverWorld::worldGenTick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -152,19 +149,37 @@ public class ServerApi
|
|||||||
public void serverPlayerJoinEvent(IServerPlayerWrapper player)
|
public void serverPlayerJoinEvent(IServerPlayerWrapper player)
|
||||||
{
|
{
|
||||||
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
|
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
|
||||||
if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well
|
LOGGER.info("Player [${player.getName()}] joined.");
|
||||||
|
if (serverWorld != null)
|
||||||
{
|
{
|
||||||
LOGGER.debug("Waiting for player to connect: " + player.getUUID());
|
serverWorld.addPlayer(player);
|
||||||
((DhServerWorld) serverWorld).addPlayer(player);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void serverPlayerDisconnectEvent(IServerPlayerWrapper player)
|
public void serverPlayerDisconnectEvent(IServerPlayerWrapper player)
|
||||||
{
|
{
|
||||||
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
|
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
|
||||||
if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well
|
LOGGER.info("Player [${player.getName()}] disconnected.");
|
||||||
|
if (serverWorld != null)
|
||||||
{
|
{
|
||||||
LOGGER.debug("Removing player from connect wait list: " + player.getUUID());
|
serverWorld.removePlayer(player);
|
||||||
((DhServerWorld) serverWorld).removePlayer(player);
|
}
|
||||||
|
}
|
||||||
|
public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
|
||||||
|
{
|
||||||
|
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
|
||||||
|
LOGGER.info("Player [${player.getName()}] changed level: [${originLevel.getKeyedLevelDimensionName()}] -> [${destinationLevel.getKeyedLevelDimensionName()}].");
|
||||||
|
if (serverWorld != null)
|
||||||
|
{
|
||||||
|
serverWorld.changePlayerLevel(player, originLevel, destinationLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message)
|
||||||
|
{
|
||||||
|
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
|
||||||
|
if (serverWorld != null)
|
||||||
|
{
|
||||||
|
serverWorld.getServerPlayerStateManager().handlePluginMessage(player, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,16 +26,16 @@ import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
|||||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||||
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
||||||
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
|
||||||
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
|
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
|
||||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.core.util.objects.Pair;
|
import com.seibel.distanthorizons.core.util.objects.Pair;
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
import com.seibel.distanthorizons.core.world.*;
|
import com.seibel.distanthorizons.core.world.*;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
@@ -43,9 +43,8 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
|
|
||||||
/** Contains code and variables used by both {@link ClientApi} and {@link ServerApi} */
|
/** Contains code and variables used by both {@link ClientApi} and {@link ServerApi} */
|
||||||
public class SharedApi
|
public class SharedApi
|
||||||
@@ -54,17 +53,15 @@ public class SharedApi
|
|||||||
|
|
||||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||||
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
||||||
private static final Set<DhChunkPos> UPDATING_CHUNK_POS_SET = ConcurrentHashMap.newKeySet();
|
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
/** how many chunks can be queued for updating per thread, used to prevent updates from infinitely pilling up if the user flys around extremely fast */
|
|
||||||
private static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD = 500;
|
|
||||||
private static final int MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE = 30_000;
|
|
||||||
|
|
||||||
private static final Timer CHUNK_UPDATE_TIMER = TimerUtil.CreateTimer("ChunkUpdateTimer");
|
private static final UpdateChunkPosManager UPDATE_POS_MANAGER = new UpdateChunkPosManager();
|
||||||
|
/** how many chunks can be queued for updating per thread, used to prevent updates from infinitely pilling up if the user flies around extremely fast */
|
||||||
|
private static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD = 500;
|
||||||
|
|
||||||
|
|
||||||
private static AbstractDhWorld currentWorld;
|
private static AbstractDhWorld currentWorld;
|
||||||
private static int lastWorldGenTickDelta = 0;
|
private static int lastWorldGenTickDelta = 0;
|
||||||
private static long lastOverloadedLogMessageMsTime = 0;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -101,7 +98,7 @@ public class SharedApi
|
|||||||
// shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed
|
// shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed
|
||||||
AbstractDhRepo.closeAllConnections();
|
AbstractDhRepo.closeAllConnections();
|
||||||
// needs to be closed on world shutdown to clear out un-processed chunks
|
// needs to be closed on world shutdown to clear out un-processed chunks
|
||||||
UPDATING_CHUNK_POS_SET.clear();
|
UPDATE_POS_MANAGER.clear();
|
||||||
|
|
||||||
// recommend that the garbage collector cleans up any objects from the old world and thread pools
|
// recommend that the garbage collector cleans up any objects from the old world and thread pools
|
||||||
System.gc();
|
System.gc();
|
||||||
@@ -120,11 +117,11 @@ public class SharedApi
|
|||||||
|
|
||||||
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; }
|
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; }
|
||||||
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientServerWorld} */
|
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientServerWorld} */
|
||||||
public static DhClientServerWorld getDhClientServerWorld() { return (currentWorld != null && DhClientServerWorld.class.isInstance(currentWorld)) ? (DhClientServerWorld) currentWorld : null; }
|
public static DhClientServerWorld getDhClientServerWorld() { return (currentWorld instanceof DhClientServerWorld) ? (DhClientServerWorld) currentWorld : null; }
|
||||||
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientWorld} or {@link DhClientServerWorld} */
|
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientWorld} or {@link DhClientServerWorld} */
|
||||||
public static IDhClientWorld getIDhClientWorld() { return (currentWorld != null && IDhClientWorld.class.isInstance(currentWorld)) ? (IDhClientWorld) currentWorld : null; }
|
public static IDhClientWorld getIDhClientWorld() { return (currentWorld instanceof IDhClientWorld) ? (IDhClientWorld) currentWorld : null; }
|
||||||
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhServerWorld} or {@link DhClientServerWorld} */
|
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhServerWorld} or {@link DhClientServerWorld} */
|
||||||
public static IDhServerWorld getIDhServerWorld() { return (currentWorld != null && IDhServerWorld.class.isInstance(currentWorld)) ? (IDhServerWorld) currentWorld : null; }
|
public static IDhServerWorld getIDhServerWorld() { return (currentWorld instanceof IDhServerWorld) ? (IDhServerWorld) currentWorld : null; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -137,25 +134,15 @@ public class SharedApi
|
|||||||
* This is important since asking MC for a chunk is slow and may block the render thread.
|
* This is important since asking MC for a chunk is slow and may block the render thread.
|
||||||
*/
|
*/
|
||||||
public static boolean isChunkAtBlockPosAlreadyUpdating(int blockPosX, int blockPosZ)
|
public static boolean isChunkAtBlockPosAlreadyUpdating(int blockPosX, int blockPosZ)
|
||||||
{ return UPDATING_CHUNK_POS_SET.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ))); }
|
{ return UPDATE_POS_MANAGER.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ))); }
|
||||||
|
|
||||||
|
public static boolean isChunkAtChunkPosAlreadyUpdating(int chunkPosX, int chunkPosZ)
|
||||||
|
{ return UPDATE_POS_MANAGER.contains(new DhChunkPos(chunkPosX, chunkPosZ)); }
|
||||||
|
|
||||||
|
|
||||||
/** handles both block place and break events */
|
/** handles both block place and break events */
|
||||||
public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true); }
|
public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true); }
|
||||||
|
|
||||||
public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, false); }
|
public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, false); }
|
||||||
public void chunkUnloadEvent(IChunkWrapper chunk, ILevelWrapper level)
|
|
||||||
{
|
|
||||||
// temporarily disabled since this was originally incorrectly designated as "chunkSaveEvent"
|
|
||||||
// but didn't actually fire on chunk save
|
|
||||||
// and generally this is unnecessary and drastically reduces LOD building performance
|
|
||||||
// when traveling around the world
|
|
||||||
if (false)
|
|
||||||
{
|
|
||||||
this.applyChunkUpdate(chunk, level, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean updateNeighborChunks)
|
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean updateNeighborChunks)
|
||||||
{
|
{
|
||||||
@@ -199,43 +186,6 @@ public class SharedApi
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=====================//
|
|
||||||
// task limiting check //
|
|
||||||
//=====================//
|
|
||||||
|
|
||||||
int currentQueueCount = UPDATING_CHUNK_POS_SET.size();
|
|
||||||
int maxQueueCount = MAX_UPDATING_CHUNK_COUNT_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads.get();
|
|
||||||
if (currentQueueCount >= maxQueueCount)
|
|
||||||
{
|
|
||||||
// The maximum number of chunks are already queued, don't add more.
|
|
||||||
// This is done to prevent overloading the system if the user fly's extremely fast and queues too many chunks
|
|
||||||
|
|
||||||
long msBetweenLastLog = System.currentTimeMillis() - lastOverloadedLogMessageMsTime;
|
|
||||||
if (msBetweenLastLog >= MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE)
|
|
||||||
{
|
|
||||||
lastOverloadedLogMessageMsTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
String message = "Distant Horizons overloaded, too many chunks queued for updating. " +
|
|
||||||
"\nThis may result in holes in your LODs. " +
|
|
||||||
"\nPlease move through the world slower, decrease your vanilla render distance, slow down your world pre-generator, or increase the Distant Horizons' CPU load config. " +
|
|
||||||
"\nMax queue count ["+maxQueueCount+"] (["+MAX_UPDATING_CHUNK_COUNT_PER_THREAD+"] per thread).";
|
|
||||||
ClientApi.INSTANCE.showChatMessageNextFrame(message);
|
|
||||||
LOGGER.warn(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevent duplicate update requests
|
|
||||||
if (UPDATING_CHUNK_POS_SET.contains(chunkWrapper.getChunkPos()))
|
|
||||||
{
|
|
||||||
// this chunk is already being updated
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UPDATING_CHUNK_POS_SET.add(chunkWrapper.getChunkPos());
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//===============================//
|
//===============================//
|
||||||
// update the necessary chunk(s) //
|
// update the necessary chunk(s) //
|
||||||
//===============================//
|
//===============================//
|
||||||
@@ -244,7 +194,7 @@ public class SharedApi
|
|||||||
{
|
{
|
||||||
// only update the center chunk
|
// only update the center chunk
|
||||||
|
|
||||||
bakeChunkLightingAndSendToLevelAsync(chunkWrapper, null, dhLevel);
|
queueChunkUpdate(chunkWrapper, null, dhLevel);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -265,7 +215,7 @@ public class SharedApi
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// neighboring chunk
|
// neighboring chunk
|
||||||
DhChunkPos neighbourPos = new DhChunkPos(chunkWrapper.getChunkPos().x + xOffset, chunkWrapper.getChunkPos().z + zOffset);
|
DhChunkPos neighbourPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
|
||||||
IChunkWrapper neighbourChunk = dhLevel.getLevelWrapper().tryGetChunk(neighbourPos);
|
IChunkWrapper neighbourChunk = dhLevel.getLevelWrapper().tryGetChunk(neighbourPos);
|
||||||
if (neighbourChunk != null)
|
if (neighbourChunk != null)
|
||||||
{
|
{
|
||||||
@@ -278,113 +228,120 @@ public class SharedApi
|
|||||||
// light and send the chunks
|
// light and send the chunks
|
||||||
for (IChunkWrapper litChunk : neighbourChunkList)
|
for (IChunkWrapper litChunk : neighbourChunkList)
|
||||||
{
|
{
|
||||||
bakeChunkLightingAndSendToLevelAsync(litChunk, neighbourChunkList, dhLevel);
|
queueChunkUpdate(litChunk, neighbourChunkList, dhLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static void bakeChunkLightingAndSendToLevelAsync(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighbourChunkList, IDhLevel dhLevel)
|
private static void queueChunkUpdate(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
|
if (MC_CLIENT != null && MC_CLIENT.playerExists())
|
||||||
ThreadPoolExecutor executor = ThreadPoolUtil.getLightPopulatorExecutor();
|
{
|
||||||
if (executor == null)
|
UPDATE_POS_MANAGER.setCenter(MC_CLIENT.getPlayerChunkPos());
|
||||||
|
UPDATE_POS_MANAGER.maxSize = MAX_UPDATING_CHUNK_COUNT_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateChunkData updateData = new UpdateChunkData(chunkWrapper, neighbourChunkList, dhLevel);
|
||||||
|
UPDATE_POS_MANAGER.addItem(chunkWrapper.getChunkPos(), updateData);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// queue updates up to the number of CPU cores allocated for the job
|
||||||
|
// (this prevents doing extra work queuing tasks that may not be necessary)
|
||||||
|
// and makes sure the chunks closest to the player are updated first
|
||||||
|
ThreadPoolExecutor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
|
||||||
|
if (executor != null && executor.getQueue().size() < executor.getCorePoolSize())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
executor.execute(SharedApi::processQueuedChunkUpdate);
|
||||||
|
}
|
||||||
|
catch (RejectedExecutionException ignore)
|
||||||
|
{
|
||||||
|
// the executor was shut down, it should be back up shortly and able to accept new jobs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** returning a {@link CompletableFuture} isn't necessary, but allows Intellij to properly show the full stack trace when debugging. */
|
||||||
|
@SuppressWarnings("UnusedReturnValue")
|
||||||
|
private static void processQueuedChunkUpdate()
|
||||||
|
{
|
||||||
|
//LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
|
||||||
|
|
||||||
|
UpdateChunkData updateData = UPDATE_POS_MANAGER.popClosest();
|
||||||
|
if (updateData == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IChunkWrapper chunkWrapper = updateData.chunkWrapper;
|
||||||
|
@Nullable ArrayList<IChunkWrapper> neighbourChunkList = updateData.neighbourChunkList;
|
||||||
|
IDhLevel dhLevel = updateData.dhLevel;
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
executor.execute(() ->
|
boolean checkChunkHash = !Config.Client.Advanced.LodBuilding.disableUnchangedChunkCheck.get();
|
||||||
|
|
||||||
|
// check if this chunk has been converted into an LOD already
|
||||||
|
int oldChunkHash = dhLevel.getChunkHash(chunkWrapper.getChunkPos()); // shouldn't happen on the render thread since it may take a few moments to run
|
||||||
|
int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
|
||||||
|
if (checkChunkHash)
|
||||||
|
{
|
||||||
|
if (oldChunkHash == newChunkHash)
|
||||||
|
{
|
||||||
|
// if the chunk hashes are the same then we don't need to bother with lighting the chunk
|
||||||
|
// or creating/updating the LODs
|
||||||
|
//LOGGER.info("skipping: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//LOGGER.info("g: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// having a list of the nearby chunks is needed for lighting and beacon generation
|
||||||
|
ArrayList<IChunkWrapper> nearbyChunkList;
|
||||||
|
if (neighbourChunkList != null)
|
||||||
|
{
|
||||||
|
nearbyChunkList = neighbourChunkList;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nearbyChunkList = new ArrayList<>(1);
|
||||||
|
nearbyChunkList.add(chunkWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// sky lighting is populated later at the data source level
|
||||||
|
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
|
||||||
|
|
||||||
|
dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList);
|
||||||
|
dhLevel.updateChunkAsync(chunkWrapper, newChunkHash);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// queue the next position if there are still positions to process
|
||||||
|
ThreadPoolExecutor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
|
||||||
|
if (executor != null && !UPDATE_POS_MANAGER.positionMap.isEmpty())
|
||||||
{
|
{
|
||||||
//LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// check if this chunk has been converted into an LOD already
|
executor.execute(SharedApi::processQueuedChunkUpdate);
|
||||||
int oldChunkHash = dhLevel.getChunkHash(chunkWrapper.getChunkPos()); // shouldn't happen on the render thread since it may take a few moments to run
|
|
||||||
int newChunkHash = chunkWrapper.getBlockBiomeHashCode();
|
|
||||||
if (oldChunkHash == newChunkHash)
|
|
||||||
{
|
|
||||||
// if the chunk hashes are the same then we don't need to bother with lighting the chunk
|
|
||||||
// or creating/updating the LODs
|
|
||||||
//LOGGER.info("skipping: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//LOGGER.info("g: "+chunkWrapper.getChunkPos()+" "+newChunkHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// having a list of the nearby chunks is needed for lighting and beacon generation
|
|
||||||
ArrayList<IChunkWrapper> nearbyChunkList;
|
|
||||||
if (neighbourChunkList != null)
|
|
||||||
{
|
|
||||||
nearbyChunkList = neighbourChunkList;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nearbyChunkList = new ArrayList<>(1);
|
|
||||||
nearbyChunkList.add(chunkWrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 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())
|
|
||||||
{
|
|
||||||
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
|
|
||||||
{
|
|
||||||
// generate the chunk's lighting, using neighboring chunks if present
|
|
||||||
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? 15 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// get this chunk's active beacons
|
|
||||||
List<BeaconBeamDTO> beaconBeamList = chunkWrapper.getAllActiveBeacons(nearbyChunkList);
|
|
||||||
dhLevel.setBeaconBeamsForChunk(chunkWrapper.getChunkPos(), beaconBeamList);
|
|
||||||
|
|
||||||
|
|
||||||
dhLevel.updateChunkAsync(chunkWrapper);
|
|
||||||
dhLevel.setChunkHash(chunkWrapper.getChunkPos(), newChunkHash);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (RejectedExecutionException ignore)
|
||||||
{
|
{
|
||||||
LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
|
// the executor was shut down, it should be back up shortly and able to accept new jobs
|
||||||
}
|
}
|
||||||
finally
|
}
|
||||||
{
|
|
||||||
// the LOD chunk has finished being updated
|
|
||||||
int updateTimeoutInSec = Config.Client.Advanced.LodBuilding.minTimeBetweenChunkUpdatesInSeconds.get();
|
|
||||||
if (updateTimeoutInSec != 0)
|
|
||||||
{
|
|
||||||
// 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (RejectedExecutionException ignore) { /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -395,11 +352,185 @@ public class SharedApi
|
|||||||
|
|
||||||
public String getDebugMenuString()
|
public String getDebugMenuString()
|
||||||
{
|
{
|
||||||
int maxUpdateCount = MAX_UPDATING_CHUNK_COUNT_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads.get();
|
String updatingCountStr = F3Screen.NUMBER_FORMAT.format(UPDATE_POS_MANAGER.closestQueue.size());
|
||||||
String updatingCountStr = F3Screen.NUMBER_FORMAT.format(UPDATING_CHUNK_POS_SET.size());
|
String maxUpdateCountStr = F3Screen.NUMBER_FORMAT.format(UPDATE_POS_MANAGER.maxSize);
|
||||||
String maxUpdateCountStr = F3Screen.NUMBER_FORMAT.format(maxUpdateCount);
|
|
||||||
return "Queued chunk updates: "+updatingCountStr+" / "+maxUpdateCountStr;
|
return "Queued chunk updates: "+updatingCountStr+" / "+maxUpdateCountStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// helper classes //
|
||||||
|
//================//
|
||||||
|
|
||||||
|
/** contains the objects needed to update a chunk */
|
||||||
|
private static class UpdateChunkData
|
||||||
|
{
|
||||||
|
public IChunkWrapper chunkWrapper;
|
||||||
|
@Nullable
|
||||||
|
public ArrayList<IChunkWrapper> neighbourChunkList;
|
||||||
|
public IDhLevel dhLevel;
|
||||||
|
|
||||||
|
public UpdateChunkData(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighbourChunkList, IDhLevel dhLevel)
|
||||||
|
{
|
||||||
|
this.chunkWrapper = chunkWrapper;
|
||||||
|
this.neighbourChunkList = neighbourChunkList;
|
||||||
|
this.dhLevel = dhLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** keeps track of which chunks need to be updated */
|
||||||
|
private static class UpdateChunkPosManager
|
||||||
|
{
|
||||||
|
private final PriorityQueue<DhChunkPos> closestQueue;
|
||||||
|
private final PriorityQueue<DhChunkPos> furthestQueue;
|
||||||
|
private final HashMap<DhChunkPos, UpdateChunkData> positionMap;
|
||||||
|
|
||||||
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
|
|
||||||
|
private DhChunkPos center;
|
||||||
|
private int maxSize = 500;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
|
||||||
|
public UpdateChunkPosManager()
|
||||||
|
{
|
||||||
|
this.closestQueue = new PriorityQueue<>(Comparator.comparingDouble(pos -> pos.squaredDistance(this.center)));
|
||||||
|
this.furthestQueue = new PriorityQueue<>(Comparator.comparingDouble(pos -> ((DhChunkPos)pos).squaredDistance(this.center)).reversed());
|
||||||
|
this.positionMap = new HashMap<>();
|
||||||
|
// defaulting to 0,0 is fine since it'll be updated once we start adding items
|
||||||
|
this.center = new DhChunkPos(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==================//
|
||||||
|
// list/set methods //
|
||||||
|
//==================//
|
||||||
|
|
||||||
|
public boolean contains(DhChunkPos pos)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.lock.lock();
|
||||||
|
|
||||||
|
return this.positionMap.containsKey(pos);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.lock.lock();
|
||||||
|
|
||||||
|
this.positionMap.clear();
|
||||||
|
this.closestQueue.clear();
|
||||||
|
this.furthestQueue.clear();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addItem(DhChunkPos pos, UpdateChunkData updateData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.lock.lock();
|
||||||
|
|
||||||
|
if (this.positionMap.containsKey(pos))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.positionMap.size() >= this.maxSize)
|
||||||
|
{
|
||||||
|
// Remove item furthest from the center
|
||||||
|
DhChunkPos furthest = this.furthestQueue.poll();
|
||||||
|
this.closestQueue.remove(furthest);
|
||||||
|
this.positionMap.remove(furthest);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.positionMap.put(pos, updateData);
|
||||||
|
this.closestQueue.add(pos);
|
||||||
|
this.furthestQueue.add(pos);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==================//
|
||||||
|
// position methods //
|
||||||
|
//==================//
|
||||||
|
|
||||||
|
public void setCenter(DhChunkPos newCenter)
|
||||||
|
{
|
||||||
|
// if the rebuild time takes too long
|
||||||
|
// (in James' testing a queue of 500 items only took around 0.1 milliseconds)
|
||||||
|
// this equation could be changed to only update after moving 2 or 4 chunks from the center
|
||||||
|
if (newCenter.equals(this.center))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.lock.lock();
|
||||||
|
|
||||||
|
this.center = newCenter;
|
||||||
|
|
||||||
|
// rebuild the priority queues to match the new center
|
||||||
|
this.closestQueue.clear();
|
||||||
|
this.furthestQueue.clear();
|
||||||
|
for (DhChunkPos pos : this.positionMap.keySet())
|
||||||
|
{
|
||||||
|
this.closestQueue.add(pos);
|
||||||
|
this.furthestQueue.add(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateChunkData popClosest()
|
||||||
|
{
|
||||||
|
if (this.closestQueue.isEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.lock.lock();
|
||||||
|
|
||||||
|
DhChunkPos closest = this.closestQueue.poll();
|
||||||
|
this.furthestQueue.remove(closest);
|
||||||
|
return this.positionMap.remove(closest);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,17 +20,20 @@
|
|||||||
package com.seibel.distanthorizons.core.config;
|
package com.seibel.distanthorizons.core.config;
|
||||||
|
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.DhApi;
|
||||||
import com.seibel.distanthorizons.api.enums.config.*;
|
import com.seibel.distanthorizons.api.enums.config.*;
|
||||||
import com.seibel.distanthorizons.api.enums.config.quickOptions.*;
|
import com.seibel.distanthorizons.api.enums.config.quickOptions.*;
|
||||||
import com.seibel.distanthorizons.api.enums.rendering.*;
|
import com.seibel.distanthorizons.api.enums.rendering.*;
|
||||||
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
|
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
|
||||||
import com.seibel.distanthorizons.core.config.eventHandlers.*;
|
import com.seibel.distanthorizons.core.config.eventHandlers.*;
|
||||||
import com.seibel.distanthorizons.core.config.eventHandlers.presets.*;
|
import com.seibel.distanthorizons.core.config.eventHandlers.presets.*;
|
||||||
|
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
|
||||||
import com.seibel.distanthorizons.core.config.types.*;
|
import com.seibel.distanthorizons.core.config.types.*;
|
||||||
import com.seibel.distanthorizons.core.config.types.enums.*;
|
import com.seibel.distanthorizons.core.config.types.enums.*;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||||
@@ -40,6 +43,7 @@ import javax.swing.*;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,7 +55,6 @@ import java.util.List;
|
|||||||
* Otherwise, you will have issues where only some of the config entries will exist when your listener is created.
|
* Otherwise, you will have issues where only some of the config entries will exist when your listener is created.
|
||||||
*
|
*
|
||||||
* @author coolGi
|
* @author coolGi
|
||||||
* @version 2023-7-16
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class Config
|
public class Config
|
||||||
@@ -104,6 +107,8 @@ public class Config
|
|||||||
|
|
||||||
public static ConfigLinkedEntry quickEnableWorldGenerator = new ConfigLinkedEntry(Advanced.WorldGenerator.enableDistantGeneration);
|
public static ConfigLinkedEntry quickEnableWorldGenerator = new ConfigLinkedEntry(Advanced.WorldGenerator.enableDistantGeneration);
|
||||||
|
|
||||||
|
public static ConfigLinkedEntry quickLodCloudRendering = new ConfigLinkedEntry(Advanced.Graphics.GenericRendering.enableCloudRendering);
|
||||||
|
|
||||||
public static ConfigEntry<Boolean> optionsButton = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> optionsButton = new ConfigEntry.Builder<Boolean>()
|
||||||
.set(true)
|
.set(true)
|
||||||
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
|
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
|
||||||
@@ -123,7 +128,6 @@ public class Config
|
|||||||
public static ConfigCategory multiplayer = new ConfigCategory.Builder().set(Multiplayer.class).build();
|
public static ConfigCategory multiplayer = new ConfigCategory.Builder().set(Multiplayer.class).build();
|
||||||
public static ConfigCategory lodBuilding = new ConfigCategory.Builder().set(LodBuilding.class).build();
|
public static ConfigCategory lodBuilding = new ConfigCategory.Builder().set(LodBuilding.class).build();
|
||||||
public static ConfigCategory multiThreading = new ConfigCategory.Builder().set(MultiThreading.class).build();
|
public static ConfigCategory multiThreading = new ConfigCategory.Builder().set(MultiThreading.class).build();
|
||||||
public static ConfigCategory buffers = new ConfigCategory.Builder().set(GpuBuffers.class).build();
|
|
||||||
public static ConfigCategory autoUpdater = new ConfigCategory.Builder().set(AutoUpdater.class).build();
|
public static ConfigCategory autoUpdater = new ConfigCategory.Builder().set(AutoUpdater.class).build();
|
||||||
|
|
||||||
public static ConfigCategory logging = new ConfigCategory.Builder().set(Logging.class).build();
|
public static ConfigCategory logging = new ConfigCategory.Builder().set(Logging.class).build();
|
||||||
@@ -160,9 +164,19 @@ public class Config
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Integer> lodChunkRenderDistanceRadius = new ConfigEntry.Builder<Integer>()
|
public static ConfigEntry<Integer> lodChunkRenderDistanceRadius = new ConfigEntry.Builder<Integer>()
|
||||||
|
.setServersideShortName("renderDistanceRadius")
|
||||||
.setMinDefaultMax(32, 128, 4096)
|
.setMinDefaultMax(32, 128, 4096)
|
||||||
.comment("The radius of the mod's render distance. (measured in chunks)")
|
.comment("" +
|
||||||
|
"The radius of the mod's render distance. (measured in chunks)\n" +
|
||||||
|
"On server changes the distance players will receive real-time updates for, if enabled." +
|
||||||
|
"\n" +
|
||||||
|
"Note for servers:\n" +
|
||||||
|
"This setting does not prevent players from generating farther out.\n" +
|
||||||
|
"If you want to limit performance impact, change rate limits\n" +
|
||||||
|
"and thread count/runtime ratio settings instead.\n" +
|
||||||
|
"It also does not affect the visuals on clients.")
|
||||||
.setPerformance(EConfigEntryPerformance.HIGH)
|
.setPerformance(EConfigEntryPerformance.HIGH)
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiVerticalQuality> verticalQuality = new ConfigEntry.Builder<EDhApiVerticalQuality>()
|
public static ConfigEntry<EDhApiVerticalQuality> verticalQuality = new ConfigEntry.Builder<EDhApiVerticalQuality>()
|
||||||
@@ -176,6 +190,7 @@ public class Config
|
|||||||
+ "Lowest Quality: " + EDhApiVerticalQuality.HEIGHT_MAP + "\n"
|
+ "Lowest Quality: " + EDhApiVerticalQuality.HEIGHT_MAP + "\n"
|
||||||
+ "Highest Quality: " + EDhApiVerticalQuality.EXTREME)
|
+ "Highest Quality: " + EDhApiVerticalQuality.EXTREME)
|
||||||
.setPerformance(EConfigEntryPerformance.VERY_HIGH)
|
.setPerformance(EConfigEntryPerformance.VERY_HIGH)
|
||||||
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiHorizontalQuality> horizontalQuality = new ConfigEntry.Builder<EDhApiHorizontalQuality>()
|
public static ConfigEntry<EDhApiHorizontalQuality> horizontalQuality = new ConfigEntry.Builder<EDhApiHorizontalQuality>()
|
||||||
@@ -197,6 +212,7 @@ public class Config
|
|||||||
+ EDhApiTransparency.DISABLED + ": LODs will be opaque. \n"
|
+ EDhApiTransparency.DISABLED + ": LODs will be opaque. \n"
|
||||||
+ "")
|
+ "")
|
||||||
.setPerformance(EConfigEntryPerformance.MEDIUM)
|
.setPerformance(EConfigEntryPerformance.MEDIUM)
|
||||||
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiBlocksToAvoid> blocksToIgnore = new ConfigEntry.Builder<EDhApiBlocksToAvoid>()
|
public static ConfigEntry<EDhApiBlocksToAvoid> blocksToIgnore = new ConfigEntry.Builder<EDhApiBlocksToAvoid>()
|
||||||
@@ -208,6 +224,7 @@ public class Config
|
|||||||
+ EDhApiBlocksToAvoid.NON_COLLIDING + ": Only represent solid blocks in the LODs (tall grass, torches, etc. won't count for a LOD's height) \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)
|
.setPerformance(EConfigEntryPerformance.NONE)
|
||||||
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Boolean> tintWithAvoidedBlocks = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> tintWithAvoidedBlocks = new ConfigEntry.Builder<Boolean>()
|
||||||
@@ -219,6 +236,7 @@ public class Config
|
|||||||
+ "False: skipped blocks will not change color of surface below them. "
|
+ "False: skipped blocks will not change color of surface below them. "
|
||||||
+ "")
|
+ "")
|
||||||
.setPerformance(EConfigEntryPerformance.NONE)
|
.setPerformance(EConfigEntryPerformance.NONE)
|
||||||
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// TODO fixme
|
// TODO fixme
|
||||||
@@ -556,6 +574,14 @@ public class Config
|
|||||||
+ "")
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Boolean> enableInstancedRendering = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.set(true)
|
||||||
|
.comment(""
|
||||||
|
+ "Can be disabled to use much slower but more compatible direct rendering. \n"
|
||||||
|
+ "Disabling this can be used to fix some crashes on Mac. \n"
|
||||||
|
+ "")
|
||||||
|
.build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AdvancedGraphics
|
public static class AdvancedGraphics
|
||||||
@@ -584,6 +610,7 @@ public class Config
|
|||||||
+ "0 = black \n"
|
+ "0 = black \n"
|
||||||
+ "1 = normal \n"
|
+ "1 = normal \n"
|
||||||
+ "2 = near white")
|
+ "2 = near white")
|
||||||
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Double> saturationMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats)
|
public static ConfigEntry<Double> saturationMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats)
|
||||||
@@ -594,6 +621,7 @@ public class Config
|
|||||||
+ "0 = black and white \n"
|
+ "0 = black and white \n"
|
||||||
+ "1 = normal \n"
|
+ "1 = normal \n"
|
||||||
+ "2 = very saturated")
|
+ "2 = very saturated")
|
||||||
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Boolean> enableCaveCulling = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> enableCaveCulling = new ConfigEntry.Builder<Boolean>()
|
||||||
@@ -608,12 +636,15 @@ public class Config
|
|||||||
+ "Additional Info: Currently this cull all faces \n"
|
+ "Additional Info: Currently this cull all faces \n"
|
||||||
+ " with skylight value of 0 in dimensions that \n"
|
+ " with skylight value of 0 in dimensions that \n"
|
||||||
+ " does not have a ceiling.")
|
+ " does not have a ceiling.")
|
||||||
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Integer> caveCullingHeight = new ConfigEntry.Builder<Integer>()
|
public static ConfigEntry<Integer> caveCullingHeight = new ConfigEntry.Builder<Integer>()
|
||||||
.setMinDefaultMax(-4096, 40, 4096)
|
.setMinDefaultMax(-4096, 60, 4096)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "At what Y value should cave culling start?")
|
+ "At what Y value should cave culling start? \n"
|
||||||
|
+ "Lower this value if you get walls for areas with 0 light.")
|
||||||
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>()
|
public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>()
|
||||||
@@ -651,6 +682,7 @@ public class Config
|
|||||||
+ EDhApiLodShading.DISABLED + ": 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)
|
.setPerformance(EConfigEntryPerformance.NONE)
|
||||||
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Boolean> disableFrustumCulling = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> disableFrustumCulling = new ConfigEntry.Builder<Boolean>()
|
||||||
@@ -685,6 +717,18 @@ public class Config
|
|||||||
+ EDhApiGrassSideRendering.AS_DIRT + ": sides render entirely as dirt. \n"
|
+ EDhApiGrassSideRendering.AS_DIRT + ": sides render entirely as dirt. \n"
|
||||||
+ "")
|
+ "")
|
||||||
.setPerformance(EConfigEntryPerformance.NONE)
|
.setPerformance(EConfigEntryPerformance.NONE)
|
||||||
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Boolean> disableBeaconDistanceCulling = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.set(false)
|
||||||
|
.comment(""
|
||||||
|
+ "If true all beacons near the camera won't be drawn to prevent vanilla overdraw. \n"
|
||||||
|
+ "If false all beacons will be rendered. \n"
|
||||||
|
+ "\n"
|
||||||
|
+ "Generally this should be left as false. It's main purpose is for debugging\n"
|
||||||
|
+ "beacon updating/rendering.\n"
|
||||||
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -694,16 +738,16 @@ public class Config
|
|||||||
public static class WorldGenerator
|
public static class WorldGenerator
|
||||||
{
|
{
|
||||||
public static ConfigEntry<Boolean> enableDistantGeneration = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> enableDistantGeneration = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.setServersideShortName("enableDistantGeneration")
|
||||||
.set(true)
|
.set(true)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ " Should Distant Horizons slowly generate LODs \n"
|
+ " Should Distant Horizons slowly generate LODs \n"
|
||||||
+ " outside the vanilla render distance?\n"
|
+ " outside the vanilla render distance?")
|
||||||
+ "\n"
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
+ " Note: when on a server, distant generation isn't supported \n"
|
|
||||||
+ " and will always be disabled.")
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiDistantGeneratorMode> distantGeneratorMode = new ConfigEntry.Builder<EDhApiDistantGeneratorMode>()
|
public static ConfigEntry<EDhApiDistantGeneratorMode> distantGeneratorMode = new ConfigEntry.Builder<EDhApiDistantGeneratorMode>()
|
||||||
|
.setServersideShortName("distantGeneratorMode")
|
||||||
.set(EDhApiDistantGeneratorMode.FEATURES)
|
.set(EDhApiDistantGeneratorMode.FEATURES)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "How detailed should LODs be generated outside the vanilla render distance? \n"
|
+ "How detailed should LODs be generated outside the vanilla render distance? \n"
|
||||||
@@ -733,32 +777,36 @@ public class Config
|
|||||||
+ EDhApiDistantGeneratorMode.FEATURES + " \n"
|
+ EDhApiDistantGeneratorMode.FEATURES + " \n"
|
||||||
+ "Generate everything except structures. \n"
|
+ "Generate everything except structures. \n"
|
||||||
+ "WARNING: This may cause world generator bugs or instability when paired with certain world generator mods. \n"
|
+ "WARNING: This may cause world generator bugs or instability when paired with certain world generator mods. \n"
|
||||||
|
//not currently implemented
|
||||||
|
//+ "\n"
|
||||||
|
//+ EDhApiDistantGeneratorMode.FULL + " \n"
|
||||||
|
//+ "Ask the local server to generate/load each chunk. \n"
|
||||||
|
//+ "This is the most compatible, but will cause server/simulation lag. \n"
|
||||||
|
//+ "- Slow (15-50 ms, with spikes up to 200 ms) \n"
|
||||||
+ "")
|
+ "")
|
||||||
/*
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
// FULL isn't currently implemented
|
|
||||||
+ "\n"
|
|
||||||
+ EDhApiDistantGeneratorMode.FULL + " \n"
|
|
||||||
+ "Ask the local server to generate/load each chunk. \n"
|
|
||||||
+ "This is the most compatible, but will cause server/simulation lag. \n"
|
|
||||||
+ "- Slow (15-50 ms, with spikes up to 200 ms) \n"
|
|
||||||
*/
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<Integer> worldGenerationTimeoutLengthInSeconds = new ConfigEntry.Builder<Integer>()
|
public static ConfigEntry<Integer> worldGenerationTimeoutLengthInSeconds = new ConfigEntry.Builder<Integer>()
|
||||||
|
.setServersideShortName("worldGenerationTimeout")
|
||||||
.setMinDefaultMax(5, 60 * 3, 60 * 10/*10 minutes*/ )
|
.setMinDefaultMax(5, 60 * 3, 60 * 10/*10 minutes*/ )
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "How long should a world generator thread run for before timing out? \n"
|
+ "How long should a world generator thread run for before timing out? \n"
|
||||||
+ "Note: If you are experiencing timeout errors it is better to lower your CPU usage first \n"
|
+ "Note: If you are experiencing timeout errors it is better to lower your CPU usage first \n"
|
||||||
+ "via the thread config before changing this value. \n"
|
+ "via the thread config before changing this value. \n"
|
||||||
+ "")
|
+ "")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class LodBuilding
|
public static class LodBuilding
|
||||||
{
|
{
|
||||||
|
@Deprecated
|
||||||
public static ConfigEntry<Integer> minTimeBetweenChunkUpdatesInSeconds = new ConfigEntry.Builder<Integer>()
|
public static ConfigEntry<Integer> minTimeBetweenChunkUpdatesInSeconds = new ConfigEntry.Builder<Integer>()
|
||||||
|
.setServersideShortName("minTimeBetweenChunkUpdates")
|
||||||
.setMinDefaultMax(0, 1, 60)
|
.setMinDefaultMax(0, 1, 60)
|
||||||
|
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "Determines how long must pass between LOD chunk updates before another. \n"
|
+ "Determines how long must pass between LOD chunk updates before another. \n"
|
||||||
+ "update can occur\n"
|
+ "update can occur\n"
|
||||||
@@ -768,8 +816,23 @@ public class Config
|
|||||||
+ "")
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Boolean> disableUnchangedChunkCheck = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.set(false)
|
||||||
|
.comment(""
|
||||||
|
+ "Normally DH will attempt to skip creating LODs for chunks it's already seen\n"
|
||||||
|
+ "and that haven't changed.\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "However sometimes that logic incorrecly prevents LODs from being updated.\n"
|
||||||
|
+ "Disabling this check may fix issues where LODs aren't updated after\n"
|
||||||
|
+ "blocks have been changed.\n"
|
||||||
|
+ "")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
/** Currently we always use the DH lighting engine because there's a high likelyhood of MC returning incorrect lighting otherwise */
|
||||||
|
@Deprecated
|
||||||
public static ConfigEntry<Boolean> onlyUseDhLightingEngine = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> onlyUseDhLightingEngine = new ConfigEntry.Builder<Boolean>()
|
||||||
.set(false)
|
.set(false)
|
||||||
|
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "If false LODs will be lit by Minecraft's lighting engine when possible \n"
|
+ "If false LODs will be lit by Minecraft's lighting engine when possible \n"
|
||||||
+ "and fall back to the DH lighting engine only when necessary. \n"
|
+ "and fall back to the DH lighting engine only when necessary. \n"
|
||||||
@@ -843,13 +906,47 @@ public class Config
|
|||||||
+ "")
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
//public static ConfigEntry<Boolean> showMigrationChatWarning = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<String> ignoredRenderBlockCsv = new ConfigEntry.Builder<String>()
|
||||||
// .set(true)
|
.set("minecraft:barrier,minecraft:structure_void,minecraft:light,minecraft:tripwire,minecraft:brown_mushroom")
|
||||||
// .comment(""
|
.comment(""
|
||||||
// + "Determines if a message should be displayed in the chat when LOD migration starts. \n"
|
+ "A comma separated list of block resource locations that won't be rendered by DH. \n"
|
||||||
// + "")
|
+ "Note: air is always included in this list. \n"
|
||||||
// .build();
|
+ "")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<String> ignoredRenderCaveBlockCsv = new ConfigEntry.Builder<String>()
|
||||||
|
.set("minecraft:glow_lichen,minecraft:rail,minecraft:water,minecraft:lava,minecraft:bubble_column")
|
||||||
|
.comment(""
|
||||||
|
+ "A comma separated list of block resource locations that shouldn't be rendered \n"
|
||||||
|
+ "if they are in a 0 sky light underground area. \n"
|
||||||
|
+ "Note: air is always included in this list. \n"
|
||||||
|
+ "")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
ignoredRenderBlockCsv.addListener(new ConfigChangeListener<String>(Config.Client.Advanced.LodBuilding.ignoredRenderBlockCsv,
|
||||||
|
(blockCsv) ->
|
||||||
|
{
|
||||||
|
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
||||||
|
if (wrapperFactory != null)
|
||||||
|
{
|
||||||
|
wrapperFactory.resetRendererIgnoredBlocksSet();
|
||||||
|
DhApi.Delayed.renderProxy.clearRenderDataCache();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
ignoredRenderCaveBlockCsv.addListener(new ConfigChangeListener<String>(Config.Client.Advanced.LodBuilding.ignoredRenderCaveBlockCsv,
|
||||||
|
(blockCsv) ->
|
||||||
|
{
|
||||||
|
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
||||||
|
if (wrapperFactory != null)
|
||||||
|
{
|
||||||
|
wrapperFactory.resetRendererIgnoredCaveBlocks();
|
||||||
|
DhApi.Delayed.renderProxy.clearRenderDataCache();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Multiplayer
|
public static class Multiplayer
|
||||||
@@ -891,27 +988,98 @@ public class Config
|
|||||||
+ "")
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// not currently implemented
|
|
||||||
private static ConfigEntry<Boolean> enableMultiverseNetworking = new ConfigEntry.Builder<Boolean>()
|
|
||||||
.set(true)
|
|
||||||
.comment(""
|
|
||||||
+ "If true Distant Horizons will attempt to communicate with the connected \n"
|
|
||||||
+ "server in order to improve multiverse support. \n"
|
|
||||||
+ "")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// not currently implemented
|
|
||||||
private static ConfigEntry<Boolean> enableServerNetworking = new ConfigEntry.Builder<Boolean>()
|
public static ConfigCategory serverNetworking = new ConfigCategory.Builder().set(ServerNetworking.class).build();
|
||||||
.set(false)
|
|
||||||
.comment(""
|
public static class ServerNetworking
|
||||||
+ "Attention: this is only for developers and hasn't been implemented.\n"
|
{
|
||||||
+ "\n"
|
public static ConfigUIComment generalSectionNote = new ConfigUIComment();
|
||||||
+ "If true Distant Horizons will attempt to communicate with the connected \n"
|
public static ConfigEntry<Boolean> enableServerNetworking = new ConfigEntry.Builder<Boolean>()
|
||||||
+ "server in order to load LODs outside your vanilla render distance. \n"
|
.setServersideShortName("enableServerNetworking")
|
||||||
+ "\n"
|
.set(true)
|
||||||
+ "Note: This requires DH to be installed on the server in order to function. \n"
|
.comment(""
|
||||||
+ "")
|
+ "WARNING!\n"
|
||||||
.build();
|
+ "Server-client networking is not yet fully implemented!\n"
|
||||||
|
+ "Both the server and client must be running the server-side fork with this option enabled\n"
|
||||||
|
+ "for Distant Horizons data to be transceived.\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "If true, the server and client will attempt to communicate to transceive Distant Horizons data.\n"
|
||||||
|
+ "This allows for further distant generation and LOD updates on all clients.\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "This should only be used on trusted servers with trusted players!\n"
|
||||||
|
+ "")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
|
||||||
|
public static ConfigEntry<Boolean> sendLevelKeys = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.setServersideShortName("sendLevelKeys")
|
||||||
|
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
|
||||||
|
.set(true)
|
||||||
|
.comment(""
|
||||||
|
+ "Makes the server send level keys for each world.\n"
|
||||||
|
+ "Disable this if you use alternative ways to send level keys.\n"
|
||||||
|
+ "")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
|
.build();
|
||||||
|
public static ConfigEntry<String> levelKeyPrefix = new ConfigEntry.Builder<String>()
|
||||||
|
.setServersideShortName("levelKeyPrefix")
|
||||||
|
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
|
||||||
|
.set(getDefaultLevelKeyPrefix())
|
||||||
|
.comment(""
|
||||||
|
+ "Prefix of the level keys sent to the clients.\n"
|
||||||
|
+ "Should be set to a unique value for each backend server behind a proxy,\n"
|
||||||
|
+ "or empty if you don't use a proxy.\n"
|
||||||
|
+ "")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
|
||||||
|
public static ConfigUIComment generationSectionNote = new ConfigUIComment();
|
||||||
|
public static ConfigEntry<Integer> generationRequestRateLimit = new ConfigEntry.Builder<Integer>()
|
||||||
|
.setServersideShortName("generationRequestRateLimit")
|
||||||
|
.setMinDefaultMax(1, 20, 100)
|
||||||
|
.comment(""
|
||||||
|
+ "How many LOD generation requests per second should a client send? \n"
|
||||||
|
+ "Also limits the amount of player's requests allowed to stay in the server's queue."
|
||||||
|
+ "")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
|
||||||
|
public static ConfigUIComment realTimeUpdatesSectionNote = new ConfigUIComment();
|
||||||
|
public static ConfigEntry<Boolean> enableRealTimeUpdates = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.setServersideShortName("enableRealTimeUpdates")
|
||||||
|
.set(false)
|
||||||
|
.comment(""
|
||||||
|
+ "If true, the client will receive real-time LOD updates for chunks outside the client's render distance."
|
||||||
|
+ "")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
|
||||||
|
// TODO rename
|
||||||
|
public static ConfigUIComment syncOnLoginSectionNote = new ConfigUIComment();
|
||||||
|
public static ConfigEntry<Boolean> synchronizeOnLogin = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.setServersideShortName("synchronizeOnLogin")
|
||||||
|
.set(false)
|
||||||
|
.comment(""
|
||||||
|
+ "If true, clients will receive updated LODs on join if any changes occurred since last join."
|
||||||
|
+ "")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Integer> syncOnLoginRateLimit = new ConfigEntry.Builder<Integer>()
|
||||||
|
.setServersideShortName("syncOnLoginRateLimit")
|
||||||
|
.setMinDefaultMax(1, 50, 100)
|
||||||
|
.comment(""
|
||||||
|
+ "How many LOD sync requests per second should a client send? \n"
|
||||||
|
+ "Also limits the amount of player's requests allowed to stay in the server's queue."
|
||||||
|
+ "")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -933,6 +1101,7 @@ public class Config
|
|||||||
|
|
||||||
|
|
||||||
public static final ConfigEntry<Integer> numberOfWorldGenerationThreads = new ConfigEntry.Builder<Integer>()
|
public static final ConfigEntry<Integer> numberOfWorldGenerationThreads = new ConfigEntry.Builder<Integer>()
|
||||||
|
.setServersideShortName("numberOfWorldGenerationThreads")
|
||||||
.setMinDefaultMax(1,
|
.setMinDefaultMax(1,
|
||||||
ThreadPresetConfigEventHandler.getWorldGenDefaultThreadCount(),
|
ThreadPresetConfigEventHandler.getWorldGenDefaultThreadCount(),
|
||||||
Runtime.getRuntime().availableProcessors())
|
Runtime.getRuntime().availableProcessors())
|
||||||
@@ -946,13 +1115,17 @@ public class Config
|
|||||||
+ "generation speed, increase this number. \n"
|
+ "generation speed, increase this number. \n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ THREAD_NOTE)
|
+ THREAD_NOTE)
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
public static final ConfigEntry<Double> runTimeRatioForWorldGenerationThreads = new ConfigEntry.Builder<Double>()
|
public static final ConfigEntry<Double> runTimeRatioForWorldGenerationThreads = new ConfigEntry.Builder<Double>()
|
||||||
|
.setServersideShortName("runTimeRatioForWorldGenerationThreads")
|
||||||
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getWorldGenDefaultRunTimeRatio(), 1.0)
|
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getWorldGenDefaultRunTimeRatio(), 1.0)
|
||||||
.comment(THREAD_RUN_TIME_RATIO_NOTE)
|
.comment(THREAD_RUN_TIME_RATIO_NOTE)
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static final ConfigEntry<Integer> numberOfFileHandlerThreads = new ConfigEntry.Builder<Integer>()
|
public static final ConfigEntry<Integer> numberOfFileHandlerThreads = new ConfigEntry.Builder<Integer>()
|
||||||
|
.setServersideShortName("numberOfFileHandlerThreads")
|
||||||
.setMinDefaultMax(1,
|
.setMinDefaultMax(1,
|
||||||
ThreadPresetConfigEventHandler.getFileHandlerDefaultThreadCount(),
|
ThreadPresetConfigEventHandler.getFileHandlerDefaultThreadCount(),
|
||||||
Runtime.getRuntime().availableProcessors())
|
Runtime.getRuntime().availableProcessors())
|
||||||
@@ -964,10 +1137,13 @@ public class Config
|
|||||||
+ "quickly flying through existing LODs. \n"
|
+ "quickly flying through existing LODs. \n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ THREAD_NOTE)
|
+ THREAD_NOTE)
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
public static final ConfigEntry<Double> runTimeRatioForFileHandlerThreads = new ConfigEntry.Builder<Double>()
|
public static final ConfigEntry<Double> runTimeRatioForFileHandlerThreads = new ConfigEntry.Builder<Double>()
|
||||||
|
.setServersideShortName("runTimeRatioForFileHandlerThreads")
|
||||||
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getFileHandlerDefaultRunTimeRatio(), 1.0)
|
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getFileHandlerDefaultRunTimeRatio(), 1.0)
|
||||||
.comment(THREAD_RUN_TIME_RATIO_NOTE)
|
.comment(THREAD_RUN_TIME_RATIO_NOTE)
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static final ConfigEntry<Integer> numberOfUpdatePropagatorThreads = new ConfigEntry.Builder<Integer>()
|
public static final ConfigEntry<Integer> numberOfUpdatePropagatorThreads = new ConfigEntry.Builder<Integer>()
|
||||||
@@ -996,6 +1172,7 @@ public class Config
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static final ConfigEntry<Integer> numberOfLodBuilderThreads = new ConfigEntry.Builder<Integer>()
|
public static final ConfigEntry<Integer> numberOfLodBuilderThreads = new ConfigEntry.Builder<Integer>()
|
||||||
|
.setServersideShortName("numberOfLodBuilderThreads")
|
||||||
.setMinDefaultMax(1,
|
.setMinDefaultMax(1,
|
||||||
ThreadPresetConfigEventHandler.getLodBuilderDefaultThreadCount(),
|
ThreadPresetConfigEventHandler.getLodBuilderDefaultThreadCount(),
|
||||||
Runtime.getRuntime().availableProcessors())
|
Runtime.getRuntime().availableProcessors())
|
||||||
@@ -1006,12 +1183,16 @@ public class Config
|
|||||||
+ "certain graphics settings are changed, and when moving around the world. \n"
|
+ "certain graphics settings are changed, and when moving around the world. \n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ THREAD_NOTE)
|
+ THREAD_NOTE)
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
public static final ConfigEntry<Double> runTimeRatioForLodBuilderThreads = new ConfigEntry.Builder<Double>()
|
public static final ConfigEntry<Double> runTimeRatioForLodBuilderThreads = new ConfigEntry.Builder<Double>()
|
||||||
|
.setServersideShortName("runTimeRatioForLodBuilderThreads")
|
||||||
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getLodBuilderDefaultRunTimeRatio(), 1.0)
|
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getLodBuilderDefaultRunTimeRatio(), 1.0)
|
||||||
.comment(THREAD_RUN_TIME_RATIO_NOTE)
|
.comment(THREAD_RUN_TIME_RATIO_NOTE)
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
public static final ConfigEntry<Boolean> enableLodBuilderThreadLimiting = new ConfigEntry.Builder<Boolean>()
|
public static final ConfigEntry<Boolean> enableLodBuilderThreadLimiting = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.setServersideShortName("enableLodBuilderThreadLimiting")
|
||||||
.set(true)
|
.set(true)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "Should only be disabled if deadlock occurs and LODs refuse to update. \n"
|
+ "Should only be disabled if deadlock occurs and LODs refuse to update. \n"
|
||||||
@@ -1019,55 +1200,29 @@ public class Config
|
|||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Note that if deadlock did occur restarting MC may be necessary to stop the locked threads. \n"
|
+ "Note that if deadlock did occur restarting MC may be necessary to stop the locked threads. \n"
|
||||||
+ "")
|
+ "")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
}
|
public static final ConfigEntry<Integer> numberOfNetworkCompressionThreads = new ConfigEntry.Builder<Integer>()
|
||||||
|
.setServersideShortName("numberOfNetworkCompressionThreads")
|
||||||
public static class GpuBuffers
|
.setMinDefaultMax(1,
|
||||||
{
|
ThreadPresetConfigEventHandler.getNetworkCompressionDefaultThreadCount(),
|
||||||
public static ConfigEntry<EDhApiGpuUploadMethod> gpuUploadMethod = new ConfigEntry.Builder<EDhApiGpuUploadMethod>()
|
Runtime.getRuntime().availableProcessors())
|
||||||
.set(EDhApiGpuUploadMethod.AUTO)
|
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "What method should be used to upload geometry to the GPU? \n"
|
+ "How many threads should be used when (de)compressing LODs \n"
|
||||||
|
+ "that are received/sent over the network?\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ EDhApiGpuUploadMethod.AUTO + ": Picks the best option based on the GPU you have. \n"
|
+ "This pool doesn't do anything in singleplayer or when connected \n"
|
||||||
|
+ "to a server that doesn't support DH networking. \n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ EDhApiGpuUploadMethod.BUFFER_STORAGE + ": Default if OpenGL 4.5 is supported. \n"
|
+ THREAD_NOTE)
|
||||||
+ " Fast rendering, no stuttering. \n"
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
+ "\n"
|
|
||||||
+ EDhApiGpuUploadMethod.SUB_DATA + ": Backup option for NVIDIA. \n"
|
|
||||||
+ " Fast rendering but may stutter when uploading. \n"
|
|
||||||
+ "\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"
|
|
||||||
+ 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"
|
|
||||||
+ "If you don't see any difference when changing these settings, \n"
|
|
||||||
+ "or the world looks corrupted: restart your game."
|
|
||||||
+ "")
|
|
||||||
.build();
|
.build();
|
||||||
|
public static final ConfigEntry<Double> runTimeRatioForNetworkCompressionThreads = new ConfigEntry.Builder<Double>()
|
||||||
public static ConfigEntry<Integer> gpuUploadPerMegabyteInMilliseconds = new ConfigEntry.Builder<Integer>()
|
.setServersideShortName("runTimeRatioForNetworkCompressionThreads")
|
||||||
.setMinDefaultMax(0, 0, 50)
|
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getNetworkCompressionDefaultRunTimeRatio(), 1.0)
|
||||||
.comment(""
|
.comment(THREAD_RUN_TIME_RATIO_NOTE)
|
||||||
+ "How long should a buffer wait per Megabyte of data uploaded? \n"
|
|
||||||
+ "Helpful resource for frame times: https://fpstoms.com \n"
|
|
||||||
+ "\n"
|
|
||||||
+ "Longer times may reduce stuttering but will make LODs \n"
|
|
||||||
+ "transition and load slower. Change this to [0] for no timeout. \n"
|
|
||||||
+ "\n"
|
|
||||||
+ "NOTE:\n"
|
|
||||||
+ "Before changing this config, try changing the \"GPU Upload method\" first. \n"
|
|
||||||
+ "")
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AutoUpdater
|
public static class AutoUpdater
|
||||||
@@ -1101,66 +1256,80 @@ public class Config
|
|||||||
// TODO add change all option
|
// TODO add change all option
|
||||||
// TODO default to error chat and info file
|
// TODO default to error chat and info file
|
||||||
public static ConfigEntry<EDhApiLoggerMode> logWorldGenEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
public static ConfigEntry<EDhApiLoggerMode> logWorldGenEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
||||||
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
|
.setServersideShortName("logWorldGenEvent")
|
||||||
|
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "If enabled, the mod will log information about the world generation process. \n"
|
+ "If enabled, the mod will log information about the world generation process. \n"
|
||||||
+ "This can be useful for debugging.")
|
+ "This can be useful for debugging.")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiLoggerMode> logWorldGenPerformance = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
public static ConfigEntry<EDhApiLoggerMode> logWorldGenPerformance = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
||||||
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
|
.setServersideShortName("logWorldGenPerformance")
|
||||||
|
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "If enabled, the mod will log performance about the world generation process. \n"
|
+ "If enabled, the mod will log performance about the world generation process. \n"
|
||||||
+ "This can be useful for debugging.")
|
+ "This can be useful for debugging.")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiLoggerMode> logWorldGenLoadEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
public static ConfigEntry<EDhApiLoggerMode> logWorldGenLoadEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
||||||
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
|
.setServersideShortName("logWorldGenPerformance")
|
||||||
|
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "If enabled, the mod will log information about the world generation process. \n"
|
+ "If enabled, the mod will log information about the world generation process. \n"
|
||||||
+ "This can be useful for debugging.")
|
+ "This can be useful for debugging.")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiLoggerMode> logLodBuilderEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
public static ConfigEntry<EDhApiLoggerMode> logLodBuilderEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
||||||
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
|
.setServersideShortName("logLodBuilderEvent")
|
||||||
|
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "If enabled, the mod will log information about the LOD generation process. \n"
|
+ "If enabled, the mod will log information about the LOD generation process. \n"
|
||||||
+ "This can be useful for debugging.")
|
+ "This can be useful for debugging.")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiLoggerMode> logRendererBufferEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
public static ConfigEntry<EDhApiLoggerMode> logRendererBufferEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
||||||
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
|
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "If enabled, the mod will log information about the renderer buffer process. \n"
|
+ "If enabled, the mod will log information about the renderer buffer process. \n"
|
||||||
+ "This can be useful for debugging.")
|
+ "This can be useful for debugging.")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiLoggerMode> logRendererGLEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
public static ConfigEntry<EDhApiLoggerMode> logRendererGLEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
||||||
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
|
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "If enabled, the mod will log information about the renderer OpenGL process. \n"
|
+ "If enabled, the mod will log information about the renderer OpenGL process. \n"
|
||||||
+ "This can be useful for debugging.")
|
+ "This can be useful for debugging.")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiLoggerMode> logFileReadWriteEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
public static ConfigEntry<EDhApiLoggerMode> logFileReadWriteEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
||||||
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
|
.setServersideShortName("logFileReadWriteEvent")
|
||||||
|
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "If enabled, the mod will log information about file read/write operations. \n"
|
+ "If enabled, the mod will log information about file read/write operations. \n"
|
||||||
+ "This can be useful for debugging.")
|
+ "This can be useful for debugging.")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiLoggerMode> logFileSubDimEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
public static ConfigEntry<EDhApiLoggerMode> logFileSubDimEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
||||||
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
|
.setServersideShortName("logFileSubDimEvent")
|
||||||
|
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "If enabled, the mod will log information about file sub-dimension operations. \n"
|
+ "If enabled, the mod will log information about file sub-dimension operations. \n"
|
||||||
+ "This can be useful for debugging.")
|
+ "This can be useful for debugging.")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
|
||||||
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
|
.setServersideShortName("logNetworkEvent")
|
||||||
|
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "If enabled, the mod will log information about network operations. \n"
|
+ "If enabled, the mod will log information about network operations. \n"
|
||||||
+ "This can be useful for debugging.")
|
+ "This can be useful for debugging.")
|
||||||
|
.setSide(EConfigEntryRelevantSide.BOTH)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
||||||
@@ -1171,6 +1340,20 @@ public class Config
|
|||||||
+ "memory allocated to run DH well.")
|
+ "memory allocated to run DH well.")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Boolean> showReplayWarningOnStartup = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.set(true)
|
||||||
|
.comment(""
|
||||||
|
+ "If enabled, a chat message will be displayed when a replay is started \n"
|
||||||
|
+ "giving some basic information about how DH will function.")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Boolean> showModCompatibilityWarningsOnStartup = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.set(true)
|
||||||
|
.comment(""
|
||||||
|
+ "If enabled, a chat message will be displayed when a potentially problematic \n"
|
||||||
|
+ "mod is installed alongside DH.")
|
||||||
|
.build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Debugging
|
public static class Debugging
|
||||||
@@ -1257,22 +1440,38 @@ public class Config
|
|||||||
public static ConfigEntry<Boolean> columnBuilderDebugEnable = new ConfigEntry.Builder<Boolean>()
|
public static ConfigEntry<Boolean> columnBuilderDebugEnable = new ConfigEntry.Builder<Boolean>()
|
||||||
.set(false)
|
.set(false)
|
||||||
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
|
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
|
||||||
.addListener(DebugColumnConfigEventHandler.INSTANCE)
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
public static ConfigEntry<Integer> columnBuilderDebugDetailLevel = new ConfigEntry.Builder<Integer>()
|
public static ConfigEntry<Integer> columnBuilderDebugDetailLevel = new ConfigEntry.Builder<Integer>()
|
||||||
.set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
|
.set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
|
||||||
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
|
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
|
||||||
.addListener(DebugColumnConfigEventHandler.INSTANCE)
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
public static ConfigEntry<Integer> columnBuilderDebugXPos = new ConfigEntry.Builder<Integer>()
|
public static ConfigEntry<Integer> columnBuilderDebugXPos = new ConfigEntry.Builder<Integer>()
|
||||||
.set(0)
|
.set(0)
|
||||||
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
|
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
|
||||||
.addListener(DebugColumnConfigEventHandler.INSTANCE)
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
public static ConfigEntry<Integer> columnBuilderDebugZPos = new ConfigEntry.Builder<Integer>()
|
public static ConfigEntry<Integer> columnBuilderDebugZPos = new ConfigEntry.Builder<Integer>()
|
||||||
.set(0)
|
.set(0)
|
||||||
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
|
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
|
||||||
.addListener(DebugColumnConfigEventHandler.INSTANCE)
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Integer> columnBuilderDebugXRow = new ConfigEntry.Builder<Integer>()
|
||||||
|
.set(-1)
|
||||||
|
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
|
||||||
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
|
.build();
|
||||||
|
public static ConfigEntry<Integer> columnBuilderDebugZRow = new ConfigEntry.Builder<Integer>()
|
||||||
|
.set(-1)
|
||||||
|
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
|
||||||
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
|
.build();
|
||||||
|
public static ConfigEntry<Integer> columnBuilderDebugColumnIndex = new ConfigEntry.Builder<Integer>()
|
||||||
|
.set(-1)
|
||||||
|
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
|
||||||
|
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
||||||
@@ -1542,4 +1741,17 @@ public class Config
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getDefaultLevelKeyPrefix()
|
||||||
|
{
|
||||||
|
IMinecraftSharedWrapper mcWrapper = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
|
||||||
|
if (!mcWrapper.isDedicatedServer() || mcWrapper.isWorldNew())
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "server" + ThreadLocalRandom.current().nextInt(1, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ import com.seibel.distanthorizons.api.DhApi;
|
|||||||
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
|
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
|
||||||
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||||
|
|
||||||
public class DebugColumnConfigEventHandler implements IConfigListener
|
public class ReloadLodsConfigEventHandler implements IConfigListener
|
||||||
{
|
{
|
||||||
public static DebugColumnConfigEventHandler INSTANCE = new DebugColumnConfigEventHandler();
|
public static ReloadLodsConfigEventHandler INSTANCE = new ReloadLodsConfigEventHandler();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigValueSet()
|
public void onConfigValueSet()
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.config.eventHandlers.presets;
|
package com.seibel.distanthorizons.core.config.eventHandlers.presets;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
|
||||||
import com.seibel.distanthorizons.core.config.ConfigBase;
|
import com.seibel.distanthorizons.core.config.ConfigBase;
|
||||||
import com.seibel.distanthorizons.core.config.ConfigEntryWithPresetOptions;
|
import com.seibel.distanthorizons.core.config.ConfigEntryWithPresetOptions;
|
||||||
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||||
@@ -30,6 +29,7 @@ import com.seibel.distanthorizons.coreapi.interfaces.config.IConfigEntry;
|
|||||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
|
|||||||
private static final Logger LOGGER = LogManager.getLogger();
|
private static final Logger LOGGER = LogManager.getLogger();
|
||||||
private static final long MS_DELAY_BEFORE_APPLYING_PRESET = 3_000;
|
private static final long MS_DELAY_BEFORE_APPLYING_PRESET = 3_000;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private static IConfigGui configGui = SingletonInjector.INSTANCE.get(IConfigGui.class);
|
private static IConfigGui configGui = SingletonInjector.INSTANCE.get(IConfigGui.class);
|
||||||
private static boolean guiListenersAdded = false;
|
private static boolean guiListenersAdded = false;
|
||||||
|
|
||||||
@@ -57,7 +58,11 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
|
|||||||
|
|
||||||
public AbstractPresetConfigEventHandler()
|
public AbstractPresetConfigEventHandler()
|
||||||
{
|
{
|
||||||
configGui.addOnScreenChangeListener(() -> this.onConfigUiClosed());
|
// don't update the UI when running on a server
|
||||||
|
if (configGui != null)
|
||||||
|
{
|
||||||
|
configGui.addOnScreenChangeListener(this::onConfigUiClosed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -168,7 +173,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
|
|||||||
|
|
||||||
if (newPreset != currentPreset)
|
if (newPreset != currentPreset)
|
||||||
{
|
{
|
||||||
this.getPresetConfigEntry().set(this.getCustomPresetEnum());
|
this.getPresetConfigEntry().set(newPreset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.apache.logging.log4j.Logger;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigEventHandler<EDhApiQualityPreset>
|
public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigEventHandler<EDhApiQualityPreset>
|
||||||
{
|
{
|
||||||
public static final RenderQualityPresetConfigEventHandler INSTANCE = new RenderQualityPresetConfigEventHandler();
|
public static final RenderQualityPresetConfigEventHandler INSTANCE = new RenderQualityPresetConfigEventHandler();
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import java.util.Arrays;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHandler<EDhApiThreadPreset>
|
public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHandler<EDhApiThreadPreset>
|
||||||
{
|
{
|
||||||
public static final ThreadPresetConfigEventHandler INSTANCE = new ThreadPresetConfigEventHandler();
|
public static final ThreadPresetConfigEventHandler INSTANCE = new ThreadPresetConfigEventHandler();
|
||||||
@@ -128,6 +129,28 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
|
|
||||||
|
public static int getNetworkCompressionDefaultThreadCount() { return getThreadCountByPercent(0.3); }
|
||||||
|
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Integer> networkCompressionThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfNetworkCompressionThreads,
|
||||||
|
new HashMap<EDhApiThreadPreset, Integer>()
|
||||||
|
{{
|
||||||
|
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 1);
|
||||||
|
this.put(EDhApiThreadPreset.LOW_IMPACT, getNetworkCompressionDefaultThreadCount());
|
||||||
|
this.put(EDhApiThreadPreset.BALANCED, getThreadCountByPercent(0.4));
|
||||||
|
this.put(EDhApiThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.6));
|
||||||
|
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(0.8));
|
||||||
|
}});
|
||||||
|
public static double getNetworkCompressionDefaultRunTimeRatio() { return 0.5; }
|
||||||
|
private final ConfigEntryWithPresetOptions<EDhApiThreadPreset, Double> networkCompressionRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForNetworkCompressionThreads,
|
||||||
|
new HashMap<EDhApiThreadPreset, Double>()
|
||||||
|
{{
|
||||||
|
this.put(EDhApiThreadPreset.MINIMAL_IMPACT, 0.25);
|
||||||
|
this.put(EDhApiThreadPreset.LOW_IMPACT, getNetworkCompressionDefaultRunTimeRatio());
|
||||||
|
this.put(EDhApiThreadPreset.BALANCED, 0.75);
|
||||||
|
this.put(EDhApiThreadPreset.AGGRESSIVE, 1.0);
|
||||||
|
this.put(EDhApiThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||||
|
}});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//==============//
|
||||||
// constructors //
|
// constructors //
|
||||||
@@ -149,6 +172,9 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
|
|||||||
this.configList.add(this.lodBuilderThreadCount);
|
this.configList.add(this.lodBuilderThreadCount);
|
||||||
this.configList.add(this.lodBuilderRunTime);
|
this.configList.add(this.lodBuilderRunTime);
|
||||||
|
|
||||||
|
this.configList.add(this.networkCompressionThreadCount);
|
||||||
|
this.configList.add(this.networkCompressionRunTime);
|
||||||
|
|
||||||
|
|
||||||
for (ConfigEntryWithPresetOptions<EDhApiThreadPreset, ?> config : this.configList)
|
for (ConfigEntryWithPresetOptions<EDhApiThreadPreset, ?> config : this.configList)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,17 +20,20 @@
|
|||||||
package com.seibel.distanthorizons.core.config.file;
|
package com.seibel.distanthorizons.core.config.file;
|
||||||
|
|
||||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||||
|
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryRelevantSide;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.config.ConfigBase;
|
import com.seibel.distanthorizons.core.config.ConfigBase;
|
||||||
import com.seibel.distanthorizons.core.config.types.AbstractConfigType;
|
import com.seibel.distanthorizons.core.config.types.AbstractConfigType;
|
||||||
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
|
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles reading and writing config files.
|
* Handles reading and writing config files.
|
||||||
@@ -40,58 +43,82 @@ import java.nio.file.Path;
|
|||||||
*/
|
*/
|
||||||
public class ConfigFileHandling
|
public class ConfigFileHandling
|
||||||
{
|
{
|
||||||
|
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
|
||||||
|
|
||||||
|
|
||||||
public final ConfigBase configBase;
|
public final ConfigBase configBase;
|
||||||
public final Path configPath;
|
public final Path configPath;
|
||||||
|
|
||||||
private final Logger LOGGER;
|
private final Logger logger;
|
||||||
|
|
||||||
/** This is the object for night-config */
|
/** This is the object for night-config */
|
||||||
private final CommentedFileConfig nightConfig;
|
private final CommentedFileConfig nightConfig;
|
||||||
|
|
||||||
|
/** prevents readers/writers from overlapping and causing the config file from being duplicated or corrupted */
|
||||||
|
private final ReentrantLock readWriteLock = new ReentrantLock();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
|
||||||
public ConfigFileHandling(ConfigBase configBase, Path configPath)
|
public ConfigFileHandling(ConfigBase configBase, Path configPath)
|
||||||
{
|
{
|
||||||
this.LOGGER = LogManager.getLogger(this.getClass().getSimpleName() + ", " + configBase.modID);
|
this.logger = LogManager.getLogger(this.getClass().getSimpleName() + ", " + configBase.modID);
|
||||||
this.configBase = configBase;
|
this.configBase = configBase;
|
||||||
this.configPath = configPath;
|
this.configPath = configPath;
|
||||||
|
|
||||||
this.nightConfig = CommentedFileConfig.builder(this.configPath.toFile()).build();
|
this.nightConfig = CommentedFileConfig.builder(this.configPath.toFile()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Saves the entire config to the file */
|
/** Saves the entire config to the file */
|
||||||
public void saveToFile()
|
public void saveToFile() { this.saveToFile(this.nightConfig); }
|
||||||
{
|
|
||||||
saveToFile(this.nightConfig);
|
|
||||||
}
|
|
||||||
/** Saves the entire config to the file */
|
/** Saves the entire config to the file */
|
||||||
public void saveToFile(CommentedFileConfig nightConfig)
|
public void saveToFile(CommentedFileConfig nightConfig)
|
||||||
{
|
{
|
||||||
if (!Files.exists(configPath)) // Try to check if the config exists
|
|
||||||
{
|
|
||||||
reCreateFile(configPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
loadNightConfig(nightConfig);
|
|
||||||
|
|
||||||
|
|
||||||
for (AbstractConfigType<?, ?> entry : this.configBase.entries)
|
|
||||||
{
|
|
||||||
if (ConfigEntry.class.isAssignableFrom(entry.getClass()))
|
|
||||||
{
|
|
||||||
createComment((ConfigEntry<?>) entry, nightConfig);
|
|
||||||
saveEntry((ConfigEntry<?>) entry, nightConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
nightConfig.save();
|
this.readWriteLock.lock();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (!Files.exists(this.configPath)) // Try to check if the config exists
|
||||||
|
{
|
||||||
|
reCreateFile(this.configPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.loadNightConfig(nightConfig);
|
||||||
|
|
||||||
|
|
||||||
|
for (AbstractConfigType<?, ?> entry : this.configBase.entries)
|
||||||
|
{
|
||||||
|
if (ConfigEntry.class.isAssignableFrom(entry.getClass()))
|
||||||
|
{
|
||||||
|
this.createComment((ConfigEntry<?>) entry, nightConfig);
|
||||||
|
this.saveEntry((ConfigEntry<?>) entry, nightConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
nightConfig.save();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// If it fails to save, crash game
|
||||||
|
SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).crashMinecraft("Failed to save config at [" + this.configPath + "]", e);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
finally
|
||||||
{
|
{
|
||||||
// If it fails to save, crash game
|
this.readWriteLock.unlock();
|
||||||
SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).crashMinecraft("Failed to save config at [" + configPath.toString() + "]", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,67 +129,77 @@ public class ConfigFileHandling
|
|||||||
*/
|
*/
|
||||||
public void loadFromFile()
|
public void loadFromFile()
|
||||||
{
|
{
|
||||||
int currentCfgVersion = configBase.configVersion;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Dont load the real `this.nightConfig`, instead create a tempoary one
|
this.readWriteLock.lock();
|
||||||
CommentedFileConfig tmpNightConfig = CommentedFileConfig.builder(this.configPath.toFile()).build();
|
|
||||||
tmpNightConfig.load();
|
int currentCfgVersion = this.configBase.configVersion;
|
||||||
// Attempt to get the version number
|
|
||||||
currentCfgVersion = (Integer) tmpNightConfig.get("_version");
|
|
||||||
tmpNightConfig.close();
|
|
||||||
} catch (Exception ignored) { }
|
|
||||||
|
|
||||||
if (currentCfgVersion == configBase.configVersion)
|
|
||||||
{}
|
|
||||||
else if (currentCfgVersion > configBase.configVersion)
|
|
||||||
{
|
|
||||||
LOGGER.warn("Found config version [" + String.valueOf(currentCfgVersion) + "] which is newer than current mods config version of [" + String.valueOf(configBase.configVersion) + "]. You may have downgraded the mod and items may have been moved, you have been warned");
|
|
||||||
}
|
|
||||||
else // if (currentCfgVersion < configBase.configVersion)
|
|
||||||
{
|
|
||||||
LOGGER.warn(configBase.modName +" config is of an older version, currently there is no config updater... so resetting config");
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Files.delete(configPath);
|
// Dont load the real `this.nightConfig`, instead create a tempoary one
|
||||||
|
CommentedFileConfig tmpNightConfig = CommentedFileConfig.builder(this.configPath.toFile()).build();
|
||||||
|
tmpNightConfig.load();
|
||||||
|
// Attempt to get the version number
|
||||||
|
currentCfgVersion = (Integer) tmpNightConfig.get("_version");
|
||||||
|
tmpNightConfig.close();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception ignored) { }
|
||||||
|
|
||||||
|
if (currentCfgVersion == this.configBase.configVersion)
|
||||||
{
|
{
|
||||||
LOGGER.error(e);
|
// handle normally
|
||||||
}
|
}
|
||||||
|
else if (currentCfgVersion > this.configBase.configVersion)
|
||||||
|
{
|
||||||
|
this.logger.warn("Found config version [" + currentCfgVersion + "] which is newer than current mods config version of [" + this.configBase.configVersion + "]. You may have downgraded the mod and items may have been moved, you have been warned");
|
||||||
|
}
|
||||||
|
else // if (currentCfgVersion < configBase.configVersion)
|
||||||
|
{
|
||||||
|
this.logger.warn(this.configBase.modName + " config is of an older version, currently there is no config updater... so resetting config");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Files.delete(this.configPath);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
this.logger.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadFromFile(this.nightConfig);
|
||||||
|
this.nightConfig.set("_version", this.configBase.configVersion);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.readWriteLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFromFile(nightConfig);
|
|
||||||
nightConfig.set("_version", configBase.configVersion);
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Loads the entire config from the file
|
* Loads the entire config from the file
|
||||||
*
|
*
|
||||||
* @apiNote This overwrites any value currently stored in the config
|
* @apiNote This overwrites any value currently stored in the config
|
||||||
*/
|
*/
|
||||||
public void loadFromFile(CommentedFileConfig nightConfig)
|
private void loadFromFile(CommentedFileConfig nightConfig)
|
||||||
{
|
{
|
||||||
// Attempt to load the file and if it fails then save config to file
|
// Attempt to load the file and if it fails then save config to file
|
||||||
if (Files.exists(configPath))
|
if (Files.exists(this.configPath))
|
||||||
{
|
{
|
||||||
loadNightConfig(nightConfig);
|
this.loadNightConfig(nightConfig);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
reCreateFile(configPath);
|
reCreateFile(this.configPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Load all the entries
|
// Load all the entries
|
||||||
for (AbstractConfigType<?, ?> entry : this.configBase.entries)
|
for (AbstractConfigType<?, ?> entry : this.configBase.entries)
|
||||||
{
|
{
|
||||||
if (
|
if (ConfigEntry.class.isAssignableFrom(entry.getClass())
|
||||||
ConfigEntry.class.isAssignableFrom(entry.getClass()) &&
|
&& entry.getAppearance().showInFile)
|
||||||
entry.getAppearance().showInFile
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
createComment((ConfigEntry<?>) entry, nightConfig);
|
this.createComment((ConfigEntry<?>) entry, nightConfig);
|
||||||
loadEntry((ConfigEntry<?>) entry, nightConfig);
|
this.loadEntry((ConfigEntry<?>) entry, nightConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +211,7 @@ public class ConfigFileHandling
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
// If it fails to save, crash game
|
// If it fails to save, crash game
|
||||||
SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).crashMinecraft("Failed to save config at [" + configPath.toString() + "]", e);
|
SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).crashMinecraft("Failed to save config at [" + this.configPath + "]", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,24 +221,34 @@ public class ConfigFileHandling
|
|||||||
// Save an entry when only given the entry
|
// Save an entry when only given the entry
|
||||||
public void saveEntry(ConfigEntry<?> entry)
|
public void saveEntry(ConfigEntry<?> entry)
|
||||||
{
|
{
|
||||||
saveEntry(entry, nightConfig);
|
this.saveEntry(entry, this.nightConfig);
|
||||||
nightConfig.save();
|
this.nightConfig.save();
|
||||||
}
|
}
|
||||||
/** Save an entry */
|
/** Save an entry */
|
||||||
public void saveEntry(ConfigEntry<?> entry, CommentedFileConfig workConfig)
|
public void saveEntry(ConfigEntry<?> entry, CommentedFileConfig workConfig)
|
||||||
{
|
{
|
||||||
if (!entry.getAppearance().showInFile) return;
|
if (!entry.getAppearance().showInFile)
|
||||||
if (entry.getTrueValue() == null)
|
{
|
||||||
throw new IllegalArgumentException("Entry [" + entry.getNameWCategory() + "] is null, this may be a problem with [" + configBase.modName + "]. Please contact the authors");
|
return;
|
||||||
|
}
|
||||||
|
else if ((entry.getRelevantSide() == EConfigEntryRelevantSide.CLIENT && MC_SHARED.isDedicatedServer())
|
||||||
|
|| (entry.getRelevantSide() == EConfigEntryRelevantSide.SERVER && !MC_SHARED.isDedicatedServer()))
|
||||||
|
{
|
||||||
|
// don't save server/client specific configs on the opposite
|
||||||
|
// (this keeps the config file clean of unnecessary items)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (entry.getTrueValue() == null)
|
||||||
|
{
|
||||||
|
// TODO when can this happen?
|
||||||
|
throw new IllegalArgumentException("Entry [" + entry.getNameWCategory() + "] is null, this may be a problem with [" + this.configBase.modName + "]. Please contact the authors.");
|
||||||
|
}
|
||||||
|
|
||||||
workConfig.set(entry.getNameWCategory(), ConfigTypeConverters.attemptToConvertToString(entry.getType(), entry.getTrueValue()));
|
workConfig.set(entry.getNameWCategory(), ConfigTypeConverters.attemptToConvertToString(entry.getType(), entry.getTrueValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Loads an entry when only given the entry */
|
/** Loads an entry when only given the entry */
|
||||||
public void loadEntry(ConfigEntry<?> entry)
|
public void loadEntry(ConfigEntry<?> entry) { this.loadEntry(entry, this.nightConfig); }
|
||||||
{
|
|
||||||
loadEntry(entry, nightConfig);
|
|
||||||
}
|
|
||||||
/** Loads an entry */
|
/** Loads an entry */
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> void loadEntry(ConfigEntry<T> entry, CommentedFileConfig nightConfig)
|
public <T> void loadEntry(ConfigEntry<T> entry, CommentedFileConfig nightConfig)
|
||||||
@@ -211,7 +258,7 @@ public class ConfigFileHandling
|
|||||||
|
|
||||||
if (!nightConfig.contains(entry.getNameWCategory()))
|
if (!nightConfig.contains(entry.getNameWCategory()))
|
||||||
{
|
{
|
||||||
saveEntry(entry, nightConfig);
|
this.saveEntry(entry, nightConfig);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +277,7 @@ public class ConfigFileHandling
|
|||||||
Object convertedValue = ConfigTypeConverters.attemptToConvertFromString(expectedValueClass, value);
|
Object convertedValue = ConfigTypeConverters.attemptToConvertFromString(expectedValueClass, value);
|
||||||
if (!convertedValue.getClass().equals(expectedValueClass))
|
if (!convertedValue.getClass().equals(expectedValueClass))
|
||||||
{
|
{
|
||||||
LOGGER.error("Unable to convert config value ["+value+"] from ["+(value != null ? value.getClass() : "NULL")+"] to ["+expectedValueClass+"] for config ["+entry.name+"], " +
|
this.logger.error("Unable to convert config value ["+value+"] from ["+(value != null ? value.getClass() : "NULL")+"] to ["+expectedValueClass+"] for config ["+entry.name+"], " +
|
||||||
"the default config value will be used instead ["+entry.getDefaultValue()+"]. " +
|
"the default config value will be used instead ["+entry.getDefaultValue()+"]. " +
|
||||||
"Make sure a converter is defined in ["+ConfigTypeConverters.class.getSimpleName()+"].");
|
"Make sure a converter is defined in ["+ConfigTypeConverters.class.getSimpleName()+"].");
|
||||||
convertedValue = entry.getDefaultValue();
|
convertedValue = entry.getDefaultValue();
|
||||||
@@ -239,31 +286,39 @@ public class ConfigFileHandling
|
|||||||
|
|
||||||
if (entry.getTrueValue() == null)
|
if (entry.getTrueValue() == null)
|
||||||
{
|
{
|
||||||
LOGGER.warn("Entry [" + entry.getNameWCategory() + "] returned as null from the config. Using default value.");
|
this.logger.warn("Entry [" + entry.getNameWCategory() + "] returned as null from the config. Using default value.");
|
||||||
entry.pureSet(entry.getDefaultValue());
|
entry.pureSet(entry.getDefaultValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
// e.printStackTrace();
|
// e.printStackTrace();
|
||||||
LOGGER.warn("Entry [" + entry.getNameWCategory() + "] had an invalid value when loading the config. Using default value.");
|
this.logger.warn("Entry [" + entry.getNameWCategory() + "] had an invalid value when loading the config. Using default value.");
|
||||||
entry.pureSet(entry.getDefaultValue());
|
entry.pureSet(entry.getDefaultValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates the comment for an entry when only given the entry
|
// Creates the comment for an entry when only given the entry
|
||||||
public void createComment(ConfigEntry<?> entry)
|
public void createComment(ConfigEntry<?> entry) { this.createComment(entry, this.nightConfig); }
|
||||||
{
|
|
||||||
createComment(entry, nightConfig);
|
|
||||||
}
|
|
||||||
// Creates a comment for an entry
|
// Creates a comment for an entry
|
||||||
public void createComment(ConfigEntry<?> entry, CommentedFileConfig nightConfig)
|
public void createComment(ConfigEntry<?> entry, CommentedFileConfig nightConfig)
|
||||||
{
|
{
|
||||||
if (
|
if (!entry.getAppearance().showInFile
|
||||||
!entry.getAppearance().showInFile ||
|
|| entry.getComment() == null)
|
||||||
entry.getComment() == null
|
{
|
||||||
)
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if ((entry.getRelevantSide() == EConfigEntryRelevantSide.CLIENT && MC_SHARED.isDedicatedServer())
|
||||||
|
|| (entry.getRelevantSide() == EConfigEntryRelevantSide.SERVER && !MC_SHARED.isDedicatedServer()))
|
||||||
|
{
|
||||||
|
// don't save server/client specific configs on the opposite
|
||||||
|
// (this keeps the config file clean of unnecessary items)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
String comment = entry.getComment().replaceAll("\n", "\n ").trim();
|
String comment = entry.getComment().replaceAll("\n", "\n ").trim();
|
||||||
// the new line makes it easier to read and separate configs
|
// the new line makes it easier to read and separate configs
|
||||||
@@ -302,7 +357,7 @@ public class ConfigFileHandling
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LOGGER.warn("Loading file failed because of this expectation:\n" + e);
|
this.logger.warn("Loading file failed because of this expectation:\n" + e);
|
||||||
|
|
||||||
reCreateFile(this.configPath);
|
reCreateFile(this.configPath);
|
||||||
|
|
||||||
@@ -312,7 +367,7 @@ public class ConfigFileHandling
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
System.out.println("Creating file failed");
|
System.out.println("Creating file failed");
|
||||||
LOGGER.error(ex);
|
this.logger.error(ex);
|
||||||
SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).crashMinecraft("Loading file and resetting config file failed at path [" + configPath + "]. Please check the file is ok and you have the permissions", ex);
|
SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).crashMinecraft("Loading file and resetting config file failed at path [" + configPath + "]. Please check the file is ok and you have the permissions", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
|
|||||||
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||||
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance;
|
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance;
|
||||||
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryPerformance;
|
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryPerformance;
|
||||||
|
import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryRelevantSide;
|
||||||
import com.seibel.distanthorizons.coreapi.interfaces.config.IConfigEntry;
|
import com.seibel.distanthorizons.coreapi.interfaces.config.IConfigEntry;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -44,6 +45,10 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
|
|||||||
private T min;
|
private T min;
|
||||||
private T max;
|
private T max;
|
||||||
private final ArrayList<IConfigListener> listenerList;
|
private final ArrayList<IConfigListener> listenerList;
|
||||||
|
private final String serversideShortName;
|
||||||
|
|
||||||
|
private final EConfigEntryPerformance performance;
|
||||||
|
private final EConfigEntryRelevantSide relevantSide;
|
||||||
|
|
||||||
// API control //
|
// API control //
|
||||||
/**
|
/**
|
||||||
@@ -53,19 +58,25 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
|
|||||||
public final boolean allowApiOverride;
|
public final boolean allowApiOverride;
|
||||||
private T apiValue;
|
private T apiValue;
|
||||||
|
|
||||||
private final EConfigEntryPerformance performance;
|
|
||||||
|
|
||||||
|
|
||||||
/** Creates the entry */
|
/** Creates the entry */
|
||||||
private ConfigEntry(EConfigEntryAppearance appearance, T value, String comment, T min, T max, boolean allowApiOverride, EConfigEntryPerformance performance, ArrayList<IConfigListener> listenerList)
|
private ConfigEntry(
|
||||||
|
EConfigEntryAppearance appearance,
|
||||||
|
T value, String comment, T min, T max,
|
||||||
|
String serversideShortName, boolean allowApiOverride,
|
||||||
|
EConfigEntryPerformance performance, EConfigEntryRelevantSide relevantSide,
|
||||||
|
ArrayList<IConfigListener> listenerList)
|
||||||
{
|
{
|
||||||
super(appearance, value);
|
super(appearance, value);
|
||||||
|
|
||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
this.min = min;
|
this.min = min;
|
||||||
this.max = max;
|
this.max = max;
|
||||||
|
this.serversideShortName = serversideShortName;
|
||||||
this.allowApiOverride = allowApiOverride;
|
this.allowApiOverride = allowApiOverride;
|
||||||
this.performance = performance;
|
this.performance = performance;
|
||||||
|
this.relevantSide = relevantSide;
|
||||||
this.listenerList = listenerList;
|
this.listenerList = listenerList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +190,9 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
|
|||||||
if (validness == 1) this.value = (T) NumberUtil.getMaximum(this.value.getClass());
|
if (validness == 1) this.value = (T) NumberUtil.getMaximum(this.value.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO is this for command line use?
|
||||||
|
public String getServersideShortName() { return this.serversideShortName; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getComment() { return this.comment; }
|
public String getComment() { return this.comment; }
|
||||||
@Override
|
@Override
|
||||||
@@ -186,6 +200,8 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
|
|||||||
|
|
||||||
/** Gets the performance impact of an option */
|
/** Gets the performance impact of an option */
|
||||||
public EConfigEntryPerformance getPerformance() { return this.performance; }
|
public EConfigEntryPerformance getPerformance() { return this.performance; }
|
||||||
|
/** Gets whether this config should apply to the client, server, or both */
|
||||||
|
public EConfigEntryRelevantSide getRelevantSide() { return this.relevantSide; }
|
||||||
|
|
||||||
/** Fired whenever the config value changes to a new value. */
|
/** Fired whenever the config value changes to a new value. */
|
||||||
public void addValueChangeListener(Consumer<T> onValueChangeFunc)
|
public void addValueChangeListener(Consumer<T> onValueChangeFunc)
|
||||||
@@ -256,20 +272,38 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
|
|||||||
public byte isValid(T value, T min, T max)
|
public byte isValid(T value, T min, T max)
|
||||||
{
|
{
|
||||||
if (this.configBase.disableMinMax)
|
if (this.configBase.disableMinMax)
|
||||||
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
if (value == null || this.value == null || value.getClass() != this.value.getClass()) // If the 2 variables aren't the same type then it will be invalid
|
else if (min == null && max == null)
|
||||||
|
{
|
||||||
|
// no validation is needed for this field
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (value == null || this.value == null
|
||||||
|
|| value.getClass() != this.value.getClass())
|
||||||
|
{
|
||||||
|
// If the 2 variables aren't the same type then it will be invalid
|
||||||
return 2;
|
return 2;
|
||||||
if (Number.class.isAssignableFrom(value.getClass()))
|
}
|
||||||
{ // Only check min max if it is a number
|
else if (Number.class.isAssignableFrom(value.getClass()))
|
||||||
|
{
|
||||||
|
// Only check min max if it is a number
|
||||||
if (max != null && NumberUtil.greaterThan((Number) value, (Number) max))
|
if (max != null && NumberUtil.greaterThan((Number) value, (Number) max))
|
||||||
|
{
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
if (min != null && NumberUtil.lessThan((Number) value, (Number) min))
|
if (min != null && NumberUtil.lessThan((Number) value, (Number) min))
|
||||||
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return 0;
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This should normally not be called since set() automatically calls this */
|
/** This should normally not be called since set() automatically calls this */
|
||||||
@@ -297,8 +331,10 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
|
|||||||
private String tmpComment = null;
|
private String tmpComment = null;
|
||||||
private T tmpMin = null;
|
private T tmpMin = null;
|
||||||
private T tmpMax = null;
|
private T tmpMax = null;
|
||||||
|
protected String tmpServersideShortName = null;
|
||||||
private boolean tmpUseApiOverwrite = true;
|
private boolean tmpUseApiOverwrite = true;
|
||||||
private EConfigEntryPerformance tmpPerformance = EConfigEntryPerformance.DONT_SHOW;
|
private EConfigEntryPerformance tmpPerformance = EConfigEntryPerformance.DONT_SHOW;
|
||||||
|
private EConfigEntryRelevantSide tmpRelevantSide = EConfigEntryRelevantSide.CLIENT;
|
||||||
protected ArrayList<IConfigListener> tmpIConfigListener = new ArrayList<>();
|
protected ArrayList<IConfigListener> tmpIConfigListener = new ArrayList<>();
|
||||||
|
|
||||||
public Builder<T> comment(String newComment)
|
public Builder<T> comment(String newComment)
|
||||||
@@ -334,6 +370,12 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder<T> setServersideShortName(String name)
|
||||||
|
{
|
||||||
|
this.tmpServersideShortName = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder<T> setUseApiOverwrite(boolean newUseApiOverwrite)
|
public Builder<T> setUseApiOverwrite(boolean newUseApiOverwrite)
|
||||||
{
|
{
|
||||||
this.tmpUseApiOverwrite = newUseApiOverwrite;
|
this.tmpUseApiOverwrite = newUseApiOverwrite;
|
||||||
@@ -346,6 +388,12 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder<T> setSide(EConfigEntryRelevantSide relevantSide)
|
||||||
|
{
|
||||||
|
this.tmpRelevantSide = relevantSide;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public Builder<T> replaceListeners(ArrayList<IConfigListener> newConfigListener)
|
public Builder<T> replaceListeners(ArrayList<IConfigListener> newConfigListener)
|
||||||
@@ -376,7 +424,11 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
|
|||||||
|
|
||||||
public ConfigEntry<T> build()
|
public ConfigEntry<T> build()
|
||||||
{
|
{
|
||||||
return new ConfigEntry<>(this.tmpAppearance, this.tmpValue, this.tmpComment, this.tmpMin, this.tmpMax, this.tmpUseApiOverwrite, this.tmpPerformance, this.tmpIConfigListener);
|
return new ConfigEntry<>(
|
||||||
|
this.tmpAppearance,
|
||||||
|
this.tmpValue, this.tmpComment, this.tmpMin, this.tmpMax,
|
||||||
|
this.tmpServersideShortName, this.tmpUseApiOverwrite,
|
||||||
|
this.tmpPerformance, this.tmpRelevantSide, this.tmpIConfigListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,20 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.seibel.distanthorizons.core.network.protocol;
|
package com.seibel.distanthorizons.core.config.types.enums;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CLIENT, <br>
|
* BOTH, <br>
|
||||||
* SERVER, <br>
|
* SERVER, <br>
|
||||||
|
* CLIENT, <br>
|
||||||
|
*
|
||||||
|
* Defines whether this config entry is for the client, server, or both.
|
||||||
|
* TODO this needs a better name.
|
||||||
*/
|
*/
|
||||||
public enum EMessageHandlerSide
|
public enum EConfigEntryRelevantSide
|
||||||
{
|
{
|
||||||
|
BOTH,
|
||||||
|
SERVER,
|
||||||
CLIENT,
|
CLIENT,
|
||||||
SERVER
|
|
||||||
}
|
}
|
||||||
@@ -62,6 +62,8 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
|
|||||||
|
|
||||||
/** measured in data columns */
|
/** measured in data columns */
|
||||||
public static final int WIDTH = 64;
|
public static final int WIDTH = 64;
|
||||||
|
/** how many chunks wide this datasource is. */
|
||||||
|
public static final int NUMB_OF_CHUNKS_WIDE = WIDTH / LodUtil.CHUNK_WIDTH;
|
||||||
|
|
||||||
public static final byte DATA_FORMAT_VERSION = 1;
|
public static final byte DATA_FORMAT_VERSION = 1;
|
||||||
|
|
||||||
@@ -88,12 +90,14 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* stores how far each column has been generated should start with {@link EDhApiWorldGenerationStep#EMPTY}
|
* stores how far each column has been generated should start with {@link EDhApiWorldGenerationStep#EMPTY}
|
||||||
* @see EDhApiWorldGenerationStep
|
*
|
||||||
|
* @see EDhApiWorldGenerationStep
|
||||||
*/
|
*/
|
||||||
public byte[] columnGenerationSteps;
|
public byte[] columnGenerationSteps;
|
||||||
/**
|
/**
|
||||||
* stores what world compression was used for each column.
|
* stores what world compression was used for each column.
|
||||||
* @see EDhApiWorldCompressionMode
|
*
|
||||||
|
* @see EDhApiWorldCompressionMode
|
||||||
*/
|
*/
|
||||||
public byte[] columnWorldCompressionMode;
|
public byte[] columnWorldCompressionMode;
|
||||||
|
|
||||||
@@ -142,7 +146,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
|
|||||||
this.columnWorldCompressionMode = columnWorldCompressionMode;
|
this.columnWorldCompressionMode = columnWorldCompressionMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FullDataSourceV2 createFromChunk(IChunkWrapper chunkWrapper) { return LodDataBuilder.createGeneratedDataSource(chunkWrapper); }
|
public static FullDataSourceV2 createFromChunk(IChunkWrapper chunkWrapper) { return LodDataBuilder.createFromChunk(chunkWrapper); }
|
||||||
|
|
||||||
public static FullDataSourceV2 createFromLegacyDataSourceV1(FullDataSourceV1 legacyData)
|
public static FullDataSourceV2 createFromLegacyDataSourceV1(FullDataSourceV1 legacyData)
|
||||||
{
|
{
|
||||||
@@ -178,7 +182,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
|
|||||||
|
|
||||||
boolean isAir = legacyData.mapping.getBlockStateWrapper(FullDataPointUtil.getId(dataPoint)).isAir();
|
boolean isAir = legacyData.mapping.getBlockStateWrapper(FullDataPointUtil.getId(dataPoint)).isAir();
|
||||||
byte blockLight = (byte) FullDataPointUtil.getBlockLight(dataPoint);
|
byte blockLight = (byte) FullDataPointUtil.getBlockLight(dataPoint);
|
||||||
|
|
||||||
if (isAir)
|
if (isAir)
|
||||||
{
|
{
|
||||||
// air shouldn't have any light, otherwise down sampling will look weird
|
// air shouldn't have any light, otherwise down sampling will look weird
|
||||||
@@ -377,7 +381,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
|
|||||||
for (int x = 0; x < WIDTH; x += 2)
|
for (int x = 0; x < WIDTH; x += 2)
|
||||||
{
|
{
|
||||||
for (int z = 0; z < WIDTH; z += 2)
|
for (int z = 0; z < WIDTH; z += 2)
|
||||||
{
|
{
|
||||||
int recipientX = (x / 2) + recipientOffsetX;
|
int recipientX = (x / 2) + recipientOffsetX;
|
||||||
int recipientZ = (z / 2) + recipientOffsetZ;
|
int recipientZ = (z / 2) + recipientOffsetZ;
|
||||||
int recipientIndex = relativePosToIndex(recipientX, recipientZ);
|
int recipientIndex = relativePosToIndex(recipientX, recipientZ);
|
||||||
@@ -610,7 +614,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
|
|||||||
int id = determineMostValueInColumnSlice(mergeIds, inputDataSource.mapping);
|
int id = determineMostValueInColumnSlice(mergeIds, inputDataSource.mapping);
|
||||||
byte blockLight = (byte) determineAverageValueInColumnSlice(mergeBlockLights);
|
byte blockLight = (byte) determineAverageValueInColumnSlice(mergeBlockLights);
|
||||||
byte skyLight = (byte) determineAverageValueInColumnSlice(mergeSkyLights);
|
byte skyLight = (byte) determineAverageValueInColumnSlice(mergeSkyLights);
|
||||||
|
|
||||||
// if this slice is different then the last one, create a new one
|
// if this slice is different then the last one, create a new one
|
||||||
if (id != lastId
|
if (id != lastId
|
||||||
// block and sky light might not be necessary
|
// block and sky light might not be necessary
|
||||||
@@ -696,7 +700,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
|
|||||||
{
|
{
|
||||||
LodUtil.assertTrue(sliceArray.length == 4, "Column Slice should only contain 4 values.");
|
LodUtil.assertTrue(sliceArray.length == 4, "Column Slice should only contain 4 values.");
|
||||||
}
|
}
|
||||||
|
|
||||||
int value0 = sliceArray[0];
|
int value0 = sliceArray[0];
|
||||||
int count0 = 0;
|
int count0 = 0;
|
||||||
int value1 = sliceArray[1];
|
int value1 = sliceArray[1];
|
||||||
@@ -717,26 +721,42 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (value == value0)
|
if (value == value0)
|
||||||
|
{
|
||||||
count0++;
|
count0++;
|
||||||
|
}
|
||||||
else if (value == value1)
|
else if (value == value1)
|
||||||
|
{
|
||||||
count1++;
|
count1++;
|
||||||
|
}
|
||||||
else if (value == value2)
|
else if (value == value2)
|
||||||
|
{
|
||||||
count2++;
|
count2++;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
count3++;
|
count3++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the most common occurance
|
// return the most common occurance
|
||||||
int maxCount = Math.max(count0, Math.max(count1, Math.max(count2, count3)));
|
int maxCount = Math.max(count0, Math.max(count1, Math.max(count2, count3)));
|
||||||
if (maxCount == count0)
|
if (maxCount == count0)
|
||||||
// if the max count is 1 then we'll just go with the first column
|
// if the max count is 1 then we'll just go with the first column
|
||||||
|
{
|
||||||
return value0;
|
return value0;
|
||||||
|
}
|
||||||
else if (maxCount == count1)
|
else if (maxCount == count1)
|
||||||
|
{
|
||||||
return value1;
|
return value1;
|
||||||
|
}
|
||||||
else if (maxCount == count2)
|
else if (maxCount == count2)
|
||||||
|
{
|
||||||
return value2;
|
return value2;
|
||||||
else
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return value3;
|
return value3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private static int determineAverageValueInColumnSlice(int[] sliceArray)
|
private static int determineAverageValueInColumnSlice(int[] sliceArray)
|
||||||
{
|
{
|
||||||
@@ -874,8 +894,8 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
|
|||||||
{
|
{
|
||||||
int index = relativePosToIndex(relX, relZ);
|
int index = relativePosToIndex(relX, relZ);
|
||||||
this.dataPoints[index] = longArray;
|
this.dataPoints[index] = longArray;
|
||||||
this.columnGenerationSteps[index] = worldGenStep.value;
|
this.columnGenerationSteps[index] = worldGenStep.value;
|
||||||
this.columnWorldCompressionMode[index] = worldCompressionMode.value;
|
this.columnWorldCompressionMode[index] = worldCompressionMode.value;
|
||||||
|
|
||||||
|
|
||||||
if (RUN_UPDATE_DEV_VALIDATION)
|
if (RUN_UPDATE_DEV_VALIDATION)
|
||||||
@@ -903,6 +923,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
|
|||||||
@Override
|
@Override
|
||||||
public String toString() { return DhSectionPos.toString(this.pos); }
|
public String toString() { return DhSectionPos.toString(this.pos); }
|
||||||
|
|
||||||
|
/** Only includes the base data in this object, not the mapping */
|
||||||
@Override
|
@Override
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
{
|
{
|
||||||
@@ -940,7 +961,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
|
|||||||
// the positions are the same, use the hash as a quick method
|
// the positions are the same, use the hash as a quick method
|
||||||
// to determine if the data inside is the same.
|
// to determine if the data inside is the same.
|
||||||
// Note: this isn't perfect, but should work well enough for our use case.
|
// Note: this isn't perfect, but should work well enough for our use case.
|
||||||
return other.hashCode() == this.hashCode();
|
return other.hashCode() == this.hashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRender
|
|||||||
import com.seibel.distanthorizons.core.file.DataSourcePool;
|
import com.seibel.distanthorizons.core.file.DataSourcePool;
|
||||||
import com.seibel.distanthorizons.core.file.IDataSource;
|
import com.seibel.distanthorizons.core.file.IDataSource;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
|
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
|
||||||
@@ -182,7 +182,7 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
|
|||||||
EDhApiWorldGenerationStep worldGenStep = inputFullDataSource.getWorldGenStepAtRelativePos(x, z);
|
EDhApiWorldGenerationStep worldGenStep = inputFullDataSource.getWorldGenStepAtRelativePos(x, z);
|
||||||
if (dataColumn != null && worldGenStep != EDhApiWorldGenerationStep.EMPTY)
|
if (dataColumn != null && worldGenStep != EDhApiWorldGenerationStep.EMPTY)
|
||||||
{
|
{
|
||||||
FullDataToRenderDataTransformer.convertColumnData(
|
FullDataToRenderDataTransformer.updateOrReplaceRenderDataViewColumnWithFullDataColumn(
|
||||||
level, inputFullDataSource.mapping,
|
level, inputFullDataSource.mapping,
|
||||||
minBlockPos.x + x,
|
minBlockPos.x + x,
|
||||||
minBlockPos.z + z,
|
minBlockPos.z + z,
|
||||||
@@ -288,7 +288,7 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
|
|||||||
String SUBDATA_DELIMITER = ",";
|
String SUBDATA_DELIMITER = ",";
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
stringBuilder.append(this.pos);
|
stringBuilder.append(DhSectionPos.toString(this.pos));
|
||||||
stringBuilder.append(LINE_DELIMITER);
|
stringBuilder.append(LINE_DELIMITER);
|
||||||
|
|
||||||
int size = 1;
|
int size = 1;
|
||||||
|
|||||||
@@ -19,29 +19,60 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
|
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||||
|
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.util.ColorUtil;
|
import com.seibel.distanthorizons.core.util.ColorUtil;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
|
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
|
||||||
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class ColumnBox
|
public class ColumnBox
|
||||||
{
|
{
|
||||||
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if the skylight has this value that means
|
||||||
|
* no data is expected
|
||||||
|
*/
|
||||||
|
private static final byte SKYLIGHT_EMPTY = -1;
|
||||||
|
/**
|
||||||
|
* if the skylight has this value that means
|
||||||
|
* that block position is covered/occuled by an adjacent block/column.
|
||||||
|
*/
|
||||||
|
private static final byte SKYLIGHT_COVERED = -2;
|
||||||
|
|
||||||
|
private static final ThreadLocal<byte[]> THREAD_LOCAL_SKY_LIGHT_ARRAY = ThreadLocal.withInitial(() ->
|
||||||
|
{
|
||||||
|
byte[] array = new byte[RenderDataPointUtil.MAX_WORLD_Y_SIZE];
|
||||||
|
Arrays.fill(array, SKYLIGHT_EMPTY);
|
||||||
|
return array;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=========//
|
||||||
|
// builder //
|
||||||
|
//=========//
|
||||||
|
|
||||||
public static void addBoxQuadsToBuilder(
|
public static void addBoxQuadsToBuilder(
|
||||||
LodQuadBuilder builder,
|
LodQuadBuilder builder, IDhClientLevel clientLevel,
|
||||||
short xSize, short ySize, short zSize,
|
short xSize, short ySize, short zSize,
|
||||||
short x, short minY, short z,
|
short x, short minY, short z,
|
||||||
int color, byte irisBlockMaterialId, byte skyLight, byte blockLight,
|
int color, byte irisBlockMaterialId, byte skyLight, byte blockLight,
|
||||||
long topData, long bottomData, ColumnArrayView[][] adjData)
|
long topData, long bottomData, ColumnArrayView[] adjData, boolean[] isAdjDataSameDetailLevel)
|
||||||
{
|
{
|
||||||
|
//================//
|
||||||
|
// variable setup //
|
||||||
|
//================//
|
||||||
|
|
||||||
short maxX = (short) (x + xSize);
|
short maxX = (short) (x + xSize);
|
||||||
short maxY = (short) (minY + ySize);
|
short maxY = (short) (minY + ySize);
|
||||||
short maxZ = (short) (z + zSize);
|
short maxZ = (short) (z + zSize);
|
||||||
@@ -53,33 +84,24 @@ public class ColumnBox
|
|||||||
boolean isTopTransparent = RenderDataPointUtil.getAlpha(topData) < 255 && LodRenderer.transparencyEnabled;
|
boolean isTopTransparent = RenderDataPointUtil.getAlpha(topData) < 255 && LodRenderer.transparencyEnabled;
|
||||||
boolean isBottomTransparent = RenderDataPointUtil.getAlpha(bottomData) < 255 && LodRenderer.transparencyEnabled;
|
boolean isBottomTransparent = RenderDataPointUtil.getAlpha(bottomData) < 255 && LodRenderer.transparencyEnabled;
|
||||||
|
|
||||||
|
// defaulting to a value far below what we can normally render means we
|
||||||
|
// don't need to have an additional "is cave culling enabled" check
|
||||||
|
int caveCullingMaxY = Integer.MIN_VALUE;
|
||||||
|
if (Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get())
|
||||||
|
{
|
||||||
|
caveCullingMaxY = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get() - clientLevel.getMinY();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// if there isn't any data below this LOD, make this LOD's color opaque to prevent seeing void through transparent blocks
|
// if there isn't any data below this LOD, make this LOD's color opaque to prevent seeing void through transparent blocks
|
||||||
// Note: this LOD should still be considered transparent for this method's checks, otherwise rendering bugs may occur
|
// Note: this LOD should still be considered transparent for this method's checks, otherwise rendering bugs may occur
|
||||||
// FIXME this transparency change should be applied before this point since this could affect other areas
|
|
||||||
// This may also be better than handling the LOD as transparent, but that is TBD
|
|
||||||
if (!RenderDataPointUtil.doesDataPointExist(bottomData))
|
if (!RenderDataPointUtil.doesDataPointExist(bottomData))
|
||||||
{
|
{
|
||||||
color = ColorUtil.setAlpha(color, 255);
|
color = ColorUtil.setAlpha(color, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
maxY = builder.skyLightCullingBelow;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// fake ocean transparency
|
// fake ocean transparency
|
||||||
if (LodRenderer.transparencyEnabled && LodRenderer.fakeOceanFloor)
|
if (LodRenderer.transparencyEnabled && LodRenderer.fakeOceanFloor)
|
||||||
{
|
{
|
||||||
@@ -99,7 +121,9 @@ public class ColumnBox
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// add top and bottom faces if requested //
|
//==========================//
|
||||||
|
// add top and bottom faces //
|
||||||
|
//==========================//
|
||||||
|
|
||||||
boolean skipTop = RenderDataPointUtil.doesDataPointExist(topData) && (RenderDataPointUtil.getYMin(topData) == maxY) && !isTopTransparent;
|
boolean skipTop = RenderDataPointUtil.doesDataPointExist(topData) && (RenderDataPointUtil.getYMin(topData) == maxY) && !isTopTransparent;
|
||||||
if (!skipTop)
|
if (!skipTop)
|
||||||
@@ -114,409 +138,291 @@ public class ColumnBox
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// add North, south, east, and west faces if requested //
|
|
||||||
|
|
||||||
// TODO merge duplicate code
|
//========================================//
|
||||||
//NORTH face vertex creation
|
// add North, south, east, and west faces //
|
||||||
|
//========================================//
|
||||||
|
|
||||||
|
// NORTH face
|
||||||
{
|
{
|
||||||
ColumnArrayView[] adjDataNorth = adjData[EDhDirection.NORTH.ordinal() - 2]; // TODO can we use something other than ordinal-2?
|
ColumnArrayView adjCol = adjData[EDhDirection.NORTH.ordinal() - 2]; // TODO can we use something other than ordinal-2?
|
||||||
int adjOverlapNorth = ColorUtil.INVISIBLE;
|
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.NORTH.ordinal() - 2];
|
||||||
if (adjDataNorth == null)
|
// if the adjacent column is null that generally means the adjacent area hasn't been generated yet
|
||||||
|
if (adjCol == null)
|
||||||
{
|
{
|
||||||
// add an adjacent face if this is opaque face or transparent over the void
|
// Add an adjacent face if this is opaque face or transparent over the void.
|
||||||
if (!isTransparent || overVoid)
|
if (!isTransparent || overVoid)
|
||||||
{
|
{
|
||||||
builder.addQuadAdj(EDhDirection.NORTH, x, minY, z, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
|
builder.addQuadAdj(EDhDirection.NORTH, x, minY, z, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (adjDataNorth.length == 1)
|
|
||||||
{
|
|
||||||
makeAdjVerticalQuad(builder, adjDataNorth[0], EDhDirection.NORTH, x, minY, z, xSize, ySize,
|
|
||||||
color, adjOverlapNorth, irisBlockMaterialId, skyLightTop, blockLight,
|
|
||||||
topData, bottomData);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
makeAdjVerticalQuad(builder, adjDataNorth[0], EDhDirection.NORTH, x, minY, z, (short) (xSize / 2), ySize,
|
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH, x, minY, z, xSize, ySize,
|
||||||
color, adjOverlapNorth, irisBlockMaterialId, skyLightTop, blockLight,
|
color, irisBlockMaterialId, blockLight);
|
||||||
topData, bottomData);
|
|
||||||
makeAdjVerticalQuad(builder, adjDataNorth[1], EDhDirection.NORTH, (short) (x + xSize / 2), minY, z, (short) (xSize / 2), ySize,
|
|
||||||
color, adjOverlapNorth, irisBlockMaterialId, skyLightTop, blockLight,
|
|
||||||
topData, bottomData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//SOUTH face vertex creation
|
// SOUTH face
|
||||||
{
|
{
|
||||||
ColumnArrayView[] adjDataSouth = adjData[EDhDirection.SOUTH.ordinal() - 2];
|
ColumnArrayView adjCol = adjData[EDhDirection.SOUTH.ordinal() - 2];
|
||||||
int adjOverlapSouth = ColorUtil.INVISIBLE;
|
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.SOUTH.ordinal() - 2];
|
||||||
if (adjDataSouth == null)
|
if (adjCol == null)
|
||||||
{
|
{
|
||||||
if (!isTransparent || overVoid)
|
if (!isTransparent || overVoid)
|
||||||
|
{
|
||||||
builder.addQuadAdj(EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
|
builder.addQuadAdj(EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
|
||||||
}
|
}
|
||||||
else if (adjDataSouth.length == 1)
|
|
||||||
{
|
|
||||||
makeAdjVerticalQuad(builder, adjDataSouth[0], EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize,
|
|
||||||
color, adjOverlapSouth, irisBlockMaterialId, skyLightTop, blockLight,
|
|
||||||
topData, bottomData);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
makeAdjVerticalQuad(builder, adjDataSouth[0], EDhDirection.SOUTH, x, minY, maxZ, (short) (xSize / 2), ySize,
|
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize,
|
||||||
color, adjOverlapSouth, irisBlockMaterialId, skyLightTop, blockLight,
|
color, irisBlockMaterialId, blockLight);
|
||||||
topData, bottomData);
|
|
||||||
|
|
||||||
makeAdjVerticalQuad(builder, adjDataSouth[1], EDhDirection.SOUTH, (short) (x + xSize / 2), minY, maxZ, (short) (xSize / 2), ySize,
|
|
||||||
color, adjOverlapSouth, irisBlockMaterialId, skyLightTop, blockLight,
|
|
||||||
topData, bottomData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//WEST face vertex creation
|
// WEST face
|
||||||
{
|
{
|
||||||
ColumnArrayView[] adjDataWest = adjData[EDhDirection.WEST.ordinal() - 2];
|
ColumnArrayView adjCol = adjData[EDhDirection.WEST.ordinal() - 2];
|
||||||
int adjOverlapWest = ColorUtil.INVISIBLE;
|
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.WEST.ordinal() - 2];
|
||||||
if (adjDataWest == null)
|
if (adjCol == null)
|
||||||
{
|
{
|
||||||
if (!isTransparent || overVoid)
|
if (!isTransparent || overVoid)
|
||||||
|
{
|
||||||
builder.addQuadAdj(EDhDirection.WEST, x, minY, z, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
|
builder.addQuadAdj(EDhDirection.WEST, x, minY, z, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
|
||||||
}
|
}
|
||||||
else if (adjDataWest.length == 1)
|
|
||||||
{
|
|
||||||
makeAdjVerticalQuad(builder, adjDataWest[0], EDhDirection.WEST, x, minY, z, zSize, ySize,
|
|
||||||
color, adjOverlapWest, irisBlockMaterialId, skyLightTop, blockLight,
|
|
||||||
topData, bottomData);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
makeAdjVerticalQuad(builder, adjDataWest[0], EDhDirection.WEST, x, minY, z, (short) (zSize / 2), ySize,
|
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST, x, minY, z, zSize, ySize,
|
||||||
color, adjOverlapWest, irisBlockMaterialId, skyLightTop, blockLight,
|
color, irisBlockMaterialId, blockLight);
|
||||||
topData, bottomData);
|
|
||||||
makeAdjVerticalQuad(builder, adjDataWest[1], EDhDirection.WEST, x, minY, (short) (z + zSize / 2), (short) (zSize / 2), ySize,
|
|
||||||
color, adjOverlapWest, irisBlockMaterialId, skyLightTop, blockLight,
|
|
||||||
topData, bottomData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//EAST face vertex creation
|
// EAST face
|
||||||
{
|
{
|
||||||
ColumnArrayView[] adjDataEast = adjData[EDhDirection.EAST.ordinal() - 2];
|
ColumnArrayView adjCol = adjData[EDhDirection.EAST.ordinal() - 2];
|
||||||
int adjOverlapEast = ColorUtil.INVISIBLE;
|
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.EAST.ordinal() - 2];
|
||||||
if (adjData[EDhDirection.EAST.ordinal() - 2] == null)
|
if (adjCol == null)
|
||||||
{
|
{
|
||||||
if (!isTransparent || overVoid)
|
if (!isTransparent || overVoid)
|
||||||
|
{
|
||||||
builder.addQuadAdj(EDhDirection.EAST, maxX, minY, z, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
|
builder.addQuadAdj(EDhDirection.EAST, maxX, minY, z, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
|
||||||
}
|
}
|
||||||
else if (adjDataEast.length == 1)
|
|
||||||
{
|
|
||||||
makeAdjVerticalQuad(builder, adjDataEast[0], EDhDirection.EAST, maxX, minY, z, zSize, ySize,
|
|
||||||
color, adjOverlapEast, irisBlockMaterialId, skyLightTop, blockLight,
|
|
||||||
topData, bottomData);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
makeAdjVerticalQuad(builder, adjDataEast[0], EDhDirection.EAST, maxX, minY, z, (short) (zSize / 2), ySize,
|
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST, maxX, minY, z, zSize, ySize,
|
||||||
color, adjOverlapEast, irisBlockMaterialId, skyLightTop, blockLight,
|
color, irisBlockMaterialId, blockLight);
|
||||||
topData, bottomData);
|
|
||||||
makeAdjVerticalQuad(builder, adjDataEast[1], EDhDirection.EAST, maxX, minY, (short) (z + zSize / 2), (short) (zSize / 2), ySize,
|
|
||||||
color, adjOverlapEast, irisBlockMaterialId, skyLightTop, blockLight,
|
|
||||||
topData, bottomData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the overlap color can be used to see faces that shouldn't be rendered
|
|
||||||
private static void makeAdjVerticalQuad(
|
private static void makeAdjVerticalQuad(
|
||||||
LodQuadBuilder builder, ColumnArrayView adjColumnView, EDhDirection direction,
|
LodQuadBuilder builder, @NotNull ColumnArrayView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction,
|
||||||
short x, short yMin, short z, short horizontalWidth, short ySize,
|
short x, short yMin, short z, short horizontalWidth, short ySize,
|
||||||
int color, int debugOverlapColor, byte irisBlockMaterialId, byte skyLightTop, byte blockLight,
|
int color, byte irisBlockMaterialId, byte blockLight)
|
||||||
long topData, long bottomData)
|
|
||||||
{
|
{
|
||||||
|
//==================//
|
||||||
|
// create face with //
|
||||||
|
// no adjacent data //
|
||||||
|
//==================//
|
||||||
|
|
||||||
color = ColorUtil.applyShade(color, MC.getShade(direction));
|
color = ColorUtil.applyShade(color, MC.getShade(direction));
|
||||||
|
|
||||||
if (adjColumnView == null || adjColumnView.size == 0 || RenderDataPointUtil.isVoid(adjColumnView.get(0)))
|
// if there isn't any data adjacent to this LOD,
|
||||||
|
// just add the full vertical quad
|
||||||
|
if (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, LodUtil.MAX_MC_LIGHT, blockLight);
|
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int yMax = yMin + ySize;
|
|
||||||
|
|
||||||
int adjIndex;
|
//===========================//
|
||||||
boolean firstFace = true;
|
// Determine face visibility //
|
||||||
boolean inputAboveAdjLods = true;
|
// based on it's neighbors //
|
||||||
short previousAdjDepth = -1;
|
//===========================//
|
||||||
byte nextTopSkyLight = skyLightTop;
|
|
||||||
boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && LodRenderer.transparencyEnabled;
|
|
||||||
boolean lastAdjWasTransparent = false;
|
|
||||||
|
|
||||||
|
short yMax = (short) (yMin + ySize); // min is inclusive, max is exclusive
|
||||||
|
byte[] skyLightAtInputPos = THREAD_LOCAL_SKY_LIGHT_ARRAY.get();
|
||||||
|
|
||||||
|
try
|
||||||
if (!RenderDataPointUtil.doesDataPointExist(bottomData))
|
|
||||||
{
|
{
|
||||||
// there isn't anything under this LOD,
|
// set the initial sky-lights for this face,
|
||||||
// to prevent seeing through the world, make it opaque
|
// if nothing overlaps or overhangs the face should have max sky light
|
||||||
color = ColorUtil.setAlpha(color, 255);
|
Arrays.fill(skyLightAtInputPos, yMin, yMax, LodUtil.MAX_MC_LIGHT);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Add adjacent faces if this LOD is surrounded by transparent LODs
|
|
||||||
// (prevents invisible sides underwater)
|
|
||||||
int adjCount = adjColumnView.size();
|
|
||||||
for (adjIndex = 0; // iterates top down
|
|
||||||
adjIndex < adjCount
|
|
||||||
&& RenderDataPointUtil.doesDataPointExist(adjColumnView.get(adjIndex))
|
|
||||||
&& !RenderDataPointUtil.isVoid(adjColumnView.get(adjIndex));
|
|
||||||
adjIndex++)
|
|
||||||
{
|
|
||||||
long adjPoint = adjColumnView.get(adjIndex);
|
|
||||||
|
|
||||||
// if the adjacent data point is over the void
|
// iterate top down
|
||||||
// don't consider it as transparent
|
int adjCount = adjColumnView.size();
|
||||||
// FIXME this transparency change should be applied before this point since this could affect other areas
|
for (int adjIndex = 0; adjIndex < adjCount; adjIndex++)
|
||||||
boolean adjOverVoid = false;
|
|
||||||
if (adjIndex > 0)
|
|
||||||
{
|
{
|
||||||
long adjBellowPoint = adjColumnView.get(adjIndex-1);
|
long adjPoint = adjColumnView.get(adjIndex);
|
||||||
adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBellowPoint);
|
short adjMinY = RenderDataPointUtil.getYMin(adjPoint);
|
||||||
}
|
short adjMaxY = RenderDataPointUtil.getYMax(adjPoint);
|
||||||
boolean adjTransparent = !adjOverVoid && RenderDataPointUtil.getAlpha(adjPoint) < 255 && LodRenderer.transparencyEnabled;
|
|
||||||
|
|
||||||
|
|
||||||
// continue if this data point is transparent or the adjacent point is not
|
|
||||||
if (inputTransparent || !adjTransparent) // TODO inputIsTransparent may be unnecessary
|
|
||||||
{
|
|
||||||
short adjYMin = RenderDataPointUtil.getYMin(adjPoint);
|
|
||||||
short adjYMax = RenderDataPointUtil.getYMax(adjPoint);
|
|
||||||
|
|
||||||
|
// skip empty adjacent datapoints
|
||||||
// if fake transparency is enabled, allow for 1 block of transparency,
|
if (!RenderDataPointUtil.doesDataPointExist(adjPoint)
|
||||||
// everything under that should be opaque
|
|| RenderDataPointUtil.isVoid(adjPoint))
|
||||||
if (LodRenderer.transparencyEnabled && LodRenderer.fakeOceanFloor)
|
|
||||||
{
|
{
|
||||||
if (lastAdjWasTransparent && !adjTransparent)
|
|
||||||
{
|
|
||||||
adjYMax = (short) (RenderDataPointUtil.getYMax(adjColumnView.get(adjIndex - 1)) - 1);
|
|
||||||
}
|
|
||||||
else if (adjTransparent && (adjIndex + 1) < adjCount)
|
|
||||||
{
|
|
||||||
if (RenderDataPointUtil.getAlpha(adjColumnView.get(adjIndex + 1)) == 255)
|
|
||||||
{
|
|
||||||
adjYMin = (short) (adjYMax - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (yMax <= adjYMin)
|
|
||||||
{
|
|
||||||
// the adjacent LOD is above the input LOD and won't affect its rendering,
|
|
||||||
// skip to the next adjacent
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
inputAboveAdjLods = false;
|
|
||||||
|
|
||||||
|
// skip this adjacent datapoint if it's above the input datapoint (since it can't affect the input data point)
|
||||||
if (adjYMax < yMin)
|
if (yMax <= adjMinY)
|
||||||
{
|
{
|
||||||
// the adjacent LOD is below the input LOD
|
continue;
|
||||||
|
|
||||||
// getting the skylight is more complicated
|
|
||||||
// since LODs can be adjacent to water, which changes how skylight works
|
|
||||||
byte skyLight;
|
|
||||||
if (adjIndex == 0)
|
|
||||||
{
|
|
||||||
// this adj LOD is at the highest position,
|
|
||||||
// its sky lighting won't be affected by anything above it
|
|
||||||
skyLight = RenderDataPointUtil.getLightSky(adjPoint);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO improve the comments here, this is a bit confusing
|
|
||||||
long aboveAdjPoint = adjColumnView.get(adjIndex - 1);
|
|
||||||
if (RenderDataPointUtil.getAlpha(aboveAdjPoint) != 255)
|
|
||||||
{
|
|
||||||
// above adjacent LOD is transparent...
|
|
||||||
|
|
||||||
boolean inputMaxHigherThanAboveAdj = yMax > RenderDataPointUtil.getYMax(aboveAdjPoint);
|
|
||||||
if (inputMaxHigherThanAboveAdj)
|
|
||||||
{
|
|
||||||
// ...and higher than the input yMax,
|
|
||||||
// use its sky light
|
|
||||||
skyLight = RenderDataPointUtil.getLightSky(aboveAdjPoint);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// ...and at or below the input yMax,
|
|
||||||
skyLight = RenderDataPointUtil.getLightSky(adjPoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// LOD above adjacent is opaque, use the adj LOD's skylight
|
|
||||||
skyLight = RenderDataPointUtil.getLightSky(adjPoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (firstFace)
|
|
||||||
{
|
|
||||||
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, skyLight, blockLight);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Now: adjMaxHeight < y < previousAdjDepth < yMax
|
|
||||||
if (previousAdjDepth == -1)
|
|
||||||
{
|
|
||||||
// TODO why is this an error?
|
|
||||||
throw new RuntimeException("Loop error");
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (previousAdjDepth - yMin), color, irisBlockMaterialId, skyLight, blockLight);
|
|
||||||
|
|
||||||
previousAdjDepth = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO why break here?
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (adjYMin <= yMin)
|
long adjAbovePoint = (adjIndex != 0) ? adjColumnView.get(adjIndex - 1) : RenderDataPointUtil.EMPTY_DATA;
|
||||||
|
long adjBelowPoint = (adjIndex + 1 < adjCount) ? adjColumnView.get(adjIndex + 1) : RenderDataPointUtil.EMPTY_DATA;
|
||||||
|
|
||||||
|
// if the adjacent data point is over the void
|
||||||
|
// don't consider it as transparent
|
||||||
|
boolean adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBelowPoint);
|
||||||
|
boolean adjTransparent = !adjOverVoid && RenderDataPointUtil.getAlpha(adjPoint) < 255 && LodRenderer.transparencyEnabled;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=================================//
|
||||||
|
// set sky light based on adjacent //
|
||||||
|
//=================================//
|
||||||
|
|
||||||
|
// set light based on overlapping adjacent
|
||||||
|
if (!adjTransparent)
|
||||||
{
|
{
|
||||||
// the adjacent LOD's base is at or below the input's base
|
// adj opaque
|
||||||
|
// mark positions adjacent is covering
|
||||||
if (yMax <= adjYMax)
|
byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint);
|
||||||
|
for (int i = adjMinY; i < adjMaxY; i++)
|
||||||
{
|
{
|
||||||
// The input face is completely inside the adj's face, don't render it
|
byte skyLightAtPos = skyLightAtInputPos[i];
|
||||||
if (debugOverlapColor != 0)
|
|
||||||
{
|
|
||||||
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// the adj data intersects the lower part of the input data, don't render below the intersection
|
|
||||||
|
|
||||||
if (adjYMax > yMin && debugOverlapColor != 0)
|
// if the adjacent is a different detail level, we want to render adjacent opaque
|
||||||
{
|
// faces to try and reduce the chance of holes on detail level borders
|
||||||
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (adjYMax - yMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
|
boolean adjacentCoversThis =
|
||||||
}
|
// if the adjacent is the same detail level, no special handling is necessary
|
||||||
|
!adjacentIsSameDetailLevel
|
||||||
|
// if the adjacent face is underground we probably don't need it
|
||||||
|
&& RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY
|
||||||
|
// check if this face is on a border
|
||||||
|
&&
|
||||||
|
(
|
||||||
|
(x == 0 && direction == EDhDirection.WEST)
|
||||||
|
|| (z == 0 && direction == EDhDirection.NORTH)
|
||||||
|
// TODO why does 256 represent a border? aren't LODs only 64 datapoints wide?
|
||||||
|
|| (x == 256 && direction == EDhDirection.EAST)
|
||||||
|
|| (z == 256 && direction == EDhDirection.SOUTH)
|
||||||
|
);
|
||||||
|
|
||||||
// if this is the only face, use the yMax and break,
|
byte newSkyLightAtPos = adjacentCoversThis ? adjSkyLight : SKYLIGHT_COVERED;
|
||||||
// if there was another face finish the last one and then break
|
skyLightAtInputPos[i] = (byte) Math.min(newSkyLightAtPos, skyLightAtPos);
|
||||||
if (firstFace)
|
|
||||||
{
|
|
||||||
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (yMax - adjYMax), color, irisBlockMaterialId,
|
|
||||||
RenderDataPointUtil.getLightSky(adjPoint), blockLight);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Now: depth <= y <= height <= previousAdjDepth < yMax
|
|
||||||
if (previousAdjDepth == -1)
|
|
||||||
{
|
|
||||||
// TODO why is this an error?
|
|
||||||
throw new RuntimeException("Loop error");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (previousAdjDepth > adjYMax)
|
|
||||||
{
|
|
||||||
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (previousAdjDepth - adjYMax), color, irisBlockMaterialId,
|
|
||||||
RenderDataPointUtil.getLightSky(adjPoint), blockLight);
|
|
||||||
}
|
|
||||||
previousAdjDepth = -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// we don't need to check any other adjacent LODs
|
|
||||||
// since this one completely covers the input
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// In here always true: y < adjYMin < yMax
|
|
||||||
// _________________&&: y < ________ (height and yMax)
|
|
||||||
|
|
||||||
if (adjYMax >= yMax)
|
|
||||||
{
|
|
||||||
// Basically: y _______ < yMax <= height
|
|
||||||
// _______&&: y < depth < yMax
|
|
||||||
// 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, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// we start the creation of a new face
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Otherwise: y < _____ height < yMax
|
// adjacent is transparent,
|
||||||
// _______&&: y < depth ______ < yMax
|
// use datapoint below adjacent for lighting
|
||||||
if (debugOverlapColor != 0)
|
byte belowSkyLight = RenderDataPointUtil.getLightSky(adjBelowPoint);
|
||||||
|
for (int i = adjMinY; i < adjMaxY; i++)
|
||||||
{
|
{
|
||||||
builder.addQuadAdj(direction, x, adjYMin, z, horizontalWidth, (short) (adjYMax - adjYMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT);
|
byte skyLightAtPos = skyLightAtInputPos[i];
|
||||||
}
|
skyLightAtInputPos[i] = (byte) Math.min(belowSkyLight, skyLightAtPos);
|
||||||
|
|
||||||
if (firstFace)
|
|
||||||
{
|
|
||||||
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (yMax - adjYMax), color, irisBlockMaterialId,
|
|
||||||
RenderDataPointUtil.getLightSky(adjPoint), blockLight);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Now: y < depth < height <= previousAdjDepth < yMax
|
|
||||||
if (previousAdjDepth == -1)
|
|
||||||
throw new RuntimeException("Loop error");
|
|
||||||
if (previousAdjDepth > adjYMax)
|
|
||||||
{
|
|
||||||
if (irisBlockMaterialId == EDhApiBlockMaterial.GRASS.index)
|
|
||||||
{
|
|
||||||
// this LOD is underneath another, grass will never show here
|
|
||||||
irisBlockMaterialId = EDhApiBlockMaterial.DIRT.index;
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (previousAdjDepth - adjYMax), color, irisBlockMaterialId,
|
|
||||||
RenderDataPointUtil.getLightSky(adjPoint), blockLight);
|
|
||||||
}
|
|
||||||
previousAdjDepth = -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// set next top as current depth
|
// fill in sky light up to the next DP,
|
||||||
previousAdjDepth = adjYMin;
|
// this is done to handle overhangs
|
||||||
firstFace = false;
|
byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint);
|
||||||
nextTopSkyLight = skyLightTop;
|
int adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint);
|
||||||
|
for (int i = adjMaxY; i < adjAboveMinY; i++)
|
||||||
if (adjIndex + 1 < adjColumnView.size() && RenderDataPointUtil.doesDataPointExist(adjColumnView.get(adjIndex + 1)))
|
|
||||||
{
|
{
|
||||||
nextTopSkyLight = RenderDataPointUtil.getLightSky(adjColumnView.get(adjIndex + 1));
|
byte skyLightAtPos = skyLightAtInputPos[i];
|
||||||
|
skyLightAtInputPos[i] = (byte) Math.min(adjSkyLight, skyLightAtPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=======================//
|
||||||
|
// create vertical faces //
|
||||||
|
//=======================//
|
||||||
|
|
||||||
|
boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && LodRenderer.transparencyEnabled;
|
||||||
|
byte lastSkyLight = skyLightAtInputPos[yMin];
|
||||||
|
int quadBottomY = yMin;
|
||||||
|
int quadTopY = -1;
|
||||||
|
|
||||||
|
// walk up the sky lights and create a new face
|
||||||
|
// whenever the light changes to different valid value
|
||||||
|
for (int i = yMin; i < yMax; i++)
|
||||||
|
{
|
||||||
|
byte skyLight = skyLightAtInputPos[i];
|
||||||
|
if (skyLight != lastSkyLight)
|
||||||
|
{
|
||||||
|
// the sky light changed, create the in-progress face
|
||||||
|
tryAddVerticalFaceWithSkyLightToBuilder(
|
||||||
|
builder, direction,
|
||||||
|
x, z, horizontalWidth,
|
||||||
|
color, irisBlockMaterialId, blockLight,
|
||||||
|
lastSkyLight, inputTransparent, quadTopY, quadBottomY
|
||||||
|
);
|
||||||
|
|
||||||
|
lastSkyLight = skyLight;
|
||||||
|
quadBottomY = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastAdjWasTransparent = adjTransparent;
|
quadTopY = (i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the in-progress face if present
|
||||||
|
if (quadTopY != -1)
|
||||||
|
{
|
||||||
|
tryAddVerticalFaceWithSkyLightToBuilder(
|
||||||
|
builder, direction,
|
||||||
|
x, z, horizontalWidth,
|
||||||
|
color, irisBlockMaterialId, blockLight,
|
||||||
|
lastSkyLight, inputTransparent, quadTopY, quadBottomY
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
|
||||||
|
|
||||||
if (inputAboveAdjLods)
|
|
||||||
{
|
{
|
||||||
// the input LOD is above all adjacent LODs and won't be affected
|
// clean up the array before the next thread uses it
|
||||||
// by them, add the vertical quad using the input's lighting and height
|
// (may be unnecessary since we only work between the yMin-yMax anyway, but is helpful for debugging)
|
||||||
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, skyLightTop, blockLight);
|
Arrays.fill(skyLightAtInputPos, yMin, yMax, SKYLIGHT_EMPTY);
|
||||||
}
|
}
|
||||||
else if (previousAdjDepth != -1)
|
}
|
||||||
|
private static void tryAddVerticalFaceWithSkyLightToBuilder(
|
||||||
|
LodQuadBuilder builder, EDhDirection direction,
|
||||||
|
short x, short z, short horizontalWidth,
|
||||||
|
int color, byte irisBlockMaterialId, byte blockLight,
|
||||||
|
byte lastSkyLight, boolean inputTransparent, int quadTopY, int quadBottomY
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// invalid positions will have a negative skylight
|
||||||
|
if (lastSkyLight >= 0)
|
||||||
{
|
{
|
||||||
// We need to finish the last quad.
|
// Don't add transparent vertical faces
|
||||||
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (previousAdjDepth - yMin), color, irisBlockMaterialId, nextTopSkyLight, blockLight);
|
// unless the adjacent position is empty.
|
||||||
|
// This is done to prevent walls between water blocks in the ocean.
|
||||||
|
if (!inputTransparent
|
||||||
|
|| (lastSkyLight == LodUtil.MAX_MC_LIGHT))
|
||||||
|
{
|
||||||
|
// don't add negative/empty height faces
|
||||||
|
short height = (short) (quadTopY - quadBottomY);
|
||||||
|
if (height > 0)
|
||||||
|
{
|
||||||
|
builder.addQuadAdj(direction, x, (short) quadBottomY, z, horizontalWidth, height, color, irisBlockMaterialId, lastSkyLight, blockLight);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,23 +19,23 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
|
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.DhApi;
|
||||||
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhBlockPos;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||||
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
||||||
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
|
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.core.util.objects.StatsMap;
|
import com.seibel.distanthorizons.core.util.objects.StatsMap;
|
||||||
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
|
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
|
||||||
import com.seibel.distanthorizons.core.util.*;
|
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Iterator;
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,7 +53,7 @@ public class ColumnRenderBuffer implements AutoCloseable
|
|||||||
/** number of bytes a single quad takes */
|
/** number of bytes a single quad takes */
|
||||||
public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 4;
|
public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 4;
|
||||||
/** how big a single VBO can be in bytes */
|
/** how big a single VBO can be in bytes */
|
||||||
public static final int MAX_VBO_BYTE_SIZE = 1024 * 1024; // 1 MB
|
public static final int MAX_VBO_BYTE_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||||
public static final int MAX_QUADS_PER_BUFFER = MAX_VBO_BYTE_SIZE / QUADS_BYTE_SIZE;
|
public static final int MAX_QUADS_PER_BUFFER = MAX_VBO_BYTE_SIZE / QUADS_BYTE_SIZE;
|
||||||
public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
|
public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ public class ColumnRenderBuffer implements AutoCloseable
|
|||||||
/** Should be run on a DH thread. */
|
/** Should be run on a DH thread. */
|
||||||
public void uploadBuffer(LodQuadBuilder builder, EDhApiGpuUploadMethod 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.");
|
LodUtil.assertTrue(DhApi.isDhThread(), "Buffer uploading needs to be done on a DH thread to prevent locking up any MC threads.");
|
||||||
|
|
||||||
|
|
||||||
// upload on MC's render thread
|
// upload on MC's render thread
|
||||||
@@ -100,7 +100,7 @@ public class ColumnRenderBuffer implements AutoCloseable
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.uploadBuffersUsingUploadMethod(builder, gpuUploadMethod);
|
this.uploadBuffers(builder, gpuUploadMethod);
|
||||||
uploadFuture.complete(null);
|
uploadFuture.complete(null);
|
||||||
}
|
}
|
||||||
catch (InterruptedException e)
|
catch (InterruptedException e)
|
||||||
@@ -126,72 +126,46 @@ public class ColumnRenderBuffer implements AutoCloseable
|
|||||||
//LOGGER.warn("Error uploading builder ["+builder+"] synchronously. Error: "+e.getMessage(), e);
|
//LOGGER.warn("Error uploading builder ["+builder+"] synchronously. Error: "+e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void uploadBuffersUsingUploadMethod(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException
|
private void uploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException
|
||||||
{
|
{
|
||||||
if (gpuUploadMethod.useEarlyMapping)
|
// uploading mapped buffers used to be done here,
|
||||||
{
|
// however due to a memory leak and complication with the previous code,
|
||||||
this.uploadBuffersMapped(builder, gpuUploadMethod);
|
// now we only allow direct uploading.
|
||||||
}
|
// (There's also insufficient data to state whether mapped buffers are necessary
|
||||||
else
|
// for DH to upload without stuttering the main thread)
|
||||||
{
|
|
||||||
this.uploadBuffersDirect(builder, gpuUploadMethod);
|
this.vbos = makeAndUploadBuffers(builder, method, this.vbos, builder.makeOpaqueVertexBuffers());
|
||||||
}
|
this.vbosTransparent = makeAndUploadBuffers(builder, method, this.vbosTransparent, builder.makeTransparentVertexBuffers());
|
||||||
|
|
||||||
this.buffersUploaded = true;
|
this.buffersUploaded = true;
|
||||||
}
|
}
|
||||||
|
/** This resizes and returns the vbo array if necessary based on the amount of data needed for this area. */
|
||||||
|
private static GLVertexBuffer[] makeAndUploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method, GLVertexBuffer[] vbos, ArrayList<ByteBuffer> buffers) throws InterruptedException
|
||||||
|
|
||||||
private void uploadBuffersMapped(LodQuadBuilder builder, EDhApiGpuUploadMethod method)
|
|
||||||
{
|
{
|
||||||
// opaque vbos //
|
try
|
||||||
|
|
||||||
this.vbos = ColumnRenderBufferBuilder.resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
|
|
||||||
for (int i = 0; i < this.vbos.length; i++)
|
|
||||||
{
|
{
|
||||||
if (this.vbos[i] == null)
|
vbos = resizeBuffer(vbos, buffers.size());
|
||||||
|
uploadBuffersDirect(vbos, buffers, method);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// all the buffers must be manually freed to prevent memory leaks
|
||||||
|
if (buffers != null)
|
||||||
{
|
{
|
||||||
this.vbos[i] = new GLVertexBuffer(method.useBufferStorage);
|
for (ByteBuffer buffer : buffers)
|
||||||
|
{
|
||||||
|
MemoryUtil.memFree(buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LodQuadBuilder.BufferFiller func = builder.makeOpaqueBufferFiller(method);
|
|
||||||
for (GLVertexBuffer vbo : this.vbos)
|
|
||||||
{
|
|
||||||
func.fill(vbo);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// return the array in case it was resized
|
||||||
// transparent vbos //
|
return vbos;
|
||||||
|
|
||||||
this.vbosTransparent = ColumnRenderBufferBuilder.resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
|
|
||||||
for (int i = 0; i < this.vbosTransparent.length; i++)
|
|
||||||
{
|
|
||||||
if (this.vbosTransparent[i] == null)
|
|
||||||
{
|
|
||||||
this.vbosTransparent[i] = new GLVertexBuffer(method.useBufferStorage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LodQuadBuilder.BufferFiller transparentFillerFunc = builder.makeTransparentBufferFiller(method);
|
|
||||||
for (GLVertexBuffer vbo : this.vbosTransparent)
|
|
||||||
{
|
|
||||||
transparentFillerFunc.fill(vbo);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, ArrayList<ByteBuffer> byteBuffers, EDhApiGpuUploadMethod 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);
|
|
||||||
|
|
||||||
this.vbosTransparent = ColumnRenderBufferBuilder.resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
|
|
||||||
uploadBuffersDirect(this.vbosTransparent, builder.makeTransparentVertexBuffers(), method);
|
|
||||||
}
|
|
||||||
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, Iterator<ByteBuffer> iter, EDhApiGpuUploadMethod method) throws InterruptedException
|
|
||||||
{
|
|
||||||
long remainingMS = 0;
|
|
||||||
long MBPerMS = Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds.get();
|
|
||||||
int vboIndex = 0;
|
int vboIndex = 0;
|
||||||
while (iter.hasNext())
|
for (int i = 0; i < byteBuffers.size(); i++)
|
||||||
{
|
{
|
||||||
if (vboIndex >= vbos.length)
|
if (vboIndex >= vbos.length)
|
||||||
{
|
{
|
||||||
@@ -207,13 +181,13 @@ public class ColumnRenderBuffer implements AutoCloseable
|
|||||||
GLVertexBuffer vbo = vbos[vboIndex];
|
GLVertexBuffer vbo = vbos[vboIndex];
|
||||||
|
|
||||||
|
|
||||||
ByteBuffer bb = iter.next();
|
ByteBuffer buffer = byteBuffers.get(i);
|
||||||
int size = bb.limit() - bb.position();
|
int size = buffer.limit() - buffer.position();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
vbo.bind();
|
vbo.bind();
|
||||||
vbo.uploadBuffer(bb, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
|
vbo.uploadBuffer(buffer, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -222,24 +196,6 @@ public class ColumnRenderBuffer implements AutoCloseable
|
|||||||
LOGGER.error("Failed to upload buffer: ", e);
|
LOGGER.error("Failed to upload buffer: ", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (MBPerMS > 0)
|
|
||||||
{
|
|
||||||
// upload buffers over an extended period of time
|
|
||||||
// to hopefully prevent stuttering.
|
|
||||||
remainingMS += size * MBPerMS;
|
|
||||||
if (remainingMS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS))
|
|
||||||
{
|
|
||||||
if (remainingMS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
|
|
||||||
{
|
|
||||||
remainingMS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.sleep(remainingMS / 1000000, (int) (remainingMS % 1000000));
|
|
||||||
remainingMS = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vboIndex++;
|
vboIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,9 +274,9 @@ public class ColumnRenderBuffer implements AutoCloseable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//================//
|
||||||
// misc methods //
|
// helper methods //
|
||||||
//==============//
|
//================//
|
||||||
|
|
||||||
/** can be used when debugging */
|
/** can be used when debugging */
|
||||||
public boolean hasNonNullVbos() { return this.vbos != null || this.vbosTransparent != null; }
|
public boolean hasNonNullVbos() { return this.vbos != null || this.vbosTransparent != null; }
|
||||||
@@ -366,6 +322,35 @@ public class ColumnRenderBuffer implements AutoCloseable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
|
||||||
|
{
|
||||||
|
if (vbos.length == newSize)
|
||||||
|
{
|
||||||
|
return vbos;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLVertexBuffer[] newVbos = new GLVertexBuffer[newSize];
|
||||||
|
System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize));
|
||||||
|
if (newSize < vbos.length)
|
||||||
|
{
|
||||||
|
for (int i = newSize; i < vbos.length; i++)
|
||||||
|
{
|
||||||
|
if (vbos[i] != null)
|
||||||
|
{
|
||||||
|
vbos[i].close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newVbos;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// base overrides //
|
||||||
|
//================//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called when object is no longer in use.
|
* This method is called when object is no longer in use.
|
||||||
* Called either after uploadBuffers() returned false (On buffer Upload
|
* Called either after uploadBuffers() returned false (On buffer Upload
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
|
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
|
||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
|
||||||
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
@@ -26,15 +27,16 @@ import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
|
|||||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
|
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhBlockPos;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
||||||
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
|
import com.seibel.distanthorizons.core.util.ColorUtil;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
||||||
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
|
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
|
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
|
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
@@ -59,21 +61,21 @@ public class ColumnRenderBufferBuilder
|
|||||||
// vbo building //
|
// vbo building //
|
||||||
//==============//
|
//==============//
|
||||||
|
|
||||||
public static CompletableFuture<ColumnRenderBuffer> buildAndUploadBuffersAsync(
|
public static CompletableFuture<LodQuadBuilder> buildBuffersAsync(
|
||||||
IDhClientLevel clientLevel,
|
IDhClientLevel clientLevel,
|
||||||
ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
|
ColumnRenderSource renderSource, ColumnRenderSource[] adjData, boolean[] isSameDetailLevel
|
||||||
|
)
|
||||||
{
|
{
|
||||||
ThreadPoolExecutor bufferBuilderExecutor = ThreadPoolUtil.getBufferBuilderExecutor();
|
ThreadPoolExecutor bufferBuilderExecutor = ThreadPoolUtil.getBufferBuilderExecutor();
|
||||||
ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor();
|
if (bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated())
|
||||||
if ((bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated()) ||
|
|
||||||
(bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated()))
|
|
||||||
{
|
{
|
||||||
// one or more of the thread pools has been shut down
|
// one or more of the thread pools has been shut down
|
||||||
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
|
CompletableFuture<LodQuadBuilder> future = new CompletableFuture<>();
|
||||||
future.cancel(true);
|
future.cancel(true);
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return CompletableFuture.supplyAsync(() ->
|
return CompletableFuture.supplyAsync(() ->
|
||||||
@@ -81,35 +83,8 @@ public class ColumnRenderBufferBuilder
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
|
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
|
||||||
|
LodQuadBuilder builder = new LodQuadBuilder(enableTransparency, clientLevel.getClientLevelWrapper());
|
||||||
//EVENT_LOGGER.trace("RenderRegion start QuadBuild @ " + renderSource.sectionPos);
|
makeLodRenderData(builder, renderSource, clientLevel, adjData, isSameDetailLevel);
|
||||||
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.
|
|
||||||
skyLightCullingBelow = Math.max(skyLightCullingBelow, clientLevel.getMinY());
|
|
||||||
|
|
||||||
|
|
||||||
long builderStartTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
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.pos + " took: " + buildMs);
|
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
catch (UncheckedInterruptedException e)
|
catch (UncheckedInterruptedException e)
|
||||||
@@ -118,20 +93,58 @@ public class ColumnRenderBufferBuilder
|
|||||||
}
|
}
|
||||||
catch (Throwable e3)
|
catch (Throwable e3)
|
||||||
{
|
{
|
||||||
LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3);
|
LOGGER.error("LodNodeBufferBuilder was unable to build quads for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: ["+ e3.getMessage()+"].", e3);
|
||||||
throw e3;
|
throw e3;
|
||||||
}
|
}
|
||||||
}, bufferBuilderExecutor)
|
}, bufferBuilderExecutor);
|
||||||
.thenApplyAsync((quadBuilder) ->
|
}
|
||||||
|
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<LodQuadBuilder> future = new CompletableFuture<>();
|
||||||
|
future.cancel(true);
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @link adjData should be null for adjacent sections that cross detail level boundaries */
|
||||||
|
public static CompletableFuture<ColumnRenderBuffer> uploadBuffersAsync(
|
||||||
|
IDhClientLevel clientLevel,
|
||||||
|
long pos,
|
||||||
|
LodQuadBuilder quadBuilder
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// TODO put into a single future/thread so it can be easily canceled
|
||||||
|
ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor();
|
||||||
|
if (bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated())
|
||||||
|
{
|
||||||
|
// one or more of the thread pools has been shut down
|
||||||
|
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
|
||||||
|
future.cancel(true);
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return CompletableFuture.supplyAsync(() ->
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(renderSource.pos), clientLevel.getMinY(), DhSectionPos.getMinCornerBlockZ(renderSource.pos)));
|
ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getMinY(), DhSectionPos.getMinCornerBlockZ(pos)));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
buffer.uploadBuffer(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
|
buffer.uploadBuffer(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
|
||||||
LodUtil.assertTrue(buffer.buffersUploaded);
|
if (buffer.buffersUploaded)
|
||||||
return buffer;
|
{
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer.close();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -145,35 +158,38 @@ public class ColumnRenderBufferBuilder
|
|||||||
}
|
}
|
||||||
catch (Throwable e3)
|
catch (Throwable e3)
|
||||||
{
|
{
|
||||||
LOGGER.error("LodNodeBufferBuilder was unable to upload buffer: " + e3.getMessage(), e3);
|
LOGGER.error("LodNodeBufferBuilder was unable to upload buffer for pos ["+DhSectionPos.toString(pos)+"], error: [" + e3.getMessage() + "].", e3);
|
||||||
throw e3;
|
throw e3;
|
||||||
}
|
}
|
||||||
}, bufferUploaderExecutor);
|
}, bufferUploaderExecutor);
|
||||||
}
|
}
|
||||||
catch (RejectedExecutionException ignore)
|
catch (RejectedExecutionException ignore)
|
||||||
{
|
{
|
||||||
// the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
|
// shouldn't happen, but just in case
|
||||||
|
|
||||||
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
|
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
|
||||||
future.cancel(true);
|
future.cancel(true);
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, ColumnRenderSource[] adjRegions)
|
private static void makeLodRenderData(
|
||||||
|
LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel,
|
||||||
|
ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel)
|
||||||
{
|
{
|
||||||
// Variable initialization
|
//=============//
|
||||||
EDhApiDebugRendering debugMode = Config.Client.Advanced.Debugging.debugRendering.get();
|
// debug check //
|
||||||
|
//=============//
|
||||||
|
|
||||||
// can be used to limit which section positions are build and thus, rendered
|
// can be used to limit which section positions are build and thus, rendered
|
||||||
// useful when debugging a specific section
|
// useful when debugging a specific section
|
||||||
boolean enableColumnBufferLimit = Config.Client.Advanced.Debugging.columnBuilderDebugEnable.get();
|
boolean columnBuilderDebugEnabled = Config.Client.Advanced.Debugging.columnBuilderDebugEnable.get();
|
||||||
if (enableColumnBufferLimit)
|
if (columnBuilderDebugEnabled)
|
||||||
{
|
{
|
||||||
if (DhSectionPos.getDetailLevel(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugDetailLevel.get()
|
if (DhSectionPos.getDetailLevel(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugDetailLevel.get()
|
||||||
&& DhSectionPos.getX(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugXPos.get()
|
&& DhSectionPos.getX(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugXPos.get()
|
||||||
&& DhSectionPos.getZ(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugZPos.get())
|
&& DhSectionPos.getZ(renderSource.pos) == Config.Client.Advanced.Debugging.columnBuilderDebugZPos.get())
|
||||||
{
|
{
|
||||||
int test = 0;
|
int breakpoint = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -181,24 +197,22 @@ public class ColumnRenderBufferBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte detailLevel = renderSource.getDataDetailLevel();
|
|
||||||
for (int x = 0; x < ColumnRenderSource.SECTION_SIZE; x++)
|
|
||||||
|
//===================//
|
||||||
|
// build each column //
|
||||||
|
//===================//
|
||||||
|
|
||||||
|
byte thisDetailLevel = renderSource.getDataDetailLevel();
|
||||||
|
for (int relX = 0; relX < ColumnRenderSource.SECTION_SIZE; relX++)
|
||||||
{
|
{
|
||||||
for (int z = 0; z < ColumnRenderSource.SECTION_SIZE; z++)
|
for (int relZ = 0; relZ < ColumnRenderSource.SECTION_SIZE; relZ++)
|
||||||
{
|
{
|
||||||
// TODO make a config for this
|
// stop the builder if requested
|
||||||
// can be uncommented to limit the buffer building to a specific
|
|
||||||
// relative position in this section.
|
|
||||||
// useful for debugging a single column's rendering
|
|
||||||
// if (x != 0 || (z != 0 && z != 1))
|
|
||||||
// {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
UncheckedInterruptedException.throwIfInterrupted();
|
UncheckedInterruptedException.throwIfInterrupted();
|
||||||
|
|
||||||
ColumnArrayView columnRenderData = renderSource.getVerticalDataPointView(x, z);
|
// ignore empty/null columns
|
||||||
|
ColumnArrayView columnRenderData = renderSource.getVerticalDataPointView(relX, relZ);
|
||||||
if (columnRenderData.size() == 0
|
if (columnRenderData.size() == 0
|
||||||
|| !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0))
|
|| !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0))
|
||||||
|| RenderDataPointUtil.isVoid(columnRenderData.get(0)))
|
|| RenderDataPointUtil.isVoid(columnRenderData.get(0)))
|
||||||
@@ -206,43 +220,66 @@ public class ColumnRenderBufferBuilder
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnRenderSource.DebugSourceFlag debugSourceFlag = renderSource.debugGetFlag(x, z);
|
|
||||||
|
|
||||||
ColumnArrayView[][] adjColumnViews = new ColumnArrayView[4][];
|
|
||||||
// We extract the adj data in the four cardinal direction
|
|
||||||
|
|
||||||
// we first reset the adjShadeDisabled. This is used to disable the shade on the
|
|
||||||
// border when we have transparent block like water or glass
|
|
||||||
// to avoid having a "darker border" underground
|
|
||||||
// Arrays.fill(adjShadeDisabled, false);
|
|
||||||
|
|
||||||
|
|
||||||
// We check every adj block in each direction
|
//=============//
|
||||||
|
// debug limit //
|
||||||
|
//=============//
|
||||||
|
|
||||||
// If the adj block is rendered in the same region and with same detail
|
// can be used to limit the buffer building to a specific relative position.
|
||||||
// and is positioned in a place that is not going to be rendered by vanilla game
|
// useful for debugging a single column
|
||||||
// then we can set this position as adj
|
if (columnBuilderDebugEnabled)
|
||||||
// We avoid cases where the adjPosition is in player chunk while the position is
|
{
|
||||||
// not
|
int wantedX = Config.Client.Advanced.Debugging.columnBuilderDebugXRow.get();
|
||||||
// to always have a wall underwater
|
if (wantedX >= 0 && relX != wantedX)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int wantedZ = Config.Client.Advanced.Debugging.columnBuilderDebugZRow.get();
|
||||||
|
if (wantedZ >= 0 && relZ != wantedZ)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==================================//
|
||||||
|
// get adjacent render data columns //
|
||||||
|
//==================================//
|
||||||
|
|
||||||
|
ColumnArrayView[] adjColumnViews = new ColumnArrayView[EDhDirection.ADJ_DIRECTIONS.length];
|
||||||
for (EDhDirection lodDirection : EDhDirection.ADJ_DIRECTIONS)
|
for (EDhDirection lodDirection : EDhDirection.ADJ_DIRECTIONS)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int xAdj = x + lodDirection.getNormal().x;
|
int xAdj = relX + lodDirection.getNormal().x;
|
||||||
int zAdj = z + lodDirection.getNormal().z;
|
int zAdj = relZ + lodDirection.getNormal().z;
|
||||||
boolean isCrossRegionBoundary =
|
boolean isCrossRenderSourceBoundary =
|
||||||
(xAdj < 0 || xAdj >= ColumnRenderSource.SECTION_SIZE) ||
|
(xAdj < 0 || xAdj >= ColumnRenderSource.SECTION_SIZE) ||
|
||||||
(zAdj < 0 || zAdj >= ColumnRenderSource.SECTION_SIZE);
|
(zAdj < 0 || zAdj >= ColumnRenderSource.SECTION_SIZE);
|
||||||
|
|
||||||
ColumnRenderSource adjRenderSource;
|
ColumnRenderSource adjRenderSource;
|
||||||
byte adjDetailLevel;
|
byte adjDetailLevel;
|
||||||
|
|
||||||
//we check if the detail of the adjPos is equal to the correct one (region border fix)
|
|
||||||
//or if the detail is wrong by 1 value (region+circle border fix)
|
|
||||||
if (isCrossRegionBoundary)
|
//=========================//
|
||||||
|
// get the adjacent render //
|
||||||
|
// source if present //
|
||||||
|
//=========================//
|
||||||
|
|
||||||
|
if (!isCrossRenderSourceBoundary)
|
||||||
{
|
{
|
||||||
//we compute at which detail that position should be rendered
|
// the adjacent position is inside this same render source
|
||||||
|
adjRenderSource = renderSource;
|
||||||
|
adjDetailLevel = thisDetailLevel;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// the adjacent position is outside this render source
|
||||||
|
|
||||||
|
// skip empty sections
|
||||||
adjRenderSource = adjRegions[lodDirection.ordinal() - 2];
|
adjRenderSource = adjRegions[lodDirection.ordinal() - 2];
|
||||||
if (adjRenderSource == null)
|
if (adjRenderSource == null)
|
||||||
{
|
{
|
||||||
@@ -250,67 +287,70 @@ public class ColumnRenderBufferBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
adjDetailLevel = adjRenderSource.getDataDetailLevel();
|
adjDetailLevel = adjRenderSource.getDataDetailLevel();
|
||||||
if (adjDetailLevel != detailLevel)
|
if (adjDetailLevel == thisDetailLevel)
|
||||||
{
|
|
||||||
//TODO: Implement this
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
|
// if the adjacent position is outside this render source,
|
||||||
|
// wrap the position around so it's inside the adjacent source
|
||||||
|
|
||||||
if (xAdj < 0)
|
if (xAdj < 0)
|
||||||
|
{
|
||||||
xAdj += ColumnRenderSource.SECTION_SIZE;
|
xAdj += ColumnRenderSource.SECTION_SIZE;
|
||||||
|
}
|
||||||
|
if (xAdj >= ColumnRenderSource.SECTION_SIZE)
|
||||||
|
{
|
||||||
|
xAdj -= ColumnRenderSource.SECTION_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
if (zAdj < 0)
|
if (zAdj < 0)
|
||||||
|
{
|
||||||
zAdj += ColumnRenderSource.SECTION_SIZE;
|
zAdj += ColumnRenderSource.SECTION_SIZE;
|
||||||
|
}
|
||||||
if (xAdj >= ColumnRenderSource.SECTION_SIZE)
|
|
||||||
xAdj -= ColumnRenderSource.SECTION_SIZE;
|
|
||||||
|
|
||||||
if (zAdj >= ColumnRenderSource.SECTION_SIZE)
|
if (zAdj >= ColumnRenderSource.SECTION_SIZE)
|
||||||
|
{
|
||||||
zAdj -= ColumnRenderSource.SECTION_SIZE;
|
zAdj -= ColumnRenderSource.SECTION_SIZE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
adjRenderSource = renderSource;
|
|
||||||
adjDetailLevel = detailLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adjDetailLevel < detailLevel - 1 || adjDetailLevel > detailLevel + 1)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adjDetailLevel == detailLevel || adjDetailLevel > detailLevel)
|
|
||||||
{
|
//========================//
|
||||||
adjColumnViews[lodDirection.ordinal() - 2] = new ColumnArrayView[1];
|
// get the adjacent views //
|
||||||
adjColumnViews[lodDirection.ordinal() - 2][0] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj);
|
//========================//
|
||||||
}
|
|
||||||
else
|
// the old logic handled additional cases, but they never appeared to fire,
|
||||||
{
|
// so just these two cases should be fine
|
||||||
adjColumnViews[lodDirection.ordinal() - 2] = new ColumnArrayView[2];
|
LodUtil.assertTrue(adjDetailLevel == thisDetailLevel || adjDetailLevel > thisDetailLevel);
|
||||||
adjColumnViews[lodDirection.ordinal() - 2][0] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj);
|
|
||||||
adjColumnViews[lodDirection.ordinal() - 2][1] = adjRenderSource.getVerticalDataPointView(
|
adjColumnViews[lodDirection.ordinal() - 2] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj);
|
||||||
xAdj + (lodDirection.getAxis() == EDhDirection.Axis.X ? 0 : 1),
|
|
||||||
zAdj + (lodDirection.getAxis() == EDhDirection.Axis.Z ? 0 : 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (RuntimeException e)
|
catch (RuntimeException e)
|
||||||
{
|
{
|
||||||
EVENT_LOGGER.warn("Failed to get adj data for [" + detailLevel + ":" + x + "," + z + "] at [" + lodDirection + "], Error: "+e.getMessage(), e);
|
EVENT_LOGGER.warn("Failed to get adj data for relative pos: [" + thisDetailLevel + ":" + relX + "," + relZ + "] at [" + lodDirection + "], Error: "+e.getMessage(), e);
|
||||||
}
|
}
|
||||||
} // for adjacent directions
|
} // for adjacent directions
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==========================//
|
||||||
|
// build this render column //
|
||||||
|
//==========================//
|
||||||
|
|
||||||
|
ColumnRenderSource.DebugSourceFlag debugSourceFlag = renderSource.debugGetFlag(relX, relZ);
|
||||||
|
|
||||||
// We render every vertical lod present in this position
|
// We render every vertical lod present in this position
|
||||||
// We only stop when we find a block that is void or non-existing block
|
// We only stop when we find a block that is void or non-existing block
|
||||||
for (int i = 0; i < columnRenderData.size(); i++)
|
for (int i = 0; i < columnRenderData.size(); i++)
|
||||||
{
|
{
|
||||||
// TODO make a config for this
|
|
||||||
// can be uncommented to limit which vertical LOD is generated
|
// can be uncommented to limit which vertical LOD is generated
|
||||||
// if (i != 0)
|
if (Config.Client.Advanced.Debugging.columnBuilderDebugEnable.get())
|
||||||
// {
|
{
|
||||||
// continue;
|
int wantedColumnIndex = Config.Client.Advanced.Debugging.columnBuilderDebugColumnIndex.get();
|
||||||
// }
|
if (wantedColumnIndex >= 0 && i != wantedColumnIndex)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
long data = columnRenderData.get(i);
|
long data = columnRenderData.get(i);
|
||||||
// If the data is not render-able (Void or non-existing) we stop since there is
|
// If the data is not render-able (Void or non-existing) we stop since there is
|
||||||
@@ -323,8 +363,12 @@ public class ColumnRenderBufferBuilder
|
|||||||
long topDataPoint = (i - 1) >= 0 ? columnRenderData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA;
|
long topDataPoint = (i - 1) >= 0 ? columnRenderData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA;
|
||||||
long bottomDataPoint = (i + 1) < columnRenderData.size() ? columnRenderData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA;
|
long bottomDataPoint = (i + 1) < columnRenderData.size() ? columnRenderData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA;
|
||||||
|
|
||||||
CubicLodTemplate.addLodToBuffer(data, topDataPoint, bottomDataPoint, adjColumnViews, detailLevel,
|
addLodToBuffer(
|
||||||
x, z, quadBuilder, debugMode, debugSourceFlag);
|
clientLevel,
|
||||||
|
data, topDataPoint, bottomDataPoint,
|
||||||
|
adjColumnViews, isSameDetailLevel,
|
||||||
|
thisDetailLevel, relX, relZ,
|
||||||
|
quadBuilder, debugSourceFlag);
|
||||||
}
|
}
|
||||||
|
|
||||||
}// for z
|
}// for z
|
||||||
@@ -332,33 +376,147 @@ public class ColumnRenderBufferBuilder
|
|||||||
|
|
||||||
quadBuilder.finalizeData();
|
quadBuilder.finalizeData();
|
||||||
}
|
}
|
||||||
|
private static void addLodToBuffer(
|
||||||
|
IDhClientLevel clientLevel,
|
||||||
|
long data, long topData, long bottomData,
|
||||||
//=================//
|
ColumnArrayView[] adjColumnViews, boolean[] isSameDetailLevel,
|
||||||
// vbo interaction //
|
byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ,
|
||||||
//=================//
|
LodQuadBuilder quadBuilder, ColumnRenderSource.DebugSourceFlag debugSource)
|
||||||
|
|
||||||
public static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
|
|
||||||
{
|
{
|
||||||
if (vbos.length == newSize)
|
long sectionPos = DhSectionPos.encode(detailLevel, renderSourceOffsetPosX, renderSourceOffsetPosZ);
|
||||||
|
|
||||||
|
short width = (short) BitShiftUtil.powerOfTwo(detailLevel);
|
||||||
|
short x = (short) DhSectionPos.getMinCornerBlockX(sectionPos);
|
||||||
|
short yMin = RenderDataPointUtil.getYMin(data);
|
||||||
|
short z = (short) DhSectionPos.getMinCornerBlockZ(sectionPos);
|
||||||
|
short ySize = (short) (RenderDataPointUtil.getYMax(data) - yMin);
|
||||||
|
|
||||||
|
if (ySize == 0)
|
||||||
{
|
{
|
||||||
return vbos;
|
return;
|
||||||
|
}
|
||||||
|
else if (ySize < 0)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Negative y size for the data! Data: [" + RenderDataPointUtil.toString(data) + "].");
|
||||||
}
|
}
|
||||||
|
|
||||||
GLVertexBuffer[] newVbos = new GLVertexBuffer[newSize];
|
byte blockMaterialId = RenderDataPointUtil.getBlockMaterialId(data);
|
||||||
System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize));
|
|
||||||
if (newSize < vbos.length)
|
|
||||||
|
|
||||||
|
int color;
|
||||||
|
boolean fullBright = false;
|
||||||
|
EDhApiDebugRendering debugging = Config.Client.Advanced.Debugging.debugRendering.get();
|
||||||
|
switch (debugging)
|
||||||
{
|
{
|
||||||
for (int i = newSize; i < vbos.length; i++)
|
case OFF:
|
||||||
{
|
{
|
||||||
if (vbos[i] != null)
|
float saturationMultiplier = Config.Client.Advanced.Graphics.AdvancedGraphics.saturationMultiplier.get().floatValue();
|
||||||
|
float brightnessMultiplier = Config.Client.Advanced.Graphics.AdvancedGraphics.brightnessMultiplier.get().floatValue();
|
||||||
|
if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0)
|
||||||
{
|
{
|
||||||
vbos[i].close();
|
color = RenderDataPointUtil.getColor(data);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float[] ahsv = ColorUtil.argbToAhsv(RenderDataPointUtil.getColor(data));
|
||||||
|
color = ColorUtil.ahsvToArgb(ahsv[0], ahsv[1], ahsv[2] * saturationMultiplier, ahsv[3] * brightnessMultiplier);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case SHOW_DETAIL:
|
||||||
|
{
|
||||||
|
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel];
|
||||||
|
fullBright = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SHOW_BLOCK_MATERIAL:
|
||||||
|
{
|
||||||
|
|
||||||
|
switch (EDhApiBlockMaterial.getFromIndex(blockMaterialId))
|
||||||
|
{
|
||||||
|
case UNKNOWN:
|
||||||
|
case AIR: // shouldn't normally be rendered, but just in case
|
||||||
|
color = ColorUtil.HOT_PINK;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LEAVES:
|
||||||
|
color = ColorUtil.DARK_GREEN;
|
||||||
|
break;
|
||||||
|
case STONE:
|
||||||
|
color = ColorUtil.GRAY;
|
||||||
|
break;
|
||||||
|
case WOOD:
|
||||||
|
color = ColorUtil.BROWN;
|
||||||
|
break;
|
||||||
|
case METAL:
|
||||||
|
color = ColorUtil.DARK_GRAY;
|
||||||
|
break;
|
||||||
|
case DIRT:
|
||||||
|
color = ColorUtil.LIGHT_BROWN;
|
||||||
|
break;
|
||||||
|
case LAVA:
|
||||||
|
color = ColorUtil.ORANGE;
|
||||||
|
break;
|
||||||
|
case DEEPSLATE:
|
||||||
|
color = ColorUtil.BLACK;
|
||||||
|
break;
|
||||||
|
case SNOW:
|
||||||
|
color = ColorUtil.WHITE;
|
||||||
|
break;
|
||||||
|
case SAND:
|
||||||
|
color = ColorUtil.TAN;
|
||||||
|
break;
|
||||||
|
case TERRACOTTA:
|
||||||
|
color = ColorUtil.DARK_ORANGE;
|
||||||
|
break;
|
||||||
|
case NETHER_STONE:
|
||||||
|
color = ColorUtil.DARK_RED;
|
||||||
|
break;
|
||||||
|
case WATER:
|
||||||
|
color = ColorUtil.BLUE;
|
||||||
|
break;
|
||||||
|
case GRASS:
|
||||||
|
color = ColorUtil.GREEN;
|
||||||
|
break;
|
||||||
|
case ILLUMINATED:
|
||||||
|
color = ColorUtil.YELLOW;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// undefined color
|
||||||
|
color = ColorUtil.CYAN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fullBright = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SHOW_OVERLAPPING_QUADS:
|
||||||
|
{
|
||||||
|
color = ColorUtil.WHITE;
|
||||||
|
fullBright = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SHOW_RENDER_SOURCE_FLAG:
|
||||||
|
{
|
||||||
|
color = debugSource == null ? ColorUtil.RED : debugSource.color;
|
||||||
|
fullBright = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown debug mode: " + debugging);
|
||||||
}
|
}
|
||||||
return newVbos;
|
|
||||||
|
ColumnBox.addBoxQuadsToBuilder(
|
||||||
|
quadBuilder, clientLevel,
|
||||||
|
width, ySize, width,
|
||||||
|
x, yMin, z,
|
||||||
|
color,
|
||||||
|
blockMaterialId,
|
||||||
|
RenderDataPointUtil.getLightSky(data),
|
||||||
|
fullBright ? 15 : RenderDataPointUtil.getLightBlock(data),
|
||||||
|
topData, bottomData, adjColumnViews, isSameDetailLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,186 +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.bufferBuilding;
|
|
||||||
|
|
||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
|
||||||
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
|
|
||||||
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.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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds LODs as rectangular prisms.
|
|
||||||
*
|
|
||||||
* @author James Seibel
|
|
||||||
* @version 2022-1-2
|
|
||||||
*/
|
|
||||||
public class CubicLodTemplate
|
|
||||||
{
|
|
||||||
|
|
||||||
public static void addLodToBuffer(
|
|
||||||
long data, long topData, long bottomData, ColumnArrayView[][] adjColumnViews,
|
|
||||||
byte detailLevel, int offsetPosX, int offsetOosZ, LodQuadBuilder quadBuilder,
|
|
||||||
EDhApiDebugRendering debugging, ColumnRenderSource.DebugSourceFlag debugSource)
|
|
||||||
{
|
|
||||||
DhLodPos blockOffsetPos = new DhLodPos(detailLevel, offsetPosX, offsetOosZ).convertToDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL);
|
|
||||||
|
|
||||||
short width = (short) BitShiftUtil.powerOfTwo(detailLevel);
|
|
||||||
short x = (short) blockOffsetPos.x;
|
|
||||||
short yMin = RenderDataPointUtil.getYMin(data);
|
|
||||||
short z = (short) (short) blockOffsetPos.z;
|
|
||||||
short ySize = (short) (RenderDataPointUtil.getYMax(data) - yMin);
|
|
||||||
|
|
||||||
if (ySize == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (ySize < 0)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Negative y size for the data! Data: " + RenderDataPointUtil.toString(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
byte blockMaterialId = RenderDataPointUtil.getBlockMaterialId(data);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int color;
|
|
||||||
boolean fullBright = false;
|
|
||||||
switch (debugging)
|
|
||||||
{
|
|
||||||
case OFF:
|
|
||||||
{
|
|
||||||
float saturationMultiplier = Config.Client.Advanced.Graphics.AdvancedGraphics.saturationMultiplier.get().floatValue();
|
|
||||||
float brightnessMultiplier = Config.Client.Advanced.Graphics.AdvancedGraphics.brightnessMultiplier.get().floatValue();
|
|
||||||
if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0)
|
|
||||||
{
|
|
||||||
color = RenderDataPointUtil.getColor(data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
float[] ahsv = ColorUtil.argbToAhsv(RenderDataPointUtil.getColor(data));
|
|
||||||
color = ColorUtil.ahsvToArgb(ahsv[0], ahsv[1], ahsv[2] * saturationMultiplier, ahsv[3] * brightnessMultiplier);
|
|
||||||
//ApiShared.LOGGER.info("Raw color:[{}], AHSV:{}, Out color:[{}]",
|
|
||||||
// ColorUtil.toString(DataPointUtil.getColor(data)),
|
|
||||||
// ahsv, ColorUtil.toString(color));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SHOW_DETAIL:
|
|
||||||
{
|
|
||||||
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel];
|
|
||||||
fullBright = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SHOW_BLOCK_MATERIAL:
|
|
||||||
{
|
|
||||||
|
|
||||||
switch (EDhApiBlockMaterial.getFromIndex(blockMaterialId))
|
|
||||||
{
|
|
||||||
case UNKNOWN:
|
|
||||||
case AIR: // shouldn't normally be rendered, but just in case
|
|
||||||
color = ColorUtil.HOT_PINK;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LEAVES:
|
|
||||||
color = ColorUtil.DARK_GREEN;
|
|
||||||
break;
|
|
||||||
case STONE:
|
|
||||||
color = ColorUtil.GRAY;
|
|
||||||
break;
|
|
||||||
case WOOD:
|
|
||||||
color = ColorUtil.BROWN;
|
|
||||||
break;
|
|
||||||
case METAL:
|
|
||||||
color = ColorUtil.DARK_GRAY;
|
|
||||||
break;
|
|
||||||
case DIRT:
|
|
||||||
color = ColorUtil.LIGHT_BROWN;
|
|
||||||
break;
|
|
||||||
case LAVA:
|
|
||||||
color = ColorUtil.ORANGE;
|
|
||||||
break;
|
|
||||||
case DEEPSLATE:
|
|
||||||
color = ColorUtil.BLACK;
|
|
||||||
break;
|
|
||||||
case SNOW:
|
|
||||||
color = ColorUtil.WHITE;
|
|
||||||
break;
|
|
||||||
case SAND:
|
|
||||||
color = ColorUtil.TAN;
|
|
||||||
break;
|
|
||||||
case TERRACOTTA:
|
|
||||||
color = ColorUtil.DARK_ORANGE;
|
|
||||||
break;
|
|
||||||
case NETHER_STONE:
|
|
||||||
color = ColorUtil.DARK_RED;
|
|
||||||
break;
|
|
||||||
case WATER:
|
|
||||||
color = ColorUtil.BLUE;
|
|
||||||
break;
|
|
||||||
case GRASS:
|
|
||||||
color = ColorUtil.GREEN;
|
|
||||||
break;
|
|
||||||
case ILLUMINATED:
|
|
||||||
color = ColorUtil.YELLOW;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// undefined color
|
|
||||||
color = ColorUtil.CYAN;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
fullBright = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SHOW_OVERLAPPING_QUADS:
|
|
||||||
{
|
|
||||||
color = ColorUtil.WHITE;
|
|
||||||
fullBright = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SHOW_RENDER_SOURCE_FLAG:
|
|
||||||
{
|
|
||||||
color = debugSource == null ? ColorUtil.RED : debugSource.color;
|
|
||||||
fullBright = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unknown debug mode: " + debugging);
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnBox.addBoxQuadsToBuilder(
|
|
||||||
quadBuilder, // buffer
|
|
||||||
width, ySize, width, // setWidth
|
|
||||||
x, yMin, z, // setOffset
|
|
||||||
color, // setColor
|
|
||||||
blockMaterialId, // irisBlockMaterialId
|
|
||||||
RenderDataPointUtil.getLightSky(data), // setSkyLights
|
|
||||||
fullBright ? 15 : RenderDataPointUtil.getLightBlock(data), // setBlockLights
|
|
||||||
topData, bottomData, adjColumnViews); // setAdjData
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -38,8 +38,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
//TODO: Recheck this class for refactoring
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to create the quads before they are converted to render-able buffers. <br><br>
|
* Used to create the quads before they are converted to render-able buffers. <br><br>
|
||||||
@@ -51,9 +50,6 @@ public class LodQuadBuilder
|
|||||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||||
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
|
|
||||||
public final boolean skipQuadsWithZeroSkylight;
|
|
||||||
public final short skyLightCullingBelow;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
|
private final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -123,7 +119,7 @@ public class LodQuadBuilder
|
|||||||
// constructor //
|
// constructor //
|
||||||
//=============//
|
//=============//
|
||||||
|
|
||||||
public LodQuadBuilder(boolean enableSkylightCulling, short skyLightCullingBelow, boolean doTransparency, IClientLevelWrapper clientLevelWrapper)
|
public LodQuadBuilder(boolean doTransparency, IClientLevelWrapper clientLevelWrapper)
|
||||||
{
|
{
|
||||||
this.doTransparency = doTransparency;
|
this.doTransparency = doTransparency;
|
||||||
for (int i = 0; i < 6; i++)
|
for (int i = 0; i < 6; i++)
|
||||||
@@ -132,8 +128,6 @@ public class LodQuadBuilder
|
|||||||
this.transparentQuads[i] = new ArrayList<>();
|
this.transparentQuads[i] = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.skipQuadsWithZeroSkylight = enableSkylightCulling;
|
|
||||||
this.skyLightCullingBelow = skyLightCullingBelow;
|
|
||||||
this.clientLevelWrapper = clientLevelWrapper;
|
this.clientLevelWrapper = clientLevelWrapper;
|
||||||
|
|
||||||
this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRendering.get();
|
this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRendering.get();
|
||||||
@@ -157,11 +151,6 @@ public class LodQuadBuilder
|
|||||||
throw new IllegalArgumentException("addQuadAdj() is only for adj direction! Not UP or Down!");
|
throw new IllegalArgumentException("addQuadAdj() is only for adj direction! Not UP or Down!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.skipQuadsWithZeroSkylight && skyLight == 0 && y + widthNorthSouthOrUpDown < this.skyLightCullingBelow)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferQuad quad = new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skyLight, blockLight, dir);
|
BufferQuad quad = new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skyLight, blockLight, dir);
|
||||||
ArrayList<BufferQuad> quadList = (this.doTransparency && ColorUtil.getAlpha(color) < 255) ? this.transparentQuads[dir.ordinal()] : this.opaqueQuads[dir.ordinal()];
|
ArrayList<BufferQuad> quadList = (this.doTransparency && ColorUtil.getAlpha(color) < 255) ? this.transparentQuads[dir.ordinal()] : this.opaqueQuads[dir.ordinal()];
|
||||||
if (!quadList.isEmpty() &&
|
if (!quadList.isEmpty() &&
|
||||||
@@ -180,12 +169,6 @@ public class LodQuadBuilder
|
|||||||
// XZ
|
// XZ
|
||||||
public void addQuadUp(short x, short maxY, short z, short widthEastWest, short widthNorthSouthOrUpDown, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) // TODO argument names are wrong
|
public void addQuadUp(short x, short maxY, short z, short widthEastWest, short widthNorthSouthOrUpDown, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) // TODO argument names are wrong
|
||||||
{
|
{
|
||||||
// cave culling
|
|
||||||
if (this.skipQuadsWithZeroSkylight && skylight == 0 && maxY < this.skyLightCullingBelow)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferQuad quad = new BufferQuad(x, maxY, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP);
|
BufferQuad quad = new BufferQuad(x, maxY, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP);
|
||||||
boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255);
|
boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255);
|
||||||
ArrayList<BufferQuad> quadList = isTransparent ? this.transparentQuads[EDhDirection.UP.ordinal()] : this.opaqueQuads[EDhDirection.UP.ordinal()];
|
ArrayList<BufferQuad> quadList = isTransparent ? this.transparentQuads[EDhDirection.UP.ordinal()] : this.opaqueQuads[EDhDirection.UP.ordinal()];
|
||||||
@@ -207,15 +190,13 @@ public class LodQuadBuilder
|
|||||||
|
|
||||||
public void addQuadDown(short x, short y, short z, short width, short wz, int color, byte irisBlockMaterialId, byte skylight, byte blocklight)
|
public void addQuadDown(short x, short y, short z, short width, short wz, int color, byte irisBlockMaterialId, byte skylight, byte blocklight)
|
||||||
{
|
{
|
||||||
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
|
|
||||||
return;
|
|
||||||
BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN);
|
BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN);
|
||||||
ArrayList<BufferQuad> qs = (doTransparency && ColorUtil.getAlpha(color) < 255)
|
ArrayList<BufferQuad> qs = (doTransparency && ColorUtil.getAlpha(color) < 255)
|
||||||
? transparentQuads[EDhDirection.DOWN.ordinal()] : opaqueQuads[EDhDirection.DOWN.ordinal()];
|
? transparentQuads[EDhDirection.DOWN.ordinal()] : opaqueQuads[EDhDirection.DOWN.ordinal()];
|
||||||
if (!qs.isEmpty() &&
|
if (!qs.isEmpty()
|
||||||
(qs.get(qs.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
|
&& (qs.get(qs.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
|
||||||
|| qs.get(qs.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown))
|
|| qs.get(qs.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown))
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
premergeCount++;
|
premergeCount++;
|
||||||
return;
|
return;
|
||||||
@@ -225,10 +206,124 @@ public class LodQuadBuilder
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=================//
|
||||||
|
// data finalizing //
|
||||||
|
//=================//
|
||||||
|
|
||||||
|
/** runs any final data cleanup, merging, etc. */
|
||||||
|
public void finalizeData() { this.mergeQuads(); }
|
||||||
|
|
||||||
|
/** Uses Greedy meshing to merge this builder's Quads. */
|
||||||
|
public void mergeQuads()
|
||||||
|
{
|
||||||
|
long mergeCount = 0; // can be used for debugging
|
||||||
|
long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
|
||||||
|
if (preQuadsCount <= 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
|
||||||
|
{
|
||||||
|
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
|
||||||
|
if (this.doTransparency)
|
||||||
|
{
|
||||||
|
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// only run the second merge if the face is the top or bottom
|
||||||
|
if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal())
|
||||||
|
{
|
||||||
|
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
|
||||||
|
if (this.doTransparency)
|
||||||
|
{
|
||||||
|
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
|
||||||
|
//LOGGER.trace("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
|
||||||
|
private static long mergeQuadsInternal(ArrayList<BufferQuad>[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection)
|
||||||
|
{
|
||||||
|
if (list[directionIndex].size() <= 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection));
|
||||||
|
|
||||||
|
long mergeCount = 0;
|
||||||
|
ListIterator<BufferQuad> iter = list[directionIndex].listIterator();
|
||||||
|
BufferQuad currentQuad = iter.next();
|
||||||
|
while (iter.hasNext())
|
||||||
|
{
|
||||||
|
BufferQuad nextQuad = iter.next();
|
||||||
|
|
||||||
|
if (currentQuad.tryMerge(nextQuad, mergeDirection))
|
||||||
|
{
|
||||||
|
// merge successful, attempt to merge the next quad
|
||||||
|
mergeCount++;
|
||||||
|
iter.set(null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// merge fail, move on to the next quad
|
||||||
|
currentQuad = nextQuad;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list[directionIndex].removeIf(Objects::isNull);
|
||||||
|
return mergeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//==============//
|
||||||
// add vertices //
|
// buffer setup //
|
||||||
//==============//
|
//==============//
|
||||||
|
|
||||||
|
public ArrayList<ByteBuffer> makeOpaqueVertexBuffers() { return this.makeVertexBuffers(this.opaqueQuads); }
|
||||||
|
public ArrayList<ByteBuffer> makeTransparentVertexBuffers() { return this.makeVertexBuffers(this.transparentQuads); }
|
||||||
|
private ArrayList<ByteBuffer> makeVertexBuffers(ArrayList<BufferQuad>[] quadList)
|
||||||
|
{
|
||||||
|
ArrayList<ByteBuffer> byteBufferList = new ArrayList<>(3);
|
||||||
|
|
||||||
|
ByteBuffer buffer = null;
|
||||||
|
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
|
||||||
|
{
|
||||||
|
// ignore empty directions
|
||||||
|
if (quadList[directionIndex].isEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// put all the quads in this direction into the buffer
|
||||||
|
for (int quadIndex = 0; quadIndex < quadList[directionIndex].size(); quadIndex++)
|
||||||
|
{
|
||||||
|
// if this is the first iteration or the buffer is full,
|
||||||
|
// create a new buffer
|
||||||
|
if (buffer == null || !buffer.hasRemaining())
|
||||||
|
{
|
||||||
|
buffer = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER);
|
||||||
|
byteBufferList.add(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.putQuad(buffer, quadList[directionIndex].get(quadIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewind all the buffers so they can be read from
|
||||||
|
for (int i = 0; i < byteBufferList.size(); i++)
|
||||||
|
{
|
||||||
|
buffer = byteBufferList.get(i);
|
||||||
|
buffer.limit(buffer.position());
|
||||||
|
buffer.rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteBufferList;
|
||||||
|
}
|
||||||
private void putQuad(ByteBuffer bb, BufferQuad quad)
|
private void putQuad(ByteBuffer bb, BufferQuad quad)
|
||||||
{
|
{
|
||||||
int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()];
|
int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()];
|
||||||
@@ -286,10 +381,10 @@ public class LodQuadBuilder
|
|||||||
if (quad.direction.getAxis().isHorizontal() || quad.direction == EDhDirection.DOWN)
|
if (quad.direction.getAxis().isHorizontal() || quad.direction == EDhDirection.DOWN)
|
||||||
{
|
{
|
||||||
if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT
|
if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT
|
||||||
// if we want the color to fade, only apply the dirt color to the bottom vertices
|
// 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)
|
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
|
||||||
// always render the bottom as dirt
|
// always render the bottom as dirt
|
||||||
|| quad.direction == EDhDirection.DOWN)
|
|| quad.direction == EDhDirection.DOWN)
|
||||||
{
|
{
|
||||||
// for horizontal and bottom faces of grass blocks, use the dirt color to
|
// for horizontal and bottom faces of grass blocks, use the dirt color to
|
||||||
// prevent green cliff walls
|
// prevent green cliff walls
|
||||||
@@ -297,7 +392,7 @@ public class LodQuadBuilder
|
|||||||
color = ColorUtil.applyShade(color, MC.getShade(quad.direction));
|
color = ColorUtil.applyShade(color, MC.getShade(quad.direction));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +406,6 @@ public class LodQuadBuilder
|
|||||||
mx, my, mz);
|
mx, my, mz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte normalIndex, byte irisBlockMaterialId, byte skylight, byte blocklight, int mx, int my, int mz)
|
private void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte normalIndex, byte irisBlockMaterialId, byte skylight, byte blocklight, int mx, int my, int mz)
|
||||||
{
|
{
|
||||||
skylight %= 16;
|
skylight %= 16;
|
||||||
@@ -352,389 +446,6 @@ public class LodQuadBuilder
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=================//
|
|
||||||
// data finalizing //
|
|
||||||
//=================//
|
|
||||||
|
|
||||||
/** runs any final data cleanup, merging, etc. */
|
|
||||||
public void finalizeData() { this.mergeQuads(); }
|
|
||||||
|
|
||||||
/** Uses Greedy meshing to merge this builder's Quads. */
|
|
||||||
public void mergeQuads()
|
|
||||||
{
|
|
||||||
long mergeCount = 0;
|
|
||||||
long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
|
|
||||||
if (preQuadsCount <= 1)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
|
|
||||||
{
|
|
||||||
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
|
|
||||||
if (this.doTransparency)
|
|
||||||
{
|
|
||||||
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// only run the second merge if the face is the top or bottom
|
|
||||||
if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal())
|
|
||||||
{
|
|
||||||
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
|
|
||||||
if (this.doTransparency)
|
|
||||||
{
|
|
||||||
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
|
|
||||||
LOGGER.debug("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
|
|
||||||
private static long mergeQuadsInternal(ArrayList<BufferQuad>[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection)
|
|
||||||
{
|
|
||||||
if (list[directionIndex].size() <= 1)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection));
|
|
||||||
|
|
||||||
long mergeCount = 0;
|
|
||||||
ListIterator<BufferQuad> iter = list[directionIndex].listIterator();
|
|
||||||
BufferQuad currentQuad = iter.next();
|
|
||||||
while (iter.hasNext())
|
|
||||||
{
|
|
||||||
BufferQuad nextQuad = iter.next();
|
|
||||||
|
|
||||||
if (currentQuad.tryMerge(nextQuad, mergeDirection))
|
|
||||||
{
|
|
||||||
// merge successful, attempt to merge the next quad
|
|
||||||
mergeCount++;
|
|
||||||
iter.set(null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// merge fail, move on to the next quad
|
|
||||||
currentQuad = nextQuad;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list[directionIndex].removeIf(Objects::isNull);
|
|
||||||
return mergeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
|
||||||
// buffer setup //
|
|
||||||
//==============//
|
|
||||||
|
|
||||||
public Iterator<ByteBuffer> makeOpaqueVertexBuffers()
|
|
||||||
{
|
|
||||||
return new Iterator<ByteBuffer>()
|
|
||||||
{
|
|
||||||
final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER)
|
|
||||||
.order(ByteOrder.nativeOrder());
|
|
||||||
int dir = skipEmpty(0);
|
|
||||||
int quad = 0;
|
|
||||||
|
|
||||||
private int skipEmpty(int d)
|
|
||||||
{
|
|
||||||
while (d < 6 && opaqueQuads[d].isEmpty())
|
|
||||||
{
|
|
||||||
d++;
|
|
||||||
}
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext()
|
|
||||||
{
|
|
||||||
return dir < 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteBuffer next()
|
|
||||||
{
|
|
||||||
if (dir >= 6)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
bb.clear();
|
|
||||||
bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER);
|
|
||||||
while (bb.hasRemaining() && dir < 6)
|
|
||||||
{
|
|
||||||
writeData();
|
|
||||||
}
|
|
||||||
bb.limit(bb.position());
|
|
||||||
bb.rewind();
|
|
||||||
return bb;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeData()
|
|
||||||
{
|
|
||||||
int i = quad;
|
|
||||||
for (; i < opaqueQuads[dir].size(); i++)
|
|
||||||
{
|
|
||||||
if (!bb.hasRemaining())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
putQuad(bb, opaqueQuads[dir].get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i >= opaqueQuads[dir].size())
|
|
||||||
{
|
|
||||||
quad = 0;
|
|
||||||
dir++;
|
|
||||||
dir = skipEmpty(dir);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
quad = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterator<ByteBuffer> makeTransparentVertexBuffers()
|
|
||||||
{
|
|
||||||
return new Iterator<ByteBuffer>()
|
|
||||||
{
|
|
||||||
final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER)
|
|
||||||
.order(ByteOrder.nativeOrder());
|
|
||||||
int directionIndex = this.skipEmptyDirectionIndices(0);
|
|
||||||
int quad = 0;
|
|
||||||
|
|
||||||
private int skipEmptyDirectionIndices(int directionIndex)
|
|
||||||
{
|
|
||||||
while (directionIndex < 6 &&
|
|
||||||
(LodQuadBuilder.this.transparentQuads[directionIndex] == null
|
|
||||||
|| LodQuadBuilder.this.transparentQuads[directionIndex].isEmpty()))
|
|
||||||
{
|
|
||||||
directionIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return directionIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() { return this.directionIndex < 6; }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteBuffer next()
|
|
||||||
{
|
|
||||||
if (this.directionIndex >= 6)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.bb.clear();
|
|
||||||
this.bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER);
|
|
||||||
while (this.bb.hasRemaining() && this.directionIndex < 6)
|
|
||||||
{
|
|
||||||
this.writeData();
|
|
||||||
}
|
|
||||||
this.bb.limit(this.bb.position());
|
|
||||||
this.bb.rewind();
|
|
||||||
return this.bb;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeData()
|
|
||||||
{
|
|
||||||
int i = this.quad;
|
|
||||||
for (; i < LodQuadBuilder.this.transparentQuads[this.directionIndex].size(); i++)
|
|
||||||
{
|
|
||||||
if (!this.bb.hasRemaining())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
putQuad(this.bb, LodQuadBuilder.this.transparentQuads[this.directionIndex].get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i >= LodQuadBuilder.this.transparentQuads[this.directionIndex].size())
|
|
||||||
{
|
|
||||||
this.quad = 0;
|
|
||||||
this.directionIndex++;
|
|
||||||
this.directionIndex = this.skipEmptyDirectionIndices(this.directionIndex);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.quad = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
public interface BufferFiller
|
|
||||||
{
|
|
||||||
/** If true: more data needs to be filled */
|
|
||||||
boolean fill(GLVertexBuffer vbo);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public BufferFiller makeOpaqueBufferFiller(EDhApiGpuUploadMethod method)
|
|
||||||
{
|
|
||||||
return new BufferFiller()
|
|
||||||
{
|
|
||||||
int dir = 0;
|
|
||||||
int quad = 0;
|
|
||||||
|
|
||||||
public boolean fill(GLVertexBuffer vbo)
|
|
||||||
{
|
|
||||||
if (dir >= 6)
|
|
||||||
{
|
|
||||||
vbo.setVertexCount(0);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int numOfQuads = _countRemainingQuads();
|
|
||||||
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 * ColumnRenderBuffer.QUADS_BYTE_SIZE, method,
|
|
||||||
ColumnRenderBuffer.FULL_SIZED_BUFFER);
|
|
||||||
if (bb == null)
|
|
||||||
throw new NullPointerException("mapBuffer returned null");
|
|
||||||
bb.clear();
|
|
||||||
bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE);
|
|
||||||
while (bb.hasRemaining() && dir < 6)
|
|
||||||
{
|
|
||||||
writeData(bb);
|
|
||||||
}
|
|
||||||
bb.rewind();
|
|
||||||
vbo.unmapBuffer();
|
|
||||||
vbo.setVertexCount(numOfQuads * 4);
|
|
||||||
return dir < 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int _countRemainingQuads()
|
|
||||||
{
|
|
||||||
int a = opaqueQuads[dir].size() - quad;
|
|
||||||
for (int i = dir + 1; i < opaqueQuads.length; i++)
|
|
||||||
{
|
|
||||||
a += opaqueQuads[i].size();
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeData(ByteBuffer bb)
|
|
||||||
{
|
|
||||||
int startQ = quad;
|
|
||||||
|
|
||||||
int i = startQ;
|
|
||||||
for (i = startQ; i < opaqueQuads[dir].size(); i++)
|
|
||||||
{
|
|
||||||
if (!bb.hasRemaining())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
putQuad(bb, opaqueQuads[dir].get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i >= opaqueQuads[dir].size())
|
|
||||||
{
|
|
||||||
quad = 0;
|
|
||||||
dir++;
|
|
||||||
while (dir < 6 && opaqueQuads[dir].isEmpty())
|
|
||||||
{
|
|
||||||
dir++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
quad = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public BufferFiller makeTransparentBufferFiller(EDhApiGpuUploadMethod method)
|
|
||||||
{
|
|
||||||
return new BufferFiller()
|
|
||||||
{
|
|
||||||
int dir = 0;
|
|
||||||
int quad = 0;
|
|
||||||
|
|
||||||
public boolean fill(GLVertexBuffer vbo)
|
|
||||||
{
|
|
||||||
if (dir >= 6)
|
|
||||||
{
|
|
||||||
vbo.setVertexCount(0);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int numOfQuads = _countRemainingQuads();
|
|
||||||
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 * ColumnRenderBuffer.QUADS_BYTE_SIZE, method,
|
|
||||||
ColumnRenderBuffer.FULL_SIZED_BUFFER);
|
|
||||||
if (bb == null)
|
|
||||||
throw new NullPointerException("mapBuffer returned null");
|
|
||||||
bb.clear();
|
|
||||||
bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE);
|
|
||||||
while (bb.hasRemaining() && dir < 6)
|
|
||||||
{
|
|
||||||
writeData(bb);
|
|
||||||
}
|
|
||||||
bb.rewind();
|
|
||||||
vbo.unmapBuffer();
|
|
||||||
vbo.setVertexCount(numOfQuads * 4);
|
|
||||||
return dir < 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int _countRemainingQuads()
|
|
||||||
{
|
|
||||||
int a = transparentQuads[dir].size() - quad;
|
|
||||||
for (int i = dir + 1; i < transparentQuads.length; i++)
|
|
||||||
{
|
|
||||||
a += transparentQuads[i].size();
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeData(ByteBuffer bb)
|
|
||||||
{
|
|
||||||
int startQ = quad;
|
|
||||||
|
|
||||||
int i = startQ;
|
|
||||||
for (i = startQ; i < transparentQuads[dir].size(); i++)
|
|
||||||
{
|
|
||||||
if (!bb.hasRemaining())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
putQuad(bb, transparentQuads[dir].get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i >= transparentQuads[dir].size())
|
|
||||||
{
|
|
||||||
quad = 0;
|
|
||||||
dir++;
|
|
||||||
while (dir < 6 && transparentQuads[dir].isEmpty())
|
|
||||||
{
|
|
||||||
dir++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
quad = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=========//
|
//=========//
|
||||||
// getters //
|
// getters //
|
||||||
//=========//
|
//=========//
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package com.seibel.distanthorizons.core.dataObjects.render.columnViews;
|
package com.seibel.distanthorizons.core.dataObjects.render.columnViews;
|
||||||
|
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
|
||||||
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
|
|
||||||
@@ -28,41 +29,60 @@ import java.util.Arrays;
|
|||||||
public final class ColumnArrayView implements IColumnDataView
|
public final class ColumnArrayView implements IColumnDataView
|
||||||
{
|
{
|
||||||
public final LongArrayList data;
|
public final LongArrayList data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many data points are currently being represented by this view. <br>
|
||||||
|
* Will be equal to or less than {@link ColumnArrayView#verticalSize}.
|
||||||
|
*/
|
||||||
public final int size;
|
public final int size;
|
||||||
public final int offset; // offset in longs
|
/**
|
||||||
/** can be 0 if this column was created for an empty data source */
|
* Vertical size in data points. <Br>
|
||||||
public final int vertSize; // vertical size in longs
|
* Can be 0 if this column was created for an empty data source.
|
||||||
|
*/
|
||||||
|
public final int verticalSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where the relative starting index is in the {@link ColumnArrayView#data} array
|
||||||
|
* if this view is representing part of a {@link ColumnRenderSource}.
|
||||||
|
*/
|
||||||
|
public final int offset;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public ColumnArrayView(LongArrayList data, int size, int offset, int vertSize)
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
|
||||||
|
public ColumnArrayView(LongArrayList data, int size, int offset, int verticalSize)
|
||||||
{
|
{
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
this.vertSize = vertSize;
|
this.verticalSize = verticalSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=====================//
|
||||||
|
// getters and setters //
|
||||||
|
//=====================//
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long get(int index) { return data.getLong(index + offset); }
|
public long get(int index) { return data.getLong(index + offset); }
|
||||||
|
|
||||||
public void set(int index, long value) { data.set(index + offset, value); }
|
public void set(int index, long value) { data.set(index + offset, value); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size() { return size; }
|
public int size() { return size; }
|
||||||
|
@Override
|
||||||
|
public int verticalSize() { return verticalSize; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int verticalSize() { return vertSize; }
|
public int dataCount() { return (this.verticalSize != 0) ? (this.size / this.verticalSize) : 0; } // TODO what does the divide by mean?
|
||||||
|
|
||||||
@Override
|
|
||||||
public int dataCount() { return (this.vertSize != 0) ? (this.size / this.vertSize) : 0; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ColumnArrayView subView(int dataIndexStart, int dataCount)
|
public ColumnArrayView subView(int dataIndexStart, int dataCount)
|
||||||
{
|
{
|
||||||
return new ColumnArrayView(data, dataCount * vertSize, offset + dataIndexStart * vertSize, vertSize);
|
return new ColumnArrayView(data, dataCount * verticalSize, offset + dataIndexStart * verticalSize, verticalSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void fill(long value) { Arrays.fill(data.elements(), offset, offset + size, value); }
|
public void fill(long value) { Arrays.fill(data.elements(), offset, offset + size, value); }
|
||||||
@@ -70,7 +90,7 @@ public final class ColumnArrayView implements IColumnDataView
|
|||||||
public void copyFrom(IColumnDataView source) { copyFrom(source, 0); }
|
public void copyFrom(IColumnDataView source) { copyFrom(source, 0); }
|
||||||
public void copyFrom(IColumnDataView source, int outputDataIndexOffset)
|
public void copyFrom(IColumnDataView source, int outputDataIndexOffset)
|
||||||
{
|
{
|
||||||
if (source.verticalSize() > vertSize)
|
if (source.verticalSize() > verticalSize)
|
||||||
{
|
{
|
||||||
throw new IllegalArgumentException("source verticalSize must be <= self's verticalSize to copy");
|
throw new IllegalArgumentException("source verticalSize must be <= self's verticalSize to copy");
|
||||||
}
|
}
|
||||||
@@ -78,19 +98,19 @@ public final class ColumnArrayView implements IColumnDataView
|
|||||||
{
|
{
|
||||||
throw new IllegalArgumentException("dataIndexStart + source.dataCount() must be <= self.dataCount() to copy");
|
throw new IllegalArgumentException("dataIndexStart + source.dataCount() must be <= self.dataCount() to copy");
|
||||||
}
|
}
|
||||||
else if (source.verticalSize() != vertSize)
|
else if (source.verticalSize() != verticalSize)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < source.dataCount(); i++)
|
for (int i = 0; i < source.dataCount(); i++)
|
||||||
{
|
{
|
||||||
int outputOffset = offset + outputDataIndexOffset * vertSize + i * vertSize;
|
int outputOffset = offset + outputDataIndexOffset * verticalSize + i * verticalSize;
|
||||||
source.subView(i, 1).copyTo(data.elements(), outputOffset, source.verticalSize());
|
source.subView(i, 1).copyTo(data.elements(), outputOffset, source.verticalSize());
|
||||||
Arrays.fill(data.elements(), outputOffset + source.verticalSize(),
|
Arrays.fill(data.elements(), outputOffset + source.verticalSize(),
|
||||||
outputOffset + vertSize, 0);
|
outputOffset + verticalSize, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
source.copyTo(data.elements(), offset + outputDataIndexOffset * vertSize, source.size());
|
source.copyTo(data.elements(), offset + outputDataIndexOffset * verticalSize, source.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,19 +123,19 @@ public final class ColumnArrayView implements IColumnDataView
|
|||||||
{
|
{
|
||||||
throw new IllegalArgumentException("Cannot merge views of different sizes");
|
throw new IllegalArgumentException("Cannot merge views of different sizes");
|
||||||
}
|
}
|
||||||
if (vertSize != source.vertSize)
|
if (verticalSize != source.verticalSize)
|
||||||
{
|
{
|
||||||
throw new IllegalArgumentException("Cannot merge views of different vertical sizes");
|
throw new IllegalArgumentException("Cannot merge views of different vertical sizes");
|
||||||
}
|
}
|
||||||
boolean anyChange = false;
|
boolean anyChange = false;
|
||||||
for (int o = 0; o < (source.size() * vertSize); o += vertSize)
|
for (int o = 0; o < (source.size() * verticalSize); o += verticalSize)
|
||||||
{
|
{
|
||||||
if (override)
|
if (override)
|
||||||
{
|
{
|
||||||
if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) >= 0)
|
if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) >= 0)
|
||||||
{
|
{
|
||||||
anyChange = true;
|
anyChange = true;
|
||||||
System.arraycopy(source.data, source.offset + o, data, offset + o, vertSize);
|
System.arraycopy(source.data, source.offset + o, data, offset + o, verticalSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -123,7 +143,7 @@ public final class ColumnArrayView implements IColumnDataView
|
|||||||
if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) > 0)
|
if (RenderDataPointUtil.compareDatapointPriority(source.get(o), get(o)) > 0)
|
||||||
{
|
{
|
||||||
anyChange = true;
|
anyChange = true;
|
||||||
System.arraycopy(source.data, source.offset + o, data, offset + o, vertSize);
|
System.arraycopy(source.data, source.offset + o, data, offset + o, verticalSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,7 +157,7 @@ public final class ColumnArrayView implements IColumnDataView
|
|||||||
throw new IllegalArgumentException("Cannot copy and resize to views with different dataCounts");
|
throw new IllegalArgumentException("Cannot copy and resize to views with different dataCounts");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.vertSize >= source.verticalSize())
|
if (this.verticalSize >= source.verticalSize())
|
||||||
{
|
{
|
||||||
this.copyFrom(source);
|
this.copyFrom(source);
|
||||||
}
|
}
|
||||||
@@ -160,12 +180,18 @@ public final class ColumnArrayView implements IColumnDataView
|
|||||||
RenderDataPointUtil.mergeMultiData(source, this);
|
RenderDataPointUtil.mergeMultiData(source, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// base overrides //
|
||||||
|
//================//
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("S:").append(size);
|
sb.append("S:").append(size);
|
||||||
sb.append(" V:").append(vertSize);
|
sb.append(" V:").append(verticalSize);
|
||||||
sb.append(" O:").append(offset);
|
sb.append(" O:").append(offset);
|
||||||
|
|
||||||
sb.append(" [");
|
sb.append(" [");
|
||||||
@@ -182,6 +208,7 @@ public final class ColumnArrayView implements IColumnDataView
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int getDataHash()
|
public int getDataHash()
|
||||||
{
|
{
|
||||||
return arrayHash(data, offset, size);
|
return arrayHash(data, offset, size);
|
||||||
|
|||||||
@@ -1,253 +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.transformers;
|
|
||||||
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
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.logging.ConfigBasedLogger;
|
|
||||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
|
||||||
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;
|
|
||||||
|
|
||||||
public class ChunkToLodBuilder implements AutoCloseable
|
|
||||||
{
|
|
||||||
public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), () -> Config.Client.Advanced.Logging.logLodBuilderEvent.get());
|
|
||||||
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
|
||||||
|
|
||||||
public static final long MAX_TICK_TIME_NS = 1000000000L / 20L;
|
|
||||||
|
|
||||||
private final ConcurrentHashMap<DhChunkPos, IChunkWrapper> concurrentChunkToBuildByChunkPos = new ConcurrentHashMap<>();
|
|
||||||
private final ConcurrentLinkedDeque<Task> concurrentTaskToBuildList = new ConcurrentLinkedDeque<>();
|
|
||||||
private final AtomicInteger runningCount = new AtomicInteger(0);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
|
||||||
// constructors //
|
|
||||||
//==============//
|
|
||||||
|
|
||||||
public ChunkToLodBuilder() { }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=================//
|
|
||||||
// data generation //
|
|
||||||
//=================//
|
|
||||||
|
|
||||||
public CompletableFuture<FullDataSourceV2> tryGenerateData(IChunkWrapper chunkWrapper)
|
|
||||||
{
|
|
||||||
if (chunkWrapper == null)
|
|
||||||
{
|
|
||||||
throw new NullPointerException("ChunkWrapper cannot be null!");
|
|
||||||
}
|
|
||||||
|
|
||||||
IChunkWrapper oldChunk = this.concurrentChunkToBuildByChunkPos.put(chunkWrapper.getChunkPos(), chunkWrapper); // an Exchange operation
|
|
||||||
// If there's old chunk, that means we just replaced an unprocessed old request on generating data on this pos.
|
|
||||||
// if so, we can just return null to signal this, as the old request's future will instead be the proper one
|
|
||||||
// that will return the latest generated data.
|
|
||||||
if (oldChunk != null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, it means we're the first to do so. Let's submit our task to this entry.
|
|
||||||
CompletableFuture<FullDataSourceV2> future = new CompletableFuture<>();
|
|
||||||
this.concurrentTaskToBuildList.addLast(new Task(chunkWrapper.getChunkPos(), future));
|
|
||||||
return future;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO why on tick?
|
|
||||||
public void tick()
|
|
||||||
{
|
|
||||||
int threadCount = ThreadPoolUtil.getWorkerThreadCount();
|
|
||||||
if (this.runningCount.get() >= threadCount)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (this.concurrentTaskToBuildList.isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (MC == null || !MC.playerExists())
|
|
||||||
{
|
|
||||||
// TODO handle server side properly
|
|
||||||
|
|
||||||
// MC hasn't finished loading (or is currently unloaded)
|
|
||||||
|
|
||||||
// can be uncommented if tasks aren't being cleared correctly
|
|
||||||
//this.clearCurrentTasks();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadPoolExecutor lodBuilderExecutor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
|
|
||||||
if (lodBuilderExecutor == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < threadCount; i++)
|
|
||||||
{
|
|
||||||
this.runningCount.incrementAndGet();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
CompletableFuture.runAsync(() ->
|
|
||||||
{
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
long time = System.nanoTime();
|
|
||||||
int count = 0;
|
|
||||||
boolean allDone = false;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// run until we either run out of time, or all tasks are complete
|
|
||||||
if (System.nanoTime() - time > MAX_TICK_TIME_NS && !this.concurrentTaskToBuildList.isEmpty())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task task = this.concurrentTaskToBuildList.pollFirst();
|
|
||||||
if (task == null)
|
|
||||||
{
|
|
||||||
allDone = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
count++;
|
|
||||||
IChunkWrapper latestChunk = this.concurrentChunkToBuildByChunkPos.remove(task.chunkPos); // Basically an Exchange operation
|
|
||||||
if (latestChunk == null)
|
|
||||||
{
|
|
||||||
LOGGER.error("Somehow Task at " + task.chunkPos + " has latestChunk as null. Skipping task.");
|
|
||||||
task.future.complete(null);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (LodDataBuilder.canGenerateLodFromChunk(latestChunk))
|
|
||||||
{
|
|
||||||
FullDataSourceV2 dataSource = LodDataBuilder.createGeneratedDataSource(latestChunk);
|
|
||||||
if (dataSource != null)
|
|
||||||
{
|
|
||||||
task.future.complete(dataSource);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (task.generationAttemptExpirationTimeMs < System.currentTimeMillis())
|
|
||||||
{
|
|
||||||
// this task won't be re-queued
|
|
||||||
//LOGGER.trace("removed chunk "+task.chunkPos);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LOGGER.error("Error while processing Task at " + task.chunkPos, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Failed to build due to chunk not meeting requirement,
|
|
||||||
// re-add it to the queue so it can be tested next time
|
|
||||||
IChunkWrapper casChunk = this.concurrentChunkToBuildByChunkPos.putIfAbsent(task.chunkPos, latestChunk); // CAS operation with expected=null
|
|
||||||
if (casChunk == null || latestChunk.isStillValid()) // That means CAS have been successful
|
|
||||||
{
|
|
||||||
this.concurrentTaskToBuildList.addLast(task); // Then add back the same old task.
|
|
||||||
}
|
|
||||||
else // Else, it means someone managed to sneak in a new gen request in this pos. Then lets drop this old task.
|
|
||||||
{
|
|
||||||
task.future.complete(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
count--;
|
|
||||||
}
|
|
||||||
|
|
||||||
long time2 = System.nanoTime();
|
|
||||||
if (!allDone)
|
|
||||||
{
|
|
||||||
//LOGGER.info("Completed {} tasks in {} in this tick", count, Duration.ofNanos(time2 - time));
|
|
||||||
}
|
|
||||||
else if (count > 0)
|
|
||||||
{
|
|
||||||
//LOGGER.info("Completed all {} tasks in {}", count, Duration.ofNanos(time2 - time));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* should be called whenever changing levels/worlds
|
|
||||||
* to prevent trying to generate LODs for chunk(s) that are no longer loaded
|
|
||||||
* (which can cause exceptions)
|
|
||||||
*/
|
|
||||||
public void clearCurrentTasks()
|
|
||||||
{
|
|
||||||
this.concurrentTaskToBuildList.clear();
|
|
||||||
this.concurrentChunkToBuildByChunkPos.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
|
||||||
// base methods //
|
|
||||||
//==============//
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() { this.clearCurrentTasks(); }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
|
||||||
// helper classes //
|
|
||||||
//================//
|
|
||||||
|
|
||||||
private static class Task
|
|
||||||
{
|
|
||||||
public final DhChunkPos chunkPos;
|
|
||||||
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<FullDataSourceV2> future)
|
|
||||||
{
|
|
||||||
this.chunkPos = chunkPos;
|
|
||||||
this.future = future;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArra
|
|||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhBlockPos;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.util.ColorUtil;
|
import com.seibel.distanthorizons.core.util.ColorUtil;
|
||||||
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
|
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
|
||||||
@@ -38,6 +38,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||||
|
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@@ -110,33 +111,260 @@ public class FullDataToRenderDataTransformer
|
|||||||
}
|
}
|
||||||
|
|
||||||
columnSource.markNotEmpty();
|
columnSource.markNotEmpty();
|
||||||
|
int baseX = DhSectionPos.getMinCornerBlockX(pos);
|
||||||
|
int baseZ = DhSectionPos.getMinCornerBlockZ(pos);
|
||||||
|
|
||||||
if (dataDetail == columnSource.getDataDetailLevel())
|
for (int x = 0; x < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); x++)
|
||||||
{
|
{
|
||||||
int baseX = DhSectionPos.getMinCornerBlockX(pos);
|
for (int z = 0; z < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); z++)
|
||||||
int baseZ = DhSectionPos.getMinCornerBlockZ(pos);
|
|
||||||
|
|
||||||
for (int x = 0; x < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); x++)
|
|
||||||
{
|
{
|
||||||
for (int z = 0; z < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); z++)
|
throwIfThreadInterrupted();
|
||||||
{
|
|
||||||
throwIfThreadInterrupted();
|
ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z);
|
||||||
|
LongArrayList dataColumn = fullDataSource.get(x, z);
|
||||||
ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z);
|
|
||||||
LongArrayList dataColumn = fullDataSource.get(x, z);
|
updateOrReplaceRenderDataViewColumnWithFullDataColumn(
|
||||||
convertColumnData(level, fullDataSource.mapping, baseX + x, baseZ + z, columnArrayView, dataColumn);
|
level, fullDataSource.mapping,
|
||||||
}
|
// bitshift is to account for LODs with a detail level greater than 0 so the block pos is correct
|
||||||
|
baseX + BitShiftUtil.pow(x,dataDetail), baseZ + BitShiftUtil.pow(z,dataDetail),
|
||||||
|
columnArrayView, dataColumn);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
columnSource.fillDebugFlag(0, 0, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.DebugSourceFlag.FULL);
|
||||||
|
|
||||||
columnSource.fillDebugFlag(0, 0, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.DebugSourceFlag.FULL);
|
return columnSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the given {@link ColumnArrayView} to match the incoming Full data {@link LongArrayList} */
|
||||||
|
public static void updateOrReplaceRenderDataViewColumnWithFullDataColumn(
|
||||||
|
IDhClientLevel level,
|
||||||
|
FullDataPointIdMap fullDataMapping, int blockX, int blockZ,
|
||||||
|
ColumnArrayView columnArrayView,
|
||||||
|
LongArrayList fullDataColumn)
|
||||||
|
{
|
||||||
|
// we can't do anything if the full data is missing or empty
|
||||||
|
if (fullDataColumn == null || fullDataColumn.size() == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fullDataLength = fullDataColumn.size();
|
||||||
|
if (fullDataLength <= columnArrayView.verticalSize())
|
||||||
|
{
|
||||||
|
// Directly use the arrayView since it fits.
|
||||||
|
setRenderColumnView(level, fullDataMapping, blockX, blockZ, columnArrayView, fullDataColumn);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new UnsupportedOperationException("To be implemented");
|
// expand the ColumnArrayView to fit the new larger max vertical size
|
||||||
//FIXME: Implement different size creation of renderData
|
ColumnArrayView newColumnArrayView = new ColumnArrayView(new LongArrayList(new long[fullDataLength]), fullDataLength, 0, fullDataLength);
|
||||||
|
setRenderColumnView(level, fullDataMapping, blockX, blockZ, newColumnArrayView, fullDataColumn);
|
||||||
|
columnArrayView.changeVerticalSizeFrom(newColumnArrayView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static void setRenderColumnView(
|
||||||
|
IDhClientLevel level, FullDataPointIdMap fullDataMapping,
|
||||||
|
int blockX, int blockZ,
|
||||||
|
ColumnArrayView renderColumnData, LongArrayList fullColumnData)
|
||||||
|
{
|
||||||
|
//===============//
|
||||||
|
// config values //
|
||||||
|
//===============//
|
||||||
|
|
||||||
|
boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING);
|
||||||
|
boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get();
|
||||||
|
|
||||||
|
HashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(level.getLevelWrapper());
|
||||||
|
HashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(level.getLevelWrapper());
|
||||||
|
|
||||||
|
int caveCullingMaxY = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get() - level.getMinY();
|
||||||
|
boolean caveCullingEnabled =
|
||||||
|
Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get()
|
||||||
|
&& (
|
||||||
|
// dimensions with a ceiling will be all caves so we don't want cave culling
|
||||||
|
!level.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)
|
||||||
|
&& !level.getLevelWrapper().getDimensionType().isTheEnd()
|
||||||
|
);
|
||||||
|
|
||||||
|
boolean isColumnVoid = true;
|
||||||
|
|
||||||
|
int colorToApplyToNextBlock = -1;
|
||||||
|
int lastColor = 0;
|
||||||
|
int lastBottom = -10_000;
|
||||||
|
|
||||||
|
int skylightToApplyToNextBlock = -1;
|
||||||
|
int blocklightToApplyToNextBlock = -1;
|
||||||
|
int renderDataIndex = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==================================//
|
||||||
|
// convert full data to render data //
|
||||||
|
//==================================//
|
||||||
|
|
||||||
|
// goes from the top down
|
||||||
|
for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++)
|
||||||
|
{
|
||||||
|
long fullData = fullColumnData.getLong(fullDataIndex);
|
||||||
|
|
||||||
|
int bottomY = FullDataPointUtil.getBottomY(fullData);
|
||||||
|
int blockHeight = FullDataPointUtil.getHeight(fullData);
|
||||||
|
int topY = bottomY + blockHeight;
|
||||||
|
int id = FullDataPointUtil.getId(fullData);
|
||||||
|
int blockLight = FullDataPointUtil.getBlockLight(fullData);
|
||||||
|
int skyLight = FullDataPointUtil.getSkyLight(fullData);
|
||||||
|
|
||||||
|
IBiomeWrapper biome;
|
||||||
|
IBlockStateWrapper block;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
biome = fullDataMapping.getBiomeWrapper(id);
|
||||||
|
block = fullDataMapping.getBlockStateWrapper(id);
|
||||||
|
}
|
||||||
|
catch (IndexOutOfBoundsException e)
|
||||||
|
{
|
||||||
|
if (!brokenPos.contains(fullDataMapping.getPos()))
|
||||||
|
{
|
||||||
|
brokenPos.add(fullDataMapping.getPos());
|
||||||
|
String dimName = level.getLevelWrapper().getDimensionName();
|
||||||
|
LOGGER.warn("Unable to get data point with id ["+id+"] " +
|
||||||
|
"(Max possible ID: ["+fullDataMapping.getMaxValidId()+"]) " +
|
||||||
|
"for pos ["+fullDataMapping.getPos()+"] in dimension ["+dimName+"]. " +
|
||||||
|
"Error: ["+e.getMessage()+"]. " +
|
||||||
|
"Further errors for this position won't be logged.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't render broken data
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//====================//
|
||||||
|
// ignored block and //
|
||||||
|
// cave culling check //
|
||||||
|
//====================//
|
||||||
|
|
||||||
|
boolean ignoreBlock = blockStatesToIgnore.contains(block);
|
||||||
|
boolean caveBlock = caveBlockStatesToIgnore.contains(block);
|
||||||
|
if (caveBlock)
|
||||||
|
{
|
||||||
|
if (caveCullingEnabled
|
||||||
|
// assume this data point is underground if it has no sky-light
|
||||||
|
&& skyLight == LodUtil.MIN_MC_LIGHT
|
||||||
|
// ignore caves above a certain height to prevent floating islands from having walls underneath them
|
||||||
|
&& topY < caveCullingMaxY
|
||||||
|
// cave culling shouldn't happen when at the top of the world
|
||||||
|
&& renderDataIndex != 0 && fullDataIndex != 0
|
||||||
|
// cave culling can't happen when at the bottom of the world
|
||||||
|
&& (fullDataIndex+1) < fullColumnData.size())
|
||||||
|
{
|
||||||
|
// we need to get the next sky/block lights because
|
||||||
|
// the air block here will always have a light of 0/0 due to only the top of the LOD's light being saved.
|
||||||
|
long nextFullData = fullColumnData.getLong(fullDataIndex+1);
|
||||||
|
int nextSkyLight = FullDataPointUtil.getSkyLight(nextFullData);
|
||||||
|
|
||||||
|
if (nextSkyLight == LodUtil.MIN_MC_LIGHT
|
||||||
|
&& ColorUtil.getAlpha(lastColor) == 255)
|
||||||
|
{
|
||||||
|
// replace the previous block with new bottom
|
||||||
|
long columnData = renderColumnData.get(renderDataIndex - 1);
|
||||||
|
columnData = RenderDataPointUtil.setYMin(columnData, bottomY);
|
||||||
|
renderColumnData.set(renderDataIndex - 1, columnData);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (ignoreBlock)
|
||||||
|
{
|
||||||
|
// this is a merged block and a cave block, so it should never be rendered
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ignoreBlock)
|
||||||
|
{
|
||||||
|
// this is an ignored block, but shouldn't be merged like a cave block
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=======================//
|
||||||
|
// non-solid block check //
|
||||||
|
//=======================//
|
||||||
|
|
||||||
|
if (ignoreNonCollidingBlocks
|
||||||
|
&& !block.isSolid() && !block.isLiquid() && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE)
|
||||||
|
{
|
||||||
|
if (colorBelowWithAvoidedBlocks)
|
||||||
|
{
|
||||||
|
int tempColor = level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block);
|
||||||
|
// don't transfer the color when alpha is 0
|
||||||
|
// this prevents issues if grass is transparent
|
||||||
|
if (ColorUtil.getAlpha(tempColor) != 0)
|
||||||
|
{
|
||||||
|
colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor,255);
|
||||||
|
skylightToApplyToNextBlock = skyLight;
|
||||||
|
blocklightToApplyToNextBlock = blockLight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip this non-colliding block
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int color;
|
||||||
|
if (colorToApplyToNextBlock == -1)
|
||||||
|
{
|
||||||
|
// use this block's color
|
||||||
|
color = level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// use the previous block's color
|
||||||
|
color = colorToApplyToNextBlock;
|
||||||
|
colorToApplyToNextBlock = -1;
|
||||||
|
skyLight = skylightToApplyToNextBlock;
|
||||||
|
blockLight = blocklightToApplyToNextBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============================//
|
||||||
|
// merge same-colored adjacent //
|
||||||
|
//=============================//
|
||||||
|
|
||||||
|
// check if they share a top-bottom face and if they have same color
|
||||||
|
if (color == lastColor && bottomY + blockHeight == lastBottom && renderDataIndex > 0)
|
||||||
|
{
|
||||||
|
//replace the previous block with new bottom
|
||||||
|
long columnData = renderColumnData.get(renderDataIndex - 1);
|
||||||
|
columnData = RenderDataPointUtil.setYMin(columnData, bottomY);
|
||||||
|
renderColumnData.set(renderDataIndex - 1, columnData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// add the block
|
||||||
|
isColumnVoid = false;
|
||||||
|
long columnData = RenderDataPointUtil.createDataPoint(bottomY + blockHeight, bottomY, color, skyLight, blockLight, block.getMaterialId());
|
||||||
|
renderColumnData.set(renderDataIndex, columnData);
|
||||||
|
renderDataIndex++;
|
||||||
|
}
|
||||||
|
lastBottom = bottomY;
|
||||||
|
lastColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (isColumnVoid)
|
||||||
|
{
|
||||||
|
renderColumnData.set(0, RenderDataPointUtil.EMPTY_DATA);
|
||||||
}
|
}
|
||||||
return columnSource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -158,168 +386,4 @@ public class FullDataToRenderDataTransformer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO what does this mean?
|
|
||||||
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() == EDhApiBlocksToAvoid.NON_COLLIDING);
|
|
||||||
boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get();
|
|
||||||
|
|
||||||
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 < fullColumnData.size(); i++)
|
|
||||||
{
|
|
||||||
long fullData = fullColumnData.getLong(i);
|
|
||||||
int bottomY = FullDataPointUtil.getBottomY(fullData);
|
|
||||||
int blockHeight = FullDataPointUtil.getHeight(fullData);
|
|
||||||
int id = FullDataPointUtil.getId(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?
|
|
||||||
// TODO if either of these happen the ID might also be invalid
|
|
||||||
//if (bottomY + blockHeight > 300)
|
|
||||||
//{
|
|
||||||
// // this data point is too tall, it's probably a monolith
|
|
||||||
// int k = 0;
|
|
||||||
// throw new RuntimeException();
|
|
||||||
//}
|
|
||||||
//if (light > 16 || light < 0)
|
|
||||||
//{
|
|
||||||
// // light is out of range
|
|
||||||
// throw new RuntimeException();
|
|
||||||
//}
|
|
||||||
|
|
||||||
IBiomeWrapper biome;
|
|
||||||
IBlockStateWrapper block;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
biome = fullDataMapping.getBiomeWrapper(id);
|
|
||||||
block = fullDataMapping.getBlockStateWrapper(id);
|
|
||||||
}
|
|
||||||
catch (IndexOutOfBoundsException e)
|
|
||||||
{
|
|
||||||
// FIXME sometimes the data map has a length of 0
|
|
||||||
if (!brokenPos.contains(fullDataMapping.getPos()))
|
|
||||||
{
|
|
||||||
brokenPos.add(fullDataMapping.getPos());
|
|
||||||
String dimName = level.getLevelWrapper().getDimensionType().getDimensionName();
|
|
||||||
LOGGER.warn("Unable to get data point with id ["+id+"] " +
|
|
||||||
"(Max possible ID: ["+fullDataMapping.getMaxValidId()+"]) " +
|
|
||||||
"for pos ["+fullDataMapping.getPos()+"] in dimension ["+dimName+"]. " +
|
|
||||||
"Error: ["+e.getMessage()+"]. " +
|
|
||||||
"Further errors for this position won't be logged.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip rendering broken data
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (blockStatesToIgnore.contains(block))
|
|
||||||
{
|
|
||||||
// Don't render: air, barriers, light blocks, etc.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// solid block check
|
|
||||||
if (avoidSolidBlocks && !block.isSolid() && !block.isLiquid() && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE)
|
|
||||||
{
|
|
||||||
if (colorBelowWithAvoidedBlocks)
|
|
||||||
{
|
|
||||||
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
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int color;
|
|
||||||
if (colorToApplyToNextBlock == -1)
|
|
||||||
{
|
|
||||||
// use this block's color
|
|
||||||
color = level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// use the previous block's color
|
|
||||||
color = colorToApplyToNextBlock;
|
|
||||||
colorToApplyToNextBlock = -1;
|
|
||||||
skyLight = skylightToApplyToNextBlock;
|
|
||||||
blockLight = blocklightToApplyToNextBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
//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.getMaterialId());
|
|
||||||
renderColumnData.set(columnOffset, columnData);
|
|
||||||
columnOffset++;
|
|
||||||
}
|
|
||||||
lastBottom = bottomY;
|
|
||||||
lastColor = color;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (isVoid)
|
|
||||||
{
|
|
||||||
renderColumnData.set(0, RenderDataPointUtil.createVoidDataPoint());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO what does this mean?
|
|
||||||
public static void convertColumnData(IDhClientLevel level, FullDataPointIdMap fullDataMapping, int blockX, int blockZ, ColumnArrayView columnArrayView, LongArrayList fullDataColumn)
|
|
||||||
{
|
|
||||||
if (fullDataColumn == null || fullDataColumn.size() == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int dataTotalLength = fullDataColumn.size();
|
|
||||||
if (dataTotalLength > columnArrayView.verticalSize())
|
|
||||||
{
|
|
||||||
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, fullDataMapping, blockX, blockZ, columnArrayView, fullDataColumn); //Directly use the arrayView since it fits.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.dataObjects.transformers;
|
package com.seibel.distanthorizons.core.dataObjects.transformers;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
|
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
|
||||||
@@ -30,14 +31,16 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
|
|||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhBlockPos;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
|
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
|
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
||||||
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
|
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IMutableBlockPosWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
@@ -47,8 +50,6 @@ public class LodDataBuilder
|
|||||||
{
|
{
|
||||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||||
private static final IBlockStateWrapper AIR = SingletonInjector.INSTANCE.get(IWrapperFactory.class).getAirBlockStateWrapper();
|
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;
|
private static boolean getTopErrorLogged = false;
|
||||||
|
|
||||||
@@ -58,21 +59,15 @@ public class LodDataBuilder
|
|||||||
// converters //
|
// converters //
|
||||||
//============//
|
//============//
|
||||||
|
|
||||||
public static FullDataSourceV2 createGeneratedDataSource(IChunkWrapper chunkWrapper)
|
public static FullDataSourceV2 createFromChunk(IChunkWrapper chunkWrapper)
|
||||||
{
|
{
|
||||||
if (!canGenerateLodFromChunk(chunkWrapper))
|
// only block lighting is needed here, sky lighting is populated at the data source stage
|
||||||
{
|
LodUtil.assertTrue(chunkWrapper.isDhBlockLightingCorrect());
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// get the section position
|
int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getX());
|
||||||
int sectionPosX = chunkWrapper.getChunkPos().x;
|
int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getZ());
|
||||||
// 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);
|
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
|
||||||
|
|
||||||
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
|
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
|
||||||
@@ -82,8 +77,8 @@ public class LodDataBuilder
|
|||||||
|
|
||||||
// compute the chunk dataSource offset
|
// compute the chunk dataSource offset
|
||||||
// this offset is used to determine where in the dataSource this chunk's data should go
|
// this offset is used to determine where in the dataSource this chunk's data should go
|
||||||
int chunkOffsetX = chunkWrapper.getChunkPos().x;
|
int chunkOffsetX = chunkWrapper.getChunkPos().getX();
|
||||||
if (chunkWrapper.getChunkPos().x < 0)
|
if (chunkWrapper.getChunkPos().getX() < 0)
|
||||||
{
|
{
|
||||||
// expected offset positions:
|
// expected offset positions:
|
||||||
// chunkPos -> offset
|
// chunkPos -> offset
|
||||||
@@ -98,30 +93,30 @@ public class LodDataBuilder
|
|||||||
// -3 -> 1
|
// -3 -> 1
|
||||||
// -4 -> 0 ---
|
// -4 -> 0 ---
|
||||||
// -5 -> 3
|
// -5 -> 3
|
||||||
chunkOffsetX = ((chunkOffsetX) % NUMB_OF_CHUNKS_WIDE);
|
chunkOffsetX = ((chunkOffsetX) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
|
||||||
if (chunkOffsetX != 0)
|
if (chunkOffsetX != 0)
|
||||||
{
|
{
|
||||||
chunkOffsetX += NUMB_OF_CHUNKS_WIDE;
|
chunkOffsetX += FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
chunkOffsetX %= NUMB_OF_CHUNKS_WIDE;
|
chunkOffsetX %= FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
|
||||||
}
|
}
|
||||||
chunkOffsetX *= LodUtil.CHUNK_WIDTH;
|
chunkOffsetX *= LodUtil.CHUNK_WIDTH;
|
||||||
|
|
||||||
int chunkOffsetZ = chunkWrapper.getChunkPos().z;
|
int chunkOffsetZ = chunkWrapper.getChunkPos().getZ();
|
||||||
if (chunkWrapper.getChunkPos().z < 0)
|
if (chunkWrapper.getChunkPos().getZ() < 0)
|
||||||
{
|
{
|
||||||
chunkOffsetZ = ((chunkOffsetZ) % NUMB_OF_CHUNKS_WIDE);
|
chunkOffsetZ = ((chunkOffsetZ) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
|
||||||
if (chunkOffsetZ != 0)
|
if (chunkOffsetZ != 0)
|
||||||
{
|
{
|
||||||
chunkOffsetZ += NUMB_OF_CHUNKS_WIDE;
|
chunkOffsetZ += FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
chunkOffsetZ %= NUMB_OF_CHUNKS_WIDE;
|
chunkOffsetZ %= FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
|
||||||
}
|
}
|
||||||
chunkOffsetZ *= LodUtil.CHUNK_WIDTH;
|
chunkOffsetZ *= LodUtil.CHUNK_WIDTH;
|
||||||
|
|
||||||
@@ -136,6 +131,9 @@ public class LodDataBuilder
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
IMutableBlockPosWrapper mcBlockPos = chunkWrapper.getMutableBlockPosWrapper();
|
||||||
|
IBlockStateWrapper previousBlockState = null;
|
||||||
|
|
||||||
int minBuildHeight = chunkWrapper.getMinNonEmptyHeight();
|
int minBuildHeight = chunkWrapper.getMinNonEmptyHeight();
|
||||||
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
|
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
|
||||||
{
|
{
|
||||||
@@ -153,21 +151,21 @@ public class LodDataBuilder
|
|||||||
if (lastY < chunkWrapper.getMaxBuildHeight())
|
if (lastY < chunkWrapper.getMaxBuildHeight())
|
||||||
{
|
{
|
||||||
// FIXME: The lastY +1 offset is to reproduce the old behavior. Remove this when we get per-face lighting
|
// 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);
|
blockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, lastY + 1, relBlockZ);
|
||||||
skyLight = (byte) chunkWrapper.getSkyLight(relBlockX, lastY + 1, relBlockZ);
|
skyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, lastY + 1, relBlockZ);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//we are at the height limit. There are no torches here, and sky is not obscured.
|
//we are at the height limit. There are no torches here, and sky is not obscured.
|
||||||
blockLight = 0;
|
blockLight = LodUtil.MIN_MC_LIGHT;
|
||||||
skyLight = 15;
|
skyLight = LodUtil.MAX_MC_LIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// determine the starting Y Pos
|
// determine the starting Y Pos
|
||||||
int y = chunkWrapper.getLightBlockingHeightMapValue(relBlockX, relBlockZ);
|
int y = chunkWrapper.getLightBlockingHeightMapValue(relBlockX, relBlockZ);
|
||||||
// go up until we reach open air or the world limit
|
// go up until we reach open air or the world limit
|
||||||
IBlockStateWrapper topBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
|
IBlockStateWrapper topBlockState = previousBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ, mcBlockPos, previousBlockState);
|
||||||
while (!topBlockState.isAir() && y < chunkWrapper.getMaxBuildHeight())
|
while (!topBlockState.isAir() && y < chunkWrapper.getMaxBuildHeight())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -175,7 +173,7 @@ public class LodDataBuilder
|
|||||||
// 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.
|
// 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.
|
// 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++;
|
y++;
|
||||||
topBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
|
topBlockState = previousBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ, mcBlockPos, previousBlockState);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -194,9 +192,9 @@ public class LodDataBuilder
|
|||||||
for (; y >= minBuildHeight; y--)
|
for (; y >= minBuildHeight; y--)
|
||||||
{
|
{
|
||||||
IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ);
|
IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ);
|
||||||
IBlockStateWrapper newBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
|
IBlockStateWrapper newBlockState = previousBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ, mcBlockPos, previousBlockState);
|
||||||
byte newBlockLight = (byte) chunkWrapper.getBlockLight(relBlockX, y + 1, relBlockZ);
|
byte newBlockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, y + 1, relBlockZ);
|
||||||
byte newSkyLight = (byte) chunkWrapper.getSkyLight(relBlockX, y + 1, relBlockZ);
|
byte newSkyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, y + 1, relBlockZ);
|
||||||
|
|
||||||
// save the biome/block change
|
// save the biome/block change
|
||||||
if (!newBiome.equals(biome) || !newBlockState.equals(blockState))
|
if (!newBiome.equals(biome) || !newBlockState.equals(blockState))
|
||||||
@@ -242,7 +240,7 @@ public class LodDataBuilder
|
|||||||
private static boolean blockVisible(IChunkWrapper chunkWrapper, int relBlockX, int blockY, int relBlockZ)
|
private static boolean blockVisible(IChunkWrapper chunkWrapper, int relBlockX, int blockY, int relBlockZ)
|
||||||
{
|
{
|
||||||
DhBlockPos originalBlockPos = new DhBlockPos(relBlockX,blockY,relBlockZ);
|
DhBlockPos originalBlockPos = new DhBlockPos(relBlockX,blockY,relBlockZ);
|
||||||
DhBlockPos testBlockPos = new DhBlockPos(relBlockX,blockY,relBlockZ);
|
final DhBlockPosMutable testBlockPos = new DhBlockPosMutable(relBlockX,blockY,relBlockZ);
|
||||||
|
|
||||||
// up/down
|
// up/down
|
||||||
if (blockInDirectionVisible(chunkWrapper, EDhDirection.UP, originalBlockPos, testBlockPos))
|
if (blockInDirectionVisible(chunkWrapper, EDhDirection.UP, originalBlockPos, testBlockPos))
|
||||||
@@ -277,20 +275,20 @@ public class LodDataBuilder
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
private static boolean blockInDirectionVisible(IChunkWrapper chunkWrapper, EDhDirection direction, DhBlockPos originalBlockPos, DhBlockPos testBlockPos)
|
private static boolean blockInDirectionVisible(IChunkWrapper chunkWrapper, EDhDirection direction, DhBlockPos originalBlockPos, DhBlockPosMutable testBlockPos)
|
||||||
{
|
{
|
||||||
originalBlockPos.mutateOffset(direction, testBlockPos);
|
originalBlockPos.mutateOffset(direction, testBlockPos);
|
||||||
|
|
||||||
// if the block is next to the border of a chunk, assume it's visible
|
// if the block is next to the border of a chunk, assume it's visible
|
||||||
if (testBlockPos.x < 0 || testBlockPos.x >= LodUtil.CHUNK_WIDTH)
|
if (testBlockPos.getX() < 0 || testBlockPos.getX() >= LodUtil.CHUNK_WIDTH)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (testBlockPos.z < 0 || testBlockPos.z >= LodUtil.CHUNK_WIDTH)
|
if (testBlockPos.getZ() < 0 || testBlockPos.getZ() >= LodUtil.CHUNK_WIDTH)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (testBlockPos.y < chunkWrapper.getMinBuildHeight() || testBlockPos.y > chunkWrapper.getMaxBuildHeight())
|
if (testBlockPos.getY() < chunkWrapper.getMinBuildHeight() || testBlockPos.getY() > chunkWrapper.getMaxBuildHeight())
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -302,14 +300,27 @@ public class LodDataBuilder
|
|||||||
|
|
||||||
|
|
||||||
/** @throws ClassCastException if an API user returns the wrong object type(s) */
|
/** @throws ClassCastException if an API user returns the wrong object type(s) */
|
||||||
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk dataPoints) throws ClassCastException, DataCorruptedException
|
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk apiChunk, boolean runAdditionalValidation) throws ClassCastException, DataCorruptedException, IllegalArgumentException
|
||||||
{
|
{
|
||||||
FullDataSourceV2 accessor = FullDataSourceV2.createEmpty(DhSectionPos.encode(new DhChunkPos(dataPoints.chunkPosX, dataPoints.chunkPosZ)));
|
// get the section position
|
||||||
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
|
int sectionPosX = getXOrZSectionPosFromChunkPos(apiChunk.chunkPosX);
|
||||||
|
int sectionPosZ = getXOrZSectionPosFromChunkPos(apiChunk.chunkPosZ);
|
||||||
|
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
|
||||||
|
|
||||||
|
// chunk relative block position in the data source
|
||||||
|
int relSourceBlockX = Math.floorMod(apiChunk.chunkPosX, 4) * LodUtil.CHUNK_WIDTH;
|
||||||
|
int relSourceBlockZ = Math.floorMod(apiChunk.chunkPosZ, 4) * LodUtil.CHUNK_WIDTH;
|
||||||
|
|
||||||
|
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
|
||||||
|
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
|
||||||
{
|
{
|
||||||
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++)
|
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
|
||||||
{
|
{
|
||||||
List<DhApiTerrainDataPoint> columnDataPoints = dataPoints.getDataPoints(relX, relZ);
|
List<DhApiTerrainDataPoint> columnDataPoints = apiChunk.getDataPoints(relBlockX, relBlockZ);
|
||||||
|
if (runAdditionalValidation)
|
||||||
|
{
|
||||||
|
validateOrThrowDataColumn(columnDataPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// this null check does 2 nice things at the same time:
|
// this null check does 2 nice things at the same time:
|
||||||
@@ -318,12 +329,14 @@ public class LodDataBuilder
|
|||||||
// AND the below loop won't run.
|
// AND the below loop won't run.
|
||||||
int size = (columnDataPoints != null) ? columnDataPoints.size() : 0;
|
int size = (columnDataPoints != null) ? columnDataPoints.size() : 0;
|
||||||
|
|
||||||
|
// TODO make missing air LODs
|
||||||
|
// TODO merge duplicate datapoints
|
||||||
LongArrayList packedDataPoints = new LongArrayList(new long[size]);
|
LongArrayList packedDataPoints = new LongArrayList(new long[size]);
|
||||||
for (int index = 0; index < size; index++)
|
for (int index = 0; index < size; index++)
|
||||||
{
|
{
|
||||||
DhApiTerrainDataPoint dataPoint = columnDataPoints.get(index);
|
DhApiTerrainDataPoint dataPoint = columnDataPoints.get(index);
|
||||||
|
|
||||||
int id = accessor.mapping.addIfNotPresentAndGetId(
|
int id = dataSource.mapping.addIfNotPresentAndGetId(
|
||||||
(IBiomeWrapper) (dataPoint.biomeWrapper),
|
(IBiomeWrapper) (dataPoint.biomeWrapper),
|
||||||
(IBlockStateWrapper) (dataPoint.blockStateWrapper)
|
(IBlockStateWrapper) (dataPoint.blockStateWrapper)
|
||||||
);
|
);
|
||||||
@@ -331,7 +344,7 @@ public class LodDataBuilder
|
|||||||
packedDataPoints.set(index, FullDataPointUtil.encode(
|
packedDataPoints.set(index, FullDataPointUtil.encode(
|
||||||
id,
|
id,
|
||||||
dataPoint.topYBlockPos - dataPoint.bottomYBlockPos,
|
dataPoint.topYBlockPos - dataPoint.bottomYBlockPos,
|
||||||
dataPoint.bottomYBlockPos - dataPoints.topYBlockPos,
|
dataPoint.bottomYBlockPos - apiChunk.bottomYBlockPos,
|
||||||
(byte) (dataPoint.blockLightLevel),
|
(byte) (dataPoint.blockLightLevel),
|
||||||
(byte) (dataPoint.skyLightLevel)
|
(byte) (dataPoint.skyLightLevel)
|
||||||
));
|
));
|
||||||
@@ -339,12 +352,84 @@ public class LodDataBuilder
|
|||||||
|
|
||||||
// TODO add the ability for API users to define a different compression mode
|
// TODO add the ability for API users to define a different compression mode
|
||||||
// or add a "unkown" compression mode
|
// or add a "unkown" compression mode
|
||||||
accessor.setSingleColumn(packedDataPoints, relX, relZ, EDhApiWorldGenerationStep.LIGHT, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
|
dataSource.setSingleColumn(
|
||||||
|
packedDataPoints,
|
||||||
|
relBlockX + relSourceBlockX, relBlockZ + relSourceBlockZ,
|
||||||
|
EDhApiWorldGenerationStep.LIGHT, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
|
||||||
|
dataSource.isEmpty = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return dataSource;
|
||||||
return accessor;
|
|
||||||
}
|
}
|
||||||
|
private static void validateOrThrowDataColumn(List<DhApiTerrainDataPoint> dataPoints) throws IllegalArgumentException
|
||||||
|
{
|
||||||
|
// order doesn't need to be checked if there is 0 or 1 items
|
||||||
|
if (dataPoints.size() > 1)
|
||||||
|
{
|
||||||
|
// DH expects datapoints to be in a top-down order
|
||||||
|
DhApiTerrainDataPoint first = dataPoints.get(0);
|
||||||
|
DhApiTerrainDataPoint last = dataPoints.get(dataPoints.size() - 1);
|
||||||
|
if (first.bottomYBlockPos < last.bottomYBlockPos)
|
||||||
|
{
|
||||||
|
// flip the array if it's in bottom-up order
|
||||||
|
Collections.reverse(dataPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// check that each datapoint is valid
|
||||||
|
int lastBottomYPos = Integer.MIN_VALUE;
|
||||||
|
for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation
|
||||||
|
{
|
||||||
|
DhApiTerrainDataPoint dataPoint = dataPoints.get(i);
|
||||||
|
|
||||||
|
if (dataPoint == null)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Datapoint: ["+i+"] is null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataPoint.detailLevel != 0)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Datapoint: ["+i+"] has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0].");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int bottomYPos = dataPoint.bottomYBlockPos;
|
||||||
|
int topYPos = dataPoint.topYBlockPos;
|
||||||
|
int height = (dataPoint.topYBlockPos - dataPoint.bottomYBlockPos);
|
||||||
|
|
||||||
|
// is the datapoint right side up?
|
||||||
|
if (bottomYPos > topYPos)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Datapoint: ["+i+"] is upside down. Top Pos: ["+topYPos+"], bottom pos: ["+bottomYPos+"].");
|
||||||
|
}
|
||||||
|
// valid height?
|
||||||
|
if (height <= 0 || height >= RenderDataPointUtil.MAX_WORLD_Y_SIZE)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Datapoint: ["+i+"] has invalid height. Height must be in the range [1 - "+RenderDataPointUtil.MAX_WORLD_Y_SIZE+"] (inclusive).");
|
||||||
|
}
|
||||||
|
|
||||||
|
// is this datapoint overlapping the last one?
|
||||||
|
if (lastBottomYPos > topYPos)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("DhApiTerrainDataPoint ["+i+"] is overlapping with the last datapoint, this top Y: ["+topYPos+"], lastBottomYPos: ["+lastBottomYPos+"].");
|
||||||
|
}
|
||||||
|
// is there a gap between the last datapoint?
|
||||||
|
if (topYPos != lastBottomYPos
|
||||||
|
&& lastBottomYPos != Integer.MIN_VALUE)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("DhApiTerrainDataPoint ["+i+"] has a gap between it and index ["+(i-1)+"]. Empty spaces should be filled by air, otherwise DH's downsampling won't calculate lighting correctly.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lastBottomYPos = bottomYPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -352,6 +437,13 @@ public class LodDataBuilder
|
|||||||
// helper methods //
|
// helper methods //
|
||||||
//================//
|
//================//
|
||||||
|
|
||||||
public static boolean canGenerateLodFromChunk(IChunkWrapper chunk) { return chunk != null && chunk.isLightCorrect(); }
|
public static int getXOrZSectionPosFromChunkPos(int chunkXOrZPos)
|
||||||
|
{
|
||||||
|
// get the section position
|
||||||
|
int sectionPos = chunkXOrZPos;
|
||||||
|
// negative positions start at -1 so the logic there is slightly different
|
||||||
|
sectionPos = (sectionPos < 0) ? ((sectionPos + 1) / FullDataSourceV2.NUMB_OF_CHUNKS_WIDE) - 1 : (sectionPos / FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
|
||||||
|
return sectionPos;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public abstract class AbstractDataSourceHandler
|
|||||||
* The lowest numerical detail level possible.
|
* The lowest numerical detail level possible.
|
||||||
*
|
*
|
||||||
* @see AbstractDataSourceHandler#TOP_SECTION_DETAIL_LEVEL
|
* @see AbstractDataSourceHandler#TOP_SECTION_DETAIL_LEVEL
|
||||||
* */
|
*/
|
||||||
public static final byte MIN_SECTION_DETAIL_LEVEL = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
|
public static final byte MIN_SECTION_DETAIL_LEVEL = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
|
||||||
|
|
||||||
|
|
||||||
@@ -223,7 +223,8 @@ public abstract class AbstractDataSourceHandler
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* After this method returns the inputData will be written to file.
|
* After this method returns the inputData will be written to file.
|
||||||
* @param updatePos the position to update
|
*
|
||||||
|
* @param updatePos the position to update
|
||||||
*/
|
*/
|
||||||
protected void updateDataSourceAtPos(long updatePos, @NotNull FullDataSourceV2 inputData, boolean lockOnUpdatePos)
|
protected void updateDataSourceAtPos(long updatePos, @NotNull FullDataSourceV2 inputData, boolean lockOnUpdatePos)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.seibel.distanthorizons.core.level.IDhLevel;
|
|||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO;
|
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO;
|
||||||
|
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
|
||||||
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV1Repo;
|
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV1Repo;
|
||||||
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
|
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
@@ -64,7 +65,7 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new FullDataSourceV1Repo("jdbc:sqlite", new File(this.saveDir.getPath() + File.separator + AbstractSaveStructure.DATABASE_NAME));
|
return new FullDataSourceV1Repo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, new File(this.saveDir.getPath() + File.separator + AbstractSaveStructure.DATABASE_NAME));
|
||||||
}
|
}
|
||||||
catch (SQLException e)
|
catch (SQLException e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.file.fullDatafile;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
|
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
|
||||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||||
|
import com.seibel.distanthorizons.core.api.internal.SharedApi;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
@@ -33,10 +34,12 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
|||||||
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
||||||
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
||||||
|
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
|
||||||
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
|
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
|
||||||
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||||
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
|
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
|
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@@ -102,9 +105,14 @@ public class FullDataSourceProviderV2
|
|||||||
|
|
||||||
// TODO only run thread if modifications happened recently
|
// TODO only run thread if modifications happened recently
|
||||||
/**
|
/**
|
||||||
* This isn't in {@link AbstractDataSourceHandler} since we don't need parent updating logic
|
* This isn't in {@link AbstractDataSourceHandler} since we only want to update
|
||||||
* for render data, only full data.
|
* the newest version of the full data, so if we have providers for either
|
||||||
|
* render data or old full data, we don't want to update them. <br><br>
|
||||||
|
*
|
||||||
|
* Will be null on the dedicated server since updates don't need to be propagated,
|
||||||
|
* only the highest detail level is needed.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
private final ThreadPoolExecutor updateQueueProcessor;
|
private final ThreadPoolExecutor updateQueueProcessor;
|
||||||
|
|
||||||
|
|
||||||
@@ -121,14 +129,22 @@ public class FullDataSourceProviderV2
|
|||||||
|
|
||||||
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataUpdateStatus);
|
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataUpdateStatus);
|
||||||
|
|
||||||
String dimensionName = level.getLevelWrapper().getDimensionType().getDimensionName();
|
String dimensionName = level.getLevelWrapper().getDimensionName();
|
||||||
|
|
||||||
// start migrating any legacy data sources present in the background
|
// start migrating any legacy data sources present in the background
|
||||||
this.migrationThreadPool = ThreadUtil.makeRateLimitedThreadPool(1, MIGRATION_THREAD_NAME_PREFIX +"["+dimensionName+"]", Config.Client.Advanced.MultiThreading.runTimeRatioForUpdatePropagatorThreads.get(), Thread.MIN_PRIORITY, (Semaphore)null);
|
this.migrationThreadPool = ThreadUtil.makeRateLimitedThreadPool(1, MIGRATION_THREAD_NAME_PREFIX + "["+dimensionName+"]", Config.Client.Advanced.MultiThreading.runTimeRatioForUpdatePropagatorThreads.get(), Thread.MIN_PRIORITY, (Semaphore) null);
|
||||||
this.migrationThreadPool.execute(() -> this.convertLegacyDataSources());
|
this.migrationThreadPool.execute(this::convertLegacyDataSources);
|
||||||
|
|
||||||
this.updateQueueProcessor = ThreadUtil.makeSingleThreadPool("Parent Update Queue ["+dimensionName+"]");
|
// update propagation doesn't need to be run on the server since only the highest detail level is needed
|
||||||
this.updateQueueProcessor.execute(() -> this.runUpdateQueue());
|
if (SharedApi.getEnvironment() != EWorldEnvironment.Server_Only)
|
||||||
|
{
|
||||||
|
this.updateQueueProcessor = ThreadUtil.makeSingleThreadPool("Parent Update Queue ["+dimensionName+"]");
|
||||||
|
this.updateQueueProcessor.execute(this::runUpdateQueue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.updateQueueProcessor = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -142,7 +158,7 @@ public class FullDataSourceProviderV2
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new FullDataSourceV2Repo("jdbc:sqlite", new File(this.saveDir.getPath() + File.separator + AbstractSaveStructure.DATABASE_NAME));
|
return new FullDataSourceV2Repo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, new File(this.saveDir.getPath() + File.separator + AbstractSaveStructure.DATABASE_NAME));
|
||||||
}
|
}
|
||||||
catch (SQLException e)
|
catch (SQLException e)
|
||||||
{
|
{
|
||||||
@@ -289,7 +305,8 @@ public class FullDataSourceProviderV2
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (RejectedExecutionException ignore) { /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
|
catch (RejectedExecutionException ignore)
|
||||||
|
{ /* the executor was shut down, it should be back up shortly and able to accept new jobs */ }
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
this.parentUpdatingPosSet.remove(parentUpdatePos);
|
this.parentUpdatingPosSet.remove(parentUpdatePos);
|
||||||
@@ -299,7 +316,10 @@ public class FullDataSourceProviderV2
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (InterruptedException ignored) { Thread.currentThread().interrupt(); }
|
catch (InterruptedException ignored)
|
||||||
|
{
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LOGGER.error("Unexpected error in the parent update queue thread. Error: " + e.getMessage(), e);
|
LOGGER.error("Unexpected error in the parent update queue thread. Error: " + e.getMessage(), e);
|
||||||
@@ -317,7 +337,7 @@ public class FullDataSourceProviderV2
|
|||||||
|
|
||||||
private void convertLegacyDataSources()
|
private void convertLegacyDataSources()
|
||||||
{
|
{
|
||||||
String dimensionName = this.level.getLevelWrapper().getDimensionType().getDimensionName();
|
String dimensionName = this.level.getLevelWrapper().getDimensionName();
|
||||||
LOGGER.info("Attempting to migrate data sources for: ["+dimensionName+"]-["+this.saveDir+"]...");
|
LOGGER.info("Attempting to migrate data sources for: ["+dimensionName+"]-["+this.saveDir+"]...");
|
||||||
|
|
||||||
|
|
||||||
@@ -339,7 +359,7 @@ public class FullDataSourceProviderV2
|
|||||||
|
|
||||||
LOGGER.info("deleting [" + dimensionName + "] - ["+totalDeleteCount+"] unused data sources...");
|
LOGGER.info("deleting [" + dimensionName + "] - ["+totalDeleteCount+"] unused data sources...");
|
||||||
this.legacyDeletionCount = totalDeleteCount;
|
this.legacyDeletionCount = totalDeleteCount;
|
||||||
|
|
||||||
ArrayList<String> unusedDataPosList = this.legacyFileHandler.repo.getUnusedDataSourcePositionStringList(50);
|
ArrayList<String> unusedDataPosList = this.legacyFileHandler.repo.getUnusedDataSourcePositionStringList(50);
|
||||||
while (unusedDataPosList.size() != 0)
|
while (unusedDataPosList.size() != 0)
|
||||||
{
|
{
|
||||||
@@ -368,8 +388,8 @@ public class FullDataSourceProviderV2
|
|||||||
}
|
}
|
||||||
catch (InterruptedException ignore){}
|
catch (InterruptedException ignore){}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.info("Done deleting [" + dimensionName + "] - ["+totalDeleteCount+"] unused data sources.");
|
LOGGER.info("Done deleting [" + dimensionName + "] - ["+totalDeleteCount+"] unused data sources.");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -420,9 +440,7 @@ public class FullDataSourceProviderV2
|
|||||||
{
|
{
|
||||||
newDataSource.close();
|
newDataSource.close();
|
||||||
}
|
}
|
||||||
catch (Exception ignore)
|
catch (Exception ignore) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -498,7 +516,7 @@ public class FullDataSourceProviderV2
|
|||||||
}
|
}
|
||||||
this.migrationStartMessageQueued = true;
|
this.migrationStartMessageQueued = true;
|
||||||
|
|
||||||
String dimName = this.level.getLevelWrapper().getDimensionType().getDimensionName();
|
String dimName = this.level.getLevelWrapper().getDimensionName();
|
||||||
ClientApi.INSTANCE.showChatMessageNextFrame(
|
ClientApi.INSTANCE.showChatMessageNextFrame(
|
||||||
"Old Distant Horizons data is being migrated for ["+dimName+"]. \n" +
|
"Old Distant Horizons data is being migrated for ["+dimName+"]. \n" +
|
||||||
"While migrating LODs may load slowly \n" +
|
"While migrating LODs may load slowly \n" +
|
||||||
@@ -509,7 +527,7 @@ public class FullDataSourceProviderV2
|
|||||||
|
|
||||||
private void showMigrationEndMessage(boolean success)
|
private void showMigrationEndMessage(boolean success)
|
||||||
{
|
{
|
||||||
String dimName = this.level.getLevelWrapper().getDimensionType().getDimensionName();
|
String dimName = this.level.getLevelWrapper().getDimensionName();
|
||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
@@ -553,8 +571,8 @@ public class FullDataSourceProviderV2
|
|||||||
* return false; <br>
|
* return false; <br>
|
||||||
* } <br>
|
* } <br>
|
||||||
* </code>
|
* </code>
|
||||||
* to the beginning of your override.
|
* to the beginning of your override.
|
||||||
* Otherwise, parent retrieval limits will be ignored.
|
* Otherwise, parent retrieval limits will be ignored.
|
||||||
*/
|
*/
|
||||||
public boolean canQueueRetrieval()
|
public boolean canQueueRetrieval()
|
||||||
{
|
{
|
||||||
@@ -569,13 +587,13 @@ public class FullDataSourceProviderV2
|
|||||||
* an empty array if all positions were generated
|
* an empty array if all positions were generated
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public LongArrayList getPositionsToRetrieve(Long pos) { return null; }
|
public LongArrayList getPositionsToRetrieve(Long pos) { return null; }
|
||||||
/**
|
/**
|
||||||
* Returns how many positions could potentially be generated for this position assuming the position is empty.
|
* Returns how many positions could potentially be generated for this position assuming the position is empty.
|
||||||
* Used when estimating the total number of retrieval requests.
|
* Used when estimating the total number of retrieval requests.
|
||||||
*/
|
*/
|
||||||
public int getMaxPossibleRetrievalPositionCountForPos(Long pos) { return -1; }
|
public int getMaxPossibleRetrievalPositionCountForPos(Long pos) { return -1; }
|
||||||
|
|
||||||
/** @return true if the position was queued, false if not */
|
/** @return true if the position was queued, false if not */
|
||||||
public boolean queuePositionForRetrieval(Long genPos) { return false; }
|
public boolean queuePositionForRetrieval(Long genPos) { return false; }
|
||||||
|
|
||||||
@@ -585,7 +603,7 @@ public class FullDataSourceProviderV2
|
|||||||
public void clearRetrievalQueue() { }
|
public void clearRetrievalQueue() { }
|
||||||
|
|
||||||
/** Can be used to display how many total retrieval requests might be available. */
|
/** Can be used to display how many total retrieval requests might be available. */
|
||||||
public void setTotalRetrievalPositionCount(int newCount) { }
|
public void setTotalRetrievalPositionCount(int newCount) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns how many data sources are currently in memory and haven't
|
* Returns how many data sources are currently in memory and haven't
|
||||||
@@ -594,6 +612,20 @@ public class FullDataSourceProviderV2
|
|||||||
*/
|
*/
|
||||||
public int getUnsavedDataSourceCount() { return -1; }
|
public int getUnsavedDataSourceCount() { return -1; }
|
||||||
|
|
||||||
|
public boolean fileExists(long pos) { return this.repo.getDataSizeInBytes(pos) > 0; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//========================//
|
||||||
|
// multiplayer networking //
|
||||||
|
//========================//
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Long getTimestampForPos(long pos)
|
||||||
|
{ return this.repo.getTimestampForPos(pos); }
|
||||||
|
public Map<Long, Long> getTimestampsForRange(byte detailLevel, int startPosX, int startPosZ, int endPosX, int endPosZ)
|
||||||
|
{ return this.repo.getTimestampsForRange(detailLevel, startPosX, startPosZ, endPosX, endPosZ); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//===========//
|
//===========//
|
||||||
@@ -616,7 +648,10 @@ public class FullDataSourceProviderV2
|
|||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
super.close();
|
super.close();
|
||||||
this.updateQueueProcessor.shutdownNow();
|
if (this.updateQueueProcessor != null)
|
||||||
|
{
|
||||||
|
this.updateQueueProcessor.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
this.legacyFileHandler.close();
|
this.legacyFileHandler.close();
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,15 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGeneratio
|
|||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
|
||||||
|
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
||||||
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
|
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
||||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||||
|
import com.seibel.distanthorizons.core.level.WorldGenModule;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
@@ -36,12 +39,15 @@ import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
|||||||
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 implements IDebugRenderable
|
public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 implements IDebugRenderable
|
||||||
{
|
{
|
||||||
@@ -70,6 +76,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
//=============//
|
//=============//
|
||||||
|
|
||||||
public GeneratedFullDataSourceProvider(IDhLevel level, AbstractSaveStructure saveStructure) { super(level, saveStructure); }
|
public GeneratedFullDataSourceProvider(IDhLevel level, AbstractSaveStructure saveStructure) { super(level, saveStructure); }
|
||||||
|
public GeneratedFullDataSourceProvider(IDhLevel level, AbstractSaveStructure saveStructure, @Nullable File saveDirOverride) { super(level, saveStructure, saveDirOverride); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -93,7 +100,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
// don't log shutdown exceptions
|
// don't log shutdown exceptions
|
||||||
if (!(exception instanceof CancellationException || exception.getCause() instanceof CancellationException))
|
if (!(exception instanceof CancellationException || exception.getCause() instanceof CancellationException))
|
||||||
{
|
{
|
||||||
LOGGER.error("Uncaught Gen Task Exception at [" + genTaskResult.pos + "], error: ["+ exception.getMessage() + "].", exception);
|
LOGGER.error("Uncaught Gen Task Exception at ["+genTaskResult.pos+"], error: ["+exception.getMessage()+"].", exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (genTaskResult.success)
|
else if (genTaskResult.success)
|
||||||
@@ -139,7 +146,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
{
|
{
|
||||||
boolean oldQueueExists = this.worldGenQueueRef.compareAndSet(null, newWorldGenQueue);
|
boolean oldQueueExists = this.worldGenQueueRef.compareAndSet(null, newWorldGenQueue);
|
||||||
LodUtil.assertTrue(oldQueueExists, "previous world gen queue is still here!");
|
LodUtil.assertTrue(oldQueueExists, "previous world gen queue is still here!");
|
||||||
LOGGER.info("Set world gen queue for level ["+this.level+"].");
|
LOGGER.info("Set world gen queue for level [" + this.level.getLevelWrapper().getDimensionName() + "].");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -214,7 +221,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
}
|
}
|
||||||
|
|
||||||
GenTask genTask = new GenTask(genPos);
|
GenTask genTask = new GenTask(genPos);
|
||||||
CompletableFuture<WorldGenResult> worldGenFuture = worldGenQueue.submitGenTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), genTask);
|
CompletableFuture<WorldGenResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), genTask);
|
||||||
worldGenFuture.whenComplete((genTaskResult, ex) -> this.onWorldGenTaskComplete(genTaskResult, ex));
|
worldGenFuture.whenComplete((genTaskResult, ex) -> this.onWorldGenTaskComplete(genTaskResult, ex));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -237,6 +244,12 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
public int getUnsavedDataSourceCount() { return this.delayedFullDataSourceSaveCache.getUnsavedCount(); }
|
public int getUnsavedDataSourceCount() { return this.delayedFullDataSourceSaveCache.getUnsavedCount(); }
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isFullyGenerated(byte[] columnGenerationSteps)
|
||||||
|
{
|
||||||
|
return IntStream.range(0, columnGenerationSteps.length)
|
||||||
|
.noneMatch(i -> columnGenerationSteps[i] == EDhApiWorldGenerationStep.EMPTY.value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LongArrayList getPositionsToRetrieve(Long pos)
|
public LongArrayList getPositionsToRetrieve(Long pos)
|
||||||
{
|
{
|
||||||
@@ -385,23 +398,35 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
public boolean isMemoryAddressValid() { return true; }
|
public boolean isMemoryAddressValid() { return true; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Consumer<FullDataSourceV2> getChunkDataConsumer()
|
public Consumer<FullDataSourceV2> getDataSourceConsumer()
|
||||||
{
|
{
|
||||||
return (chunkSizedFullDataSource) ->
|
return (chunkSizedFullDataSource) ->
|
||||||
{
|
{
|
||||||
GeneratedFullDataSourceProvider.this.delayedFullDataSourceSaveCache.queueDataSourceForUpdateAndSave(chunkSizedFullDataSource);
|
GeneratedFullDataSourceProvider.this.delayedFullDataSourceSaveCache.queueDataSourceForUpdateAndSave(chunkSizedFullDataSource);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
private void onDataSourceSave(FullDataSourceV2 fullDataSource)
|
private void onDataSourceSave(FullDataSourceV2 fullDataSource)
|
||||||
{ GeneratedFullDataSourceProvider.this.updateDataSourceAsync(fullDataSource); }
|
{
|
||||||
|
// block lights should have been populated at the chunkWrapper stage
|
||||||
|
// waiting to populate the data source's skylight at this stage prevents re-lighting and
|
||||||
|
// allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once
|
||||||
|
DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, LodUtil.MAX_MC_LIGHT);
|
||||||
|
|
||||||
|
GeneratedFullDataSourceProvider.this.updateDataSourceAsync(fullDataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** used by external event listeners */
|
/** used by external event listeners */
|
||||||
@FunctionalInterface
|
|
||||||
public interface IOnWorldGenCompleteListener
|
public interface IOnWorldGenCompleteListener
|
||||||
{
|
{
|
||||||
|
boolean shouldDoWorldGen();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
DhBlockPos2D getTargetPosForGeneration();
|
||||||
|
|
||||||
/** Fired whenever a section has completed generating */
|
/** Fired whenever a section has completed generating */
|
||||||
void onWorldGenTaskComplete(long pos);
|
void onWorldGenTaskComplete(long pos);
|
||||||
|
|
||||||
|
|||||||
@@ -19,15 +19,119 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.file.fullDatafile;
|
package com.seibel.distanthorizons.core.file.fullDatafile;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
|
||||||
|
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
|
||||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||||
|
import com.seibel.distanthorizons.core.level.WorldGenModule;
|
||||||
|
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoginRequestQueue;
|
||||||
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class RemoteFullDataSourceProvider extends FullDataSourceProviderV2
|
/**
|
||||||
|
* Only handles {@link SyncOnLoginRequestQueue} requests (IE updating existing LODs based on a timestamp).
|
||||||
|
* Missing data is handled by {@link WorldGenModule} and {@link RemoteWorldRetrievalQueue}.
|
||||||
|
*/
|
||||||
|
public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvider
|
||||||
{
|
{
|
||||||
public RemoteFullDataSourceProvider(IDhLevel level, AbstractSaveStructure saveStructure) { super(level, saveStructure); }
|
@Nullable
|
||||||
public RemoteFullDataSourceProvider(IDhLevel level, AbstractSaveStructure saveStructure, @Nullable File saveDirOverride) { super(level, saveStructure, saveDirOverride); }
|
private final SyncOnLoginRequestQueue syncOnLoginRequestQueue;
|
||||||
|
private final Set<Long> finishedTaskPositions = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
|
||||||
|
public RemoteFullDataSourceProvider(
|
||||||
|
IDhLevel level, AbstractSaveStructure saveStructure, @Nullable File saveDirOverride,
|
||||||
|
@Nullable SyncOnLoginRequestQueue syncOnLoginRequestQueue)
|
||||||
|
{
|
||||||
|
super(level, saveStructure, saveDirOverride);
|
||||||
|
this.syncOnLoginRequestQueue = syncOnLoginRequestQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==================//
|
||||||
|
// override methods //
|
||||||
|
//==================//
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public FullDataSourceV2 get(long pos)
|
||||||
|
{
|
||||||
|
//=======================//
|
||||||
|
// get local data source //
|
||||||
|
//=======================//
|
||||||
|
|
||||||
|
FullDataSourceV2 fullDataSource = super.get(pos);
|
||||||
|
if (fullDataSource == null)
|
||||||
|
{
|
||||||
|
// we don't have any local data for this position,
|
||||||
|
// we can't queue updates based on a timestamp
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.syncOnLoginRequestQueue == null)
|
||||||
|
{
|
||||||
|
// we have local data, but aren't allowed to
|
||||||
|
// request timestamp updates from the server.
|
||||||
|
return fullDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===========================//
|
||||||
|
// request timestamp updates //
|
||||||
|
// from server //
|
||||||
|
//===========================//
|
||||||
|
|
||||||
|
// get the timestamp for every maximum detail position in this section
|
||||||
|
int posToMinimumDetailScale = (DhSectionPos.getDetailLevel(pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + 1);
|
||||||
|
Map<Long, Long> timestamps = this.getTimestampsForRange(
|
||||||
|
DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL,
|
||||||
|
DhSectionPos.getX(pos) * posToMinimumDetailScale,
|
||||||
|
DhSectionPos.getZ(pos) * posToMinimumDetailScale,
|
||||||
|
(DhSectionPos.getX(pos) + 1) * posToMinimumDetailScale - 1,
|
||||||
|
(DhSectionPos.getZ(pos) + 1) * posToMinimumDetailScale - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
// check if the server has newer versions of these LODs
|
||||||
|
for (Map.Entry<Long, Long> timestampBySectionPos : timestamps.entrySet())
|
||||||
|
{
|
||||||
|
Long subPos = timestampBySectionPos.getKey();
|
||||||
|
Long subTimestamp = timestampBySectionPos.getValue();
|
||||||
|
|
||||||
|
if (this.finishedTaskPositions.add(subPos))
|
||||||
|
{
|
||||||
|
this.syncOnLoginRequestQueue.submitRequest(subPos, subTimestamp, this.delayedFullDataSourceSaveCache::queueDataSourceForUpdateAndSave);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==========//
|
||||||
|
// shutdown //
|
||||||
|
//==========//
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
if (this.syncOnLoginRequestQueue != null)
|
||||||
|
{
|
||||||
|
this.syncOnLoginRequestQueue.close();
|
||||||
|
}
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -26,12 +26,11 @@ import com.seibel.distanthorizons.core.config.Config;
|
|||||||
import com.seibel.distanthorizons.api.enums.config.EDhApiServerFolderNameMode;
|
import com.seibel.distanthorizons.api.enums.config.EDhApiServerFolderNameMode;
|
||||||
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
|
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
|
||||||
import com.seibel.distanthorizons.core.util.objects.ParsedIp;
|
import com.seibel.distanthorizons.core.util.objects.ParsedIp;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IDimensionTypeWrapper;
|
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
|
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -41,13 +40,17 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public class ClientOnlySaveStructure extends AbstractSaveStructure
|
public class ClientOnlySaveStructure extends AbstractSaveStructure
|
||||||
{
|
{
|
||||||
final File folder;
|
public static final String SERVER_DATA_FOLDER_NAME = "Distant_Horizons_server_data";
|
||||||
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
public static final String REPLAY_SERVER_FOLDER_NAME = "REPLAY";
|
||||||
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
|
|
||||||
public static final String INVALID_FILE_CHARACTERS_REGEX = "[\\\\/:*?\"<>|]";
|
public static final String INVALID_FILE_CHARACTERS_REGEX = "[\\\\/:*?\"<>|]";
|
||||||
|
|
||||||
SubDimensionLevelMatcher subDimMatcher = null;
|
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
final HashMap<ILevelWrapper, File> levelWrapperToFileMap = new HashMap<>();
|
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
|
||||||
|
|
||||||
|
|
||||||
|
private SubDimensionLevelMatcher subDimMatcher = null;
|
||||||
|
private final File folder;
|
||||||
|
private final HashMap<ILevelWrapper, File> levelWrapperToFileMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -84,9 +87,9 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
|
|||||||
if (newLevelWrapper instanceof IServerKeyedClientLevel)
|
if (newLevelWrapper instanceof IServerKeyedClientLevel)
|
||||||
{
|
{
|
||||||
IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper;
|
IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper;
|
||||||
LOGGER.info("Loading level " + newLevelWrapper.getDimensionType().getDimensionName() + " with key: " + keyedClientLevel.getServerLevelKey());
|
LOGGER.info("Loading level " + newLevelWrapper.getDimensionName() + " with key: " + keyedClientLevel.getServerLevelKey());
|
||||||
// This world was identified by the server directly, so we can know for sure which folder to use.
|
// This world was identified by the server directly, so we can know for sure which folder to use.
|
||||||
return new File(getSaveStructureFolderPath() + File.separatorChar + keyedClientLevel.getServerLevelKey());
|
return new File(getSaveStructureFolderPath() + File.separatorChar + keyedClientLevel.getServerLevelKey().replaceAll(":", "@@"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -98,9 +101,9 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
|
|||||||
// create the matcher if one doesn't exist
|
// create the matcher if one doesn't exist
|
||||||
if (this.subDimMatcher == null || !this.subDimMatcher.isFindingLevel(newClientLevelWrapper))
|
if (this.subDimMatcher == null || !this.subDimMatcher.isFindingLevel(newClientLevelWrapper))
|
||||||
{
|
{
|
||||||
LOGGER.info("Loading level " + newClientLevelWrapper.getDimensionType().getDimensionName());
|
LOGGER.info("Loading level " + newClientLevelWrapper.getDimensionName());
|
||||||
|
|
||||||
List<File> levelFolders = this.getDhDataFoldersForDimension(newClientLevelWrapper.getDimensionType());
|
List<File> levelFolders = this.getDhDataFoldersForLevel(newClientLevelWrapper);
|
||||||
this.subDimMatcher = new SubDimensionLevelMatcher(newClientLevelWrapper, this.folder, levelFolders);
|
this.subDimMatcher = new SubDimensionLevelMatcher(newClientLevelWrapper, this.folder, levelFolders);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,23 +132,23 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
|
|||||||
|
|
||||||
private File getLevelFolderWithoutSimilarityMatching(ILevelWrapper level)
|
private File getLevelFolderWithoutSimilarityMatching(ILevelWrapper level)
|
||||||
{
|
{
|
||||||
List<File> folders = this.getDhDataFoldersForDimension(level.getDimensionType());
|
List<File> folders = this.getDhDataFoldersForLevel(level);
|
||||||
if (!folders.isEmpty() && folders.get(0) != null)
|
if (!folders.isEmpty() && folders.get(0) != null)
|
||||||
{
|
{
|
||||||
// use the first existing sub-dimension
|
// use the first existing sub-dimension
|
||||||
String folderName = folders.get(0).getName();
|
String folderName = folders.get(0).getName();
|
||||||
LOGGER.info("Default Sub Dimension set to: [" + LodUtil.shortenString(folderName, 8) + "...]");
|
LOGGER.info("Default Sub Dimension set to: [" + StringUtil.shortenString(folderName, 8) + "...]");
|
||||||
return folders.get(0);
|
return folders.get(0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// no valid sub dimension was found, create a new one
|
// no valid sub dimension was found, create a new one
|
||||||
LOGGER.info("Default Sub Dimension not found. Creating: [" + level.getDimensionType().getDimensionName() + "]");
|
LOGGER.info("Default Sub Dimension not found. Creating: [" + level.getDimensionName() + "]");
|
||||||
return new File(this.folder, level.getDimensionType().getDimensionName());
|
return new File(this.folder, level.getDimensionName().replaceAll(":", "@@"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<File> getDhDataFoldersForDimension(IDimensionTypeWrapper dimensionType)
|
public List<File> getDhDataFoldersForLevel(ILevelWrapper level)
|
||||||
{
|
{
|
||||||
File[] folders = this.folder.listFiles();
|
File[] folders = this.folder.listFiles();
|
||||||
if (folders == null)
|
if (folders == null)
|
||||||
@@ -154,7 +157,7 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
|
|||||||
}
|
}
|
||||||
|
|
||||||
// filter by dimension name
|
// filter by dimension name
|
||||||
String expectedDimName = dimensionType.getDimensionName();
|
String expectedDimName = level.getDimensionName();
|
||||||
ArrayList<File> possibleDimFolders = new ArrayList<>();
|
ArrayList<File> possibleDimFolders = new ArrayList<>();
|
||||||
for (File dimFolder : folders)
|
for (File dimFolder : folders)
|
||||||
{
|
{
|
||||||
@@ -237,7 +240,7 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
|
|||||||
private static String getSaveStructureFolderPath()
|
private static String getSaveStructureFolderPath()
|
||||||
{
|
{
|
||||||
String path = MC_SHARED.getInstallationDirectory().getPath() + File.separatorChar
|
String path = MC_SHARED.getInstallationDirectory().getPath() + File.separatorChar
|
||||||
+ "Distant_Horizons_server_data" + File.separatorChar
|
+ SERVER_DATA_FOLDER_NAME + File.separatorChar
|
||||||
+ getServerFolderName();
|
+ getServerFolderName();
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
@@ -245,6 +248,14 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
|
|||||||
/** Generated from the server the client is currently connected to. */
|
/** Generated from the server the client is currently connected to. */
|
||||||
private static String getServerFolderName()
|
private static String getServerFolderName()
|
||||||
{
|
{
|
||||||
|
// if connected to a replay we won't have any server info
|
||||||
|
// use the dedicated replay server folder
|
||||||
|
if (MC_CLIENT.connectedToReplay())
|
||||||
|
{
|
||||||
|
return REPLAY_SERVER_FOLDER_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// parse the current server's IP
|
// parse the current server's IP
|
||||||
ParsedIp parsedIp = new ParsedIp(MC_CLIENT.getCurrentServerIp());
|
ParsedIp parsedIp = new ParsedIp(MC_CLIENT.getCurrentServerIp());
|
||||||
String serverIpCleaned = parsedIp.ip.replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
|
String serverIpCleaned = parsedIp.ip.replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
|
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
* @author James Seibel
|
* @author James Seibel
|
||||||
* @version 12-17-2022
|
* @version 12-17-2022
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class SubDimensionLevelMatcher implements AutoCloseable
|
public class SubDimensionLevelMatcher implements AutoCloseable
|
||||||
{
|
{
|
||||||
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
@@ -92,7 +94,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
|
|||||||
if (potentialLevelFolders.size() == 0)
|
if (potentialLevelFolders.size() == 0)
|
||||||
{
|
{
|
||||||
String newId = UUID.randomUUID().toString();
|
String newId = UUID.randomUUID().toString();
|
||||||
LOGGER.info("No potential level files found. Creating a new sub dimension with the ID ["+LodUtil.shortenString(newId, 8)+"]...");
|
LOGGER.info("No potential level files found. Creating a new sub dimension with the ID ["+ StringUtil.shortenString(newId, 8)+"]...");
|
||||||
this.foundLevelFile = this.CreateSubDimFolder(newId);
|
this.foundLevelFile = this.CreateSubDimFolder(newId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,14 +185,9 @@ public class SubDimensionLevelMatcher implements AutoCloseable
|
|||||||
DhLightingEngine.INSTANCE.lightChunk(newlyLoadedChunk, new ArrayList<>(), MC_CLIENT.getWrappedClientLevel().hasSkyLight() ? 15 : 0);
|
DhLightingEngine.INSTANCE.lightChunk(newlyLoadedChunk, new ArrayList<>(), MC_CLIENT.getWrappedClientLevel().hasSkyLight() ? 15 : 0);
|
||||||
|
|
||||||
// build the chunk LOD
|
// build the chunk LOD
|
||||||
if (!LodDataBuilder.canGenerateLodFromChunk(newlyLoadedChunk))
|
|
||||||
{
|
|
||||||
LOGGER.warn("unable to build lod for chunk:"+newlyLoadedChunk.getChunkPos());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
FullDataSourceV2 newChunkSizedFullDataView = FullDataSourceV2.createFromChunk(newlyLoadedChunk);
|
FullDataSourceV2 newChunkSizedFullDataView = FullDataSourceV2.createFromChunk(newlyLoadedChunk);
|
||||||
// convert to a data source for easier comparing
|
// convert to a data source for easier comparing
|
||||||
FullDataSourceV2 newDataSource = FullDataSourceV2.createEmpty(DhSectionPos.encode(this.playerData.playerBlockPos));
|
FullDataSourceV2 newDataSource = FullDataSourceV2.createEmpty(DhSectionPos.encodeContaining(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, this.playerData.playerBlockPos));
|
||||||
newDataSource.update(newChunkSizedFullDataView);
|
newDataSource.update(newChunkSizedFullDataView);
|
||||||
|
|
||||||
|
|
||||||
@@ -200,22 +197,22 @@ public class SubDimensionLevelMatcher implements AutoCloseable
|
|||||||
//================================//
|
//================================//
|
||||||
|
|
||||||
// log the start of this attempt
|
// log the start of this attempt
|
||||||
LOGGER.info("Attempting to determine sub-dimension for [" + MC_CLIENT.getWrappedClientLevel().getDimensionType().getDimensionName() + "]");
|
LOGGER.info("Attempting to determine sub-dimension for [" + MC_CLIENT.getWrappedClientLevel().getDimensionName() + "]");
|
||||||
LOGGER.info("Player block pos in dimension: [" + this.playerData.playerBlockPos.x + "," + this.playerData.playerBlockPos.y + "," + this.playerData.playerBlockPos.z + "]");
|
LOGGER.info("Player block pos in dimension: [" + this.playerData.playerBlockPos.getX() + "," + this.playerData.playerBlockPos.getY() + "," + this.playerData.playerBlockPos.getZ() + "]");
|
||||||
LOGGER.info("Potential Sub Dimension folders: [" + this.potentialLevelFolders.size() + "]");
|
LOGGER.info("Potential Sub Dimension folders: [" + this.potentialLevelFolders.size() + "]");
|
||||||
|
|
||||||
SubDimCompare mostSimilarSubDim = null;
|
SubDimCompare mostSimilarSubDim = null;
|
||||||
for (File testLevelFolder : this.potentialLevelFolders)
|
for (File testLevelFolder : this.potentialLevelFolders)
|
||||||
{
|
{
|
||||||
LOGGER.info("Testing level folder: [" + LodUtil.shortenString(testLevelFolder.getName(), 8) + "]");
|
LOGGER.info("Testing level folder: [" + StringUtil.shortenString(testLevelFolder.getName(), 8) + "]");
|
||||||
|
|
||||||
FullDataSourceV2 testFullDataSource = null;
|
FullDataSourceV2 testFullDataSource = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// get the data source to compare against
|
// get the data source to compare against
|
||||||
try (IDhLevel tempLevel = new DhClientLevel(new ClientOnlySaveStructure(), this.currentClientLevel, testLevelFolder, false))
|
try (IDhLevel tempLevel = new DhClientLevel(new ClientOnlySaveStructure(), this.currentClientLevel, testLevelFolder, false, null))
|
||||||
{
|
{
|
||||||
testFullDataSource = tempLevel.getFullDataProvider().getAsync(DhSectionPos.encode(this.playerData.playerBlockPos)).join();
|
testFullDataSource = tempLevel.getFullDataProvider().getAsync(DhSectionPos.encodeContaining(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, this.playerData.playerBlockPos)).join();
|
||||||
if (testFullDataSource == null)
|
if (testFullDataSource == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -314,7 +311,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
|
|||||||
|
|
||||||
// get the player data for this dimension folder
|
// get the player data for this dimension folder
|
||||||
SubDimensionPlayerData testPlayerData = new SubDimensionPlayerData(testLevelFolder);
|
SubDimensionPlayerData testPlayerData = new SubDimensionPlayerData(testLevelFolder);
|
||||||
LOGGER.info("Last known player pos: [" + testPlayerData.playerBlockPos.x + "," + testPlayerData.playerBlockPos.y + "," + testPlayerData.playerBlockPos.z + "]");
|
LOGGER.info("Last known player pos: [" + testPlayerData.playerBlockPos.getX() + "," + testPlayerData.playerBlockPos.getY() + "," + testPlayerData.playerBlockPos.getZ() + "]");
|
||||||
|
|
||||||
// check if the block positions are close
|
// check if the block positions are close
|
||||||
int playerBlockDist = testPlayerData.playerBlockPos.getManhattanDistance(this.playerData.playerBlockPos);
|
int playerBlockDist = testPlayerData.playerBlockPos.getManhattanDistance(this.playerData.playerBlockPos);
|
||||||
@@ -328,8 +325,8 @@ public class SubDimensionLevelMatcher implements AutoCloseable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
String subDimShortName = LodUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging
|
String subDimShortName = StringUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging
|
||||||
String equalPercent = LodUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5);
|
String equalPercent = StringUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5);
|
||||||
LOGGER.info("Sub dimension ["+subDimShortName+"...] is current dimension probability: "+equalPercent+" ("+equalDataPoints+"/"+totalDataPointCount+")");
|
LOGGER.info("Sub dimension ["+subDimShortName+"...] is current dimension probability: "+equalPercent+" ("+equalDataPoints+"/"+totalDataPointCount+")");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -359,7 +356,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
|
|||||||
{
|
{
|
||||||
// we found a sub dim folder that is similar, use it
|
// we found a sub dim folder that is similar, use it
|
||||||
|
|
||||||
LOGGER.info("Sub Dimension set to: [" + LodUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]");
|
LOGGER.info("Sub Dimension set to: [" + StringUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]");
|
||||||
return mostSimilarSubDim.folder;
|
return mostSimilarSubDim.folder;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -369,7 +366,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
|
|||||||
String newId = UUID.randomUUID().toString();
|
String newId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
double highestEqualityPercent = mostSimilarSubDim != null ? mostSimilarSubDim.getPercentEqual() : 0;
|
double highestEqualityPercent = mostSimilarSubDim != null ? mostSimilarSubDim.getPercentEqual() : 0;
|
||||||
String message = "No suitable sub dimension found. The highest equality was [" + LodUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + LodUtil.shortenString(newId, 8) + "...";
|
String message = "No suitable sub dimension found. The highest equality was [" + StringUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + StringUtil.shortenString(newId, 8) + "...";
|
||||||
LOGGER.info(message);
|
LOGGER.info(message);
|
||||||
|
|
||||||
File folder = this.CreateSubDimFolder(newId);
|
File folder = this.CreateSubDimFolder(newId);
|
||||||
@@ -379,7 +376,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private File CreateSubDimFolder(String subDimId) { return new File(this.levelsFolder.getPath() + File.separatorChar + this.currentClientLevel.getDimensionType().getDimensionName(), subDimId); }
|
private File CreateSubDimFolder(String subDimId) { return new File(this.levelsFolder.getPath() + File.separatorChar + this.currentClientLevel.getDimensionName(), subDimId); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() { this.matcherThread.shutdownNow(); }
|
public void close() { this.matcherThread.shutdownNow(); }
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ package com.seibel.distanthorizons.core.file.subDimMatching;
|
|||||||
|
|
||||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.pos.DhBlockPos;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
|
|
||||||
@@ -127,9 +127,9 @@ public class SubDimensionPlayerData
|
|||||||
public void toTomlFile(CommentedFileConfig toml)
|
public void toTomlFile(CommentedFileConfig toml)
|
||||||
{
|
{
|
||||||
// player block pos
|
// player block pos
|
||||||
toml.add(PLAYER_BLOCK_POS_X_PATH, this.playerBlockPos.x);
|
toml.add(PLAYER_BLOCK_POS_X_PATH, this.playerBlockPos.getX());
|
||||||
toml.add(PLAYER_BLOCK_POS_Y_PATH, this.playerBlockPos.y);
|
toml.add(PLAYER_BLOCK_POS_Y_PATH, this.playerBlockPos.getY());
|
||||||
toml.add(PLAYER_BLOCK_POS_Z_PATH, this.playerBlockPos.z);
|
toml.add(PLAYER_BLOCK_POS_Z_PATH, this.playerBlockPos.getZ());
|
||||||
|
|
||||||
toml.save();
|
toml.save();
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ public class SubDimensionPlayerData
|
|||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "PlayerBlockPos: [" + this.playerBlockPos.x + "," + this.playerBlockPos.y + "," + this.playerBlockPos.z + "]";
|
return "PlayerBlockPos: [" + this.playerBlockPos.getX() + "," + this.playerBlockPos.getY() + "," + this.playerBlockPos.getZ() + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class AdjacentChunkHolder
|
|||||||
{
|
{
|
||||||
for (int zOffset = -1; zOffset <= 1; zOffset++)
|
for (int zOffset = -1; zOffset <= 1; zOffset++)
|
||||||
{
|
{
|
||||||
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.x + xOffset, centerChunkPos.z + zOffset);
|
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.getX() + xOffset, centerChunkPos.getZ() + zOffset);
|
||||||
requestedAdjacentPositions.add(adjacentPos);
|
requestedAdjacentPositions.add(adjacentPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,13 +69,13 @@ public class AdjacentChunkHolder
|
|||||||
DhChunkPos centerPos = this.chunkArray[4].getChunkPos();
|
DhChunkPos centerPos = this.chunkArray[4].getChunkPos();
|
||||||
DhChunkPos offsetPos = centerWrapper.getChunkPos();
|
DhChunkPos offsetPos = centerWrapper.getChunkPos();
|
||||||
|
|
||||||
int offsetX = offsetPos.x - centerPos.x;
|
int offsetX = offsetPos.getX() - centerPos.getX();
|
||||||
if (offsetX < -1 || offsetX > 1)
|
if (offsetX < -1 || offsetX > 1)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int offsetZ = offsetPos.z - centerPos.z;
|
int offsetZ = offsetPos.getZ() - centerPos.getZ();
|
||||||
if (offsetZ < -1 || offsetZ > 1)
|
if (offsetZ < -1 || offsetZ > 1)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -91,18 +91,18 @@ public class AdjacentChunkHolder
|
|||||||
int chunkZ = BitShiftUtil.divideByPowerOfTwo(blockZ, 4);
|
int chunkZ = BitShiftUtil.divideByPowerOfTwo(blockZ, 4);
|
||||||
IChunkWrapper centerChunk = this.chunkArray[4];
|
IChunkWrapper centerChunk = this.chunkArray[4];
|
||||||
DhChunkPos centerPos = centerChunk.getChunkPos();
|
DhChunkPos centerPos = centerChunk.getChunkPos();
|
||||||
if (centerPos.x == chunkX && centerPos.z == chunkZ)
|
if (centerPos.getX() == chunkX && centerPos.getZ() == chunkZ)
|
||||||
{
|
{
|
||||||
return centerChunk;
|
return centerChunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
int offsetX = chunkX - centerPos.x;
|
int offsetX = chunkX - centerPos.getX();
|
||||||
if (offsetX < -1 || offsetX > 1)
|
if (offsetX < -1 || offsetX > 1)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int offsetZ = chunkZ - centerPos.z;
|
int offsetZ = chunkZ - centerPos.getZ();
|
||||||
if (offsetZ < -1 || offsetZ > 1)
|
if (offsetZ < -1 || offsetZ > 1)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -47,15 +47,6 @@ public class BatchGenerator implements IDhApiWorldGenerator
|
|||||||
private static final IWrapperFactory FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
private static final IWrapperFactory FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
||||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines how many tasks can be queued per thread. <br><br>
|
|
||||||
*
|
|
||||||
* TODO the multiplier here should change dynamically based on how fast the generator is vs the queuing thread,
|
|
||||||
* if this is too high it may cause issues when moving,
|
|
||||||
* but if it is too low the generator threads won't have enough tasks to work on
|
|
||||||
*/
|
|
||||||
private static final int MAX_QUEUED_TASKS_PER_THREAD = 3;
|
|
||||||
|
|
||||||
public AbstractBatchGenerationEnvironmentWrapper generationEnvironment;
|
public AbstractBatchGenerationEnvironmentWrapper generationEnvironment;
|
||||||
public IDhLevel targetDhLevel;
|
public IDhLevel targetDhLevel;
|
||||||
|
|
||||||
@@ -147,14 +138,6 @@ public class BatchGenerator implements IDhApiWorldGenerator
|
|||||||
@Override
|
@Override
|
||||||
public void preGeneratorTaskStart() { this.generationEnvironment.updateAllFutures(); }
|
public void preGeneratorTaskStart() { this.generationEnvironment.updateAllFutures(); }
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isBusy()
|
|
||||||
{
|
|
||||||
int worldGenThreadCount = Math.max(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads.get(), 1);
|
|
||||||
int maxWorldGenTaskCount = worldGenThreadCount * MAX_QUEUED_TASKS_PER_THREAD;
|
|
||||||
return this.generationEnvironment.getEventCount() > maxWorldGenTaskCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=========//
|
//=========//
|
||||||
|
|||||||
@@ -19,15 +19,23 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.generation;
|
package com.seibel.distanthorizons.core.generation;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhBlockPos;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||||
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
|
||||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||||
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
|
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
||||||
|
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IMutableBlockPosWrapper;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
@@ -49,15 +57,59 @@ public class DhLightingEngine
|
|||||||
* Since these objects are always mutated anyway, using a {@link ThreadLocal} will allow us to
|
* Since these objects are always mutated anyway, using a {@link ThreadLocal} will allow us to
|
||||||
* only create as many of these {@link DhBlockPos} as necessary.
|
* only create as many of these {@link DhBlockPos} as necessary.
|
||||||
*/
|
*/
|
||||||
private static final ThreadLocal<DhBlockPos> PRIMARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPos());
|
private static final ThreadLocal<DhBlockPosMutable> PRIMARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPosMutable());
|
||||||
private static final ThreadLocal<DhBlockPos> SECONDARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPos());
|
private static final ThreadLocal<DhBlockPosMutable> SECONDARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPosMutable());
|
||||||
|
|
||||||
|
/** if enabled will render each block light value when the chunk lighting engine is run */
|
||||||
|
private static final boolean RENDER_BLOCK_LIGHT_WIREFRAME = false;
|
||||||
|
/** if enabled will render each sky light value when the chunk lighting engine is run */
|
||||||
|
private static final boolean RENDER_SKY_LIGHT_WIREFRAME = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for dataSource lighting. <br>
|
||||||
|
* Packed as alternating x and z offsets.
|
||||||
|
*/
|
||||||
|
private static final byte[] ADJACENT_DIRECTION_OFFSETS = new byte[]
|
||||||
|
{
|
||||||
|
-1, 0,
|
||||||
|
+1, 0,
|
||||||
|
0, -1,
|
||||||
|
0, +1
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
|
||||||
private DhLightingEngine() { }
|
private DhLightingEngine() { }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// chunk lighting //
|
||||||
|
//================//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates both block and sky lighting.
|
||||||
|
* @see DhLightingEngine#lightChunk(IChunkWrapper, ArrayList, int, boolean, boolean)
|
||||||
|
*/
|
||||||
|
public void lightChunk(
|
||||||
|
@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList,
|
||||||
|
int maxSkyLight)
|
||||||
|
{ this.lightChunk(centerChunk, nearbyChunkList, maxSkyLight, true, true); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only populates block lights.
|
||||||
|
* @see DhLightingEngine#lightChunk(IChunkWrapper, ArrayList, int, boolean, boolean)
|
||||||
|
*/
|
||||||
|
public void bakeChunkBlockLighting(
|
||||||
|
@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList,
|
||||||
|
int maxSkyLight)
|
||||||
|
{ this.lightChunk(centerChunk, nearbyChunkList, maxSkyLight, true, false); }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note: depending on the implementation of {@link IChunkWrapper#setDhBlockLight(int, int, int, int)} and {@link IChunkWrapper#setDhSkyLight(int, int, int, int)}
|
* Note: depending on the implementation of {@link IChunkWrapper#setDhBlockLight(int, int, int, int)} and {@link IChunkWrapper#setDhSkyLight(int, int, int, int)}
|
||||||
* the light values may be stored in the wrapper itself instead of the wrapped chunk object.
|
* the light values may be stored in the wrapper itself instead of the wrapped chunk object.
|
||||||
@@ -67,21 +119,21 @@ public class DhLightingEngine
|
|||||||
* @param nearbyChunkList should also contain centerChunk
|
* @param nearbyChunkList should also contain centerChunk
|
||||||
* @param maxSkyLight should be a value between 0 and 15
|
* @param maxSkyLight should be a value between 0 and 15
|
||||||
*/
|
*/
|
||||||
public void lightChunk(@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList, int maxSkyLight)
|
private void lightChunk(
|
||||||
|
@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList,
|
||||||
|
int maxSkyLight, boolean updateBlockLight, boolean updateSkyLight)
|
||||||
{
|
{
|
||||||
DhChunkPos centerChunkPos = centerChunk.getChunkPos();
|
DhChunkPos centerChunkPos = centerChunk.getChunkPos();
|
||||||
AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk);
|
AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk);
|
||||||
|
|
||||||
long startTimeNs = System.nanoTime();
|
|
||||||
|
|
||||||
|
|
||||||
// try-finally to handle the stableArray resources
|
// try-finally to handle the stableArray resources
|
||||||
StableLightPosStack blockLightPosQueue = null;
|
StableLightPosStack blockLightWorldPosQueue = null;
|
||||||
StableLightPosStack skyLightPosQueue = null;
|
StableLightPosStack skyLightWorldPosQueue = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
blockLightPosQueue = StableLightPosStack.borrowStableLightPosArray();
|
blockLightWorldPosQueue = StableLightPosStack.borrowStableLightPosArray();
|
||||||
skyLightPosQueue = StableLightPosStack.borrowStableLightPosArray();
|
skyLightWorldPosQueue = StableLightPosStack.borrowStableLightPosArray();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -92,7 +144,7 @@ public class DhLightingEngine
|
|||||||
{
|
{
|
||||||
for (int zOffset = -1; zOffset <= 1; zOffset++)
|
for (int zOffset = -1; zOffset <= 1; zOffset++)
|
||||||
{
|
{
|
||||||
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.x + xOffset, centerChunkPos.z + zOffset);
|
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.getX() + xOffset, centerChunkPos.getZ() + zOffset);
|
||||||
requestedAdjacentPositions.add(adjacentPos);
|
requestedAdjacentPositions.add(adjacentPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +152,6 @@ public class DhLightingEngine
|
|||||||
|
|
||||||
// find all adjacent chunks
|
// find all adjacent chunks
|
||||||
// and get any necessary info from them
|
// and get any necessary info from them
|
||||||
boolean warningLogged = false;
|
|
||||||
for (int chunkIndex = 0; chunkIndex < nearbyChunkList.size(); chunkIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
|
for (int chunkIndex = 0; chunkIndex < nearbyChunkList.size(); chunkIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
|
||||||
{
|
{
|
||||||
IChunkWrapper chunk = nearbyChunkList.get(chunkIndex);
|
IChunkWrapper chunk = nearbyChunkList.get(chunkIndex);
|
||||||
@@ -112,33 +163,46 @@ public class DhLightingEngine
|
|||||||
// add the adjacent chunk
|
// add the adjacent chunk
|
||||||
adjacentChunkHolder.add(chunk);
|
adjacentChunkHolder.add(chunk);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// get and set the adjacent chunk's initial block lights
|
// get and set the adjacent chunk's initial block lights
|
||||||
final DhBlockPos relLightBlockPos = PRIMARY_BLOCK_POS_REF.get();
|
final DhBlockPosMutable relLightBlockPos = PRIMARY_BLOCK_POS_REF.get();
|
||||||
final DhBlockPos relBlockPos = SECONDARY_BLOCK_POS_REF.get();
|
|
||||||
|
|
||||||
ArrayList<DhBlockPos> blockLightPosList = chunk.getBlockLightPosList();
|
|
||||||
for (int blockLightIndex = 0; blockLightIndex < blockLightPosList.size(); blockLightIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
|
|
||||||
|
//==================//
|
||||||
|
// set block lights //
|
||||||
|
//==================//
|
||||||
|
|
||||||
|
if (updateBlockLight)
|
||||||
{
|
{
|
||||||
DhBlockPos blockLightPos = blockLightPosList.get(blockLightIndex);
|
ArrayList<DhBlockPos> blockLightPosList = chunk.getWorldBlockLightPosList();
|
||||||
blockLightPos.mutateToChunkRelativePos(relLightBlockPos);
|
for (int blockLightIndex = 0; blockLightIndex < blockLightPosList.size(); blockLightIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
|
||||||
|
{
|
||||||
// get the light
|
DhBlockPos blockLightPos = blockLightPosList.get(blockLightIndex);
|
||||||
IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos);
|
blockLightPos.mutateToChunkRelativePos(relLightBlockPos);
|
||||||
int lightValue = blockState.getLightEmission();
|
|
||||||
blockLightPosQueue.push(blockLightPos.x, blockLightPos.y, blockLightPos.z, lightValue);
|
// get the light
|
||||||
|
IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos);
|
||||||
// set the light
|
int lightValue = blockState.getLightEmission();
|
||||||
blockLightPos.mutateToChunkRelativePos(relBlockPos);
|
blockLightWorldPosQueue.push(blockLightPos.getX(), blockLightPos.getY(), blockLightPos.getZ(), lightValue);
|
||||||
chunk.setDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, lightValue);
|
|
||||||
|
// set the light
|
||||||
|
chunk.setDhBlockLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), lightValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//================//
|
||||||
|
// set sky lights //
|
||||||
|
//================//
|
||||||
|
|
||||||
// get and set the adjacent chunk's initial skylights,
|
// get and set the adjacent chunk's initial skylights,
|
||||||
// if the dimension has skylights
|
// if the dimension has skylights
|
||||||
if (maxSkyLight > 0)
|
if (updateSkyLight && maxSkyLight > 0)
|
||||||
{
|
{
|
||||||
|
IMutableBlockPosWrapper mcBlockPos = chunk.getMutableBlockPosWrapper();
|
||||||
|
IBlockStateWrapper previousBlockState = null;
|
||||||
|
|
||||||
int maxY = chunk.getMaxNonEmptyHeight();
|
int maxY = chunk.getMaxNonEmptyHeight();
|
||||||
int minY = chunk.getMinBuildHeight();
|
int minY = chunk.getMinBuildHeight();
|
||||||
|
|
||||||
@@ -147,10 +211,10 @@ public class DhLightingEngine
|
|||||||
{
|
{
|
||||||
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
|
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
|
||||||
{
|
{
|
||||||
// set each pos' sky light all the way down until a opaque block is hit
|
// set each pos' sky light all the way down until an opaque block is hit
|
||||||
for (int y = maxY; y >= minY; y--)
|
for (int y = maxY; y >= minY; y--)
|
||||||
{
|
{
|
||||||
IBlockStateWrapper block = chunk.getBlockState(relX, y, relZ);
|
IBlockStateWrapper block = previousBlockState = chunk.getBlockState(relX, y, relZ, mcBlockPos, previousBlockState);
|
||||||
if (block != null && block.getOpacity() != LodUtil.BLOCK_FULLY_TRANSPARENT)
|
if (block != null && block.getOpacity() != LodUtil.BLOCK_FULLY_TRANSPARENT)
|
||||||
{
|
{
|
||||||
// keep moving down until we find a non-transparent block
|
// keep moving down until we find a non-transparent block
|
||||||
@@ -160,11 +224,11 @@ public class DhLightingEngine
|
|||||||
|
|
||||||
// add sky light to the queue
|
// add sky light to the queue
|
||||||
DhBlockPos skyLightPos = new DhBlockPos(chunk.getMinBlockX() + relX, y, chunk.getMinBlockZ() + relZ);
|
DhBlockPos skyLightPos = new DhBlockPos(chunk.getMinBlockX() + relX, y, chunk.getMinBlockZ() + relZ);
|
||||||
skyLightPosQueue.push(skyLightPos.x, skyLightPos.y, skyLightPos.z, maxSkyLight);
|
skyLightWorldPosQueue.push(skyLightPos.getX(), skyLightPos.getY(), skyLightPos.getZ(), maxSkyLight);
|
||||||
|
|
||||||
// set the chunk's sky light
|
// set the chunk's sky light
|
||||||
skyLightPos.mutateToChunkRelativePos(relBlockPos);
|
skyLightPos.mutateToChunkRelativePos(relLightBlockPos);
|
||||||
chunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, maxSkyLight);
|
chunk.setDhSkyLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), maxSkyLight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,14 +244,22 @@ public class DhLightingEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
// block light
|
// block light
|
||||||
this.propagateLightPosList(blockLightPosQueue, adjacentChunkHolder,
|
if (updateBlockLight)
|
||||||
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z),
|
{
|
||||||
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, newLightValue));
|
this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
|
||||||
|
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
|
||||||
|
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
// sky light
|
// sky light
|
||||||
this.propagateLightPosList(skyLightPosQueue, adjacentChunkHolder,
|
if (updateSkyLight)
|
||||||
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z),
|
{
|
||||||
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, newLightValue));
|
this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder,
|
||||||
|
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
|
||||||
|
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
|
||||||
|
false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -195,31 +267,35 @@ public class DhLightingEngine
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
StableLightPosStack.returnStableLightPosArray(blockLightPosQueue);
|
StableLightPosStack.returnStableLightPosArray(blockLightWorldPosQueue);
|
||||||
StableLightPosStack.returnStableLightPosArray(skyLightPosQueue);
|
StableLightPosStack.returnStableLightPosArray(skyLightWorldPosQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (updateBlockLight)
|
||||||
centerChunk.setIsDhLightCorrect(true);
|
{
|
||||||
centerChunk.setUseDhLighting(true);
|
centerChunk.setIsDhBlockLightCorrect(true);
|
||||||
|
}
|
||||||
long endTimeNs = System.nanoTime();
|
if (updateSkyLight)
|
||||||
float totalTimeMs = (endTimeNs - startTimeNs) / 1_000_000.0f;
|
{
|
||||||
//LOGGER.trace("Finished generating lighting for chunk: [" + centerChunkPos + "] in ["+totalTimeMs+"] milliseconds");
|
centerChunk.setIsDhSkyLightCorrect(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */
|
/** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */
|
||||||
private void propagateLightPosList(
|
private void propagateChunkLightPosList(
|
||||||
StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder,
|
StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder,
|
||||||
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc)
|
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc,
|
||||||
|
boolean propagatingBlockLights)
|
||||||
{
|
{
|
||||||
// these objects are saved so they can be mutated throughout the method,
|
// these objects are saved so they can be mutated throughout the method,
|
||||||
// this reduces the number of allocations necessary, reducing GC pressure
|
// this reduces the number of allocations necessary, reducing GC pressure
|
||||||
final LightPos lightPos = new LightPos(0, 0, 0, 0);
|
final LightPos lightPos = new LightPos(0, 0, 0, 0);
|
||||||
final DhBlockPos neighbourBlockPos = PRIMARY_BLOCK_POS_REF.get();
|
final DhBlockPosMutable neighbourBlockPos = PRIMARY_BLOCK_POS_REF.get();
|
||||||
final DhBlockPos relNeighbourBlockPos = SECONDARY_BLOCK_POS_REF.get();
|
final DhBlockPosMutable relNeighbourBlockPos = SECONDARY_BLOCK_POS_REF.get();
|
||||||
|
|
||||||
|
IMutableBlockPosWrapper mcBlockPos = null;
|
||||||
|
IBlockStateWrapper previousBlockState = null;
|
||||||
|
|
||||||
// update each light position
|
// update each light position
|
||||||
while (!lightPosQueue.isEmpty())
|
while (!lightPosQueue.isEmpty())
|
||||||
@@ -239,14 +315,14 @@ public class DhLightingEngine
|
|||||||
|
|
||||||
|
|
||||||
// only continue if the light position is inside one of our chunks
|
// only continue if the light position is inside one of our chunks
|
||||||
IChunkWrapper neighbourChunk = adjacentChunkHolder.getByBlockPos(neighbourBlockPos.x, neighbourBlockPos.z);
|
IChunkWrapper neighbourChunk = adjacentChunkHolder.getByBlockPos(neighbourBlockPos.getX(), neighbourBlockPos.getZ());
|
||||||
if (neighbourChunk == null)
|
if (neighbourChunk == null)
|
||||||
{
|
{
|
||||||
// the light pos is outside our generator's range, ignore it
|
// the light pos is outside our generator's range, ignore it
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relNeighbourBlockPos.y < neighbourChunk.getMinNonEmptyHeight() || relNeighbourBlockPos.y > neighbourChunk.getMaxBuildHeight())
|
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight() || relNeighbourBlockPos.getY() > neighbourChunk.getMaxBuildHeight())
|
||||||
{
|
{
|
||||||
// the light pos is outside the chunk's min/max height,
|
// the light pos is outside the chunk's min/max height,
|
||||||
// this can happen if given a chunk that hasn't finished generating
|
// this can happen if given a chunk that hasn't finished generating
|
||||||
@@ -263,7 +339,15 @@ public class DhLightingEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
IBlockStateWrapper neighbourBlockState = neighbourChunk.getBlockState(relNeighbourBlockPos);
|
if (mcBlockPos == null)
|
||||||
|
{
|
||||||
|
// it doesn't matter what chunk we get the position object from
|
||||||
|
// TODO move this getter logic out of ChunkWrapper
|
||||||
|
mcBlockPos = neighbourChunk.getMutableBlockPosWrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IBlockStateWrapper neighbourBlockState = previousBlockState = neighbourChunk.getBlockState(relNeighbourBlockPos, mcBlockPos, previousBlockState);
|
||||||
// Math.max(1, ...) is used so that the propagated light level always drops by at least 1, preventing infinite cycles.
|
// Math.max(1, ...) is used so that the propagated light level always drops by at least 1, preventing infinite cycles.
|
||||||
int targetLevel = lightValue - Math.max(1, neighbourBlockState.getOpacity());
|
int targetLevel = lightValue - Math.max(1, neighbourBlockState.getOpacity());
|
||||||
if (targetLevel > currentBlockLight)
|
if (targetLevel > currentBlockLight)
|
||||||
@@ -273,16 +357,338 @@ public class DhLightingEngine
|
|||||||
|
|
||||||
// now that light has been propagated to this blockPos
|
// now that light has been propagated to this blockPos
|
||||||
// we need to queue it up so its neighbours can be propagated as well
|
// we need to queue it up so its neighbours can be propagated as well
|
||||||
lightPosQueue.push(neighbourBlockPos.x, neighbourBlockPos.y, neighbourBlockPos.z, targetLevel);
|
lightPosQueue.push(neighbourBlockPos.getX(), neighbourBlockPos.getY(), neighbourBlockPos.getZ(), targetLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// can be enable if troubleshooting lighting issues
|
||||||
|
if (RENDER_BLOCK_LIGHT_WIREFRAME && propagatingBlockLights)
|
||||||
|
{
|
||||||
|
RenderDhLightValuesAsWireframe(adjacentChunkHolder, true);
|
||||||
|
}
|
||||||
|
else if (RENDER_SKY_LIGHT_WIREFRAME && !propagatingBlockLights)
|
||||||
|
{
|
||||||
|
RenderDhLightValuesAsWireframe(adjacentChunkHolder, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// propagation complete
|
// propagation complete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//======================//
|
||||||
|
// data source lighting //
|
||||||
|
//======================//
|
||||||
|
|
||||||
|
/** @author BuilderB0y */
|
||||||
|
public void bakeDataSourceSkyLight(FullDataSourceV2 dataSource, int maxSkyLight)
|
||||||
|
{
|
||||||
|
// create a cache of all the IDs which are completely transparent.
|
||||||
|
// FullDataPointIdMap is thread-safe with locks, and is also a map lookup,
|
||||||
|
// and both of these things add a bit of overhead which is not necessary
|
||||||
|
// in this context.
|
||||||
|
// note: since IDs map to both biomes and blocks, there can be more than
|
||||||
|
// one ID which corresponds to air.
|
||||||
|
BitSet airIDs = new BitSet(dataSource.mapping.size());
|
||||||
|
for (int id = 0, size = dataSource.mapping.size(); id < size; id++)
|
||||||
|
{
|
||||||
|
if (dataSource.mapping.getBlockStateWrapper(id).getOpacity() == 0)
|
||||||
|
{
|
||||||
|
airIDs.set(id, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
|
||||||
|
{
|
||||||
|
LongArrayList dataPoints = dataSource.get(x, z);
|
||||||
|
if (dataPoints != null && !dataPoints.isEmpty())
|
||||||
|
{
|
||||||
|
// iterate through the data points in this column top-down
|
||||||
|
// until we reach light level 0 in some way. at this point,
|
||||||
|
// no more propagation needs to be performed for this column.
|
||||||
|
int size = dataPoints.size();
|
||||||
|
for (int index = 0; index < size; index++)
|
||||||
|
{
|
||||||
|
long point = dataPoints.getLong(index);
|
||||||
|
// if the data point in the column is transparent,
|
||||||
|
// then fill it with light and then propagate
|
||||||
|
// that light both horizontally and downwards.
|
||||||
|
if (airIDs.get(FullDataPointUtil.getId(point)))
|
||||||
|
{
|
||||||
|
int skylight;
|
||||||
|
if (index == 0)
|
||||||
|
{
|
||||||
|
// top-most data point in the column.
|
||||||
|
skylight = maxSkyLight;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// handle down propagation here. sort of.
|
||||||
|
// down propagation is also handled partially elsewhere.
|
||||||
|
// basically if the data point above is transparent,
|
||||||
|
// we copy its light level.
|
||||||
|
// otherwise, if the data point above is opaque,
|
||||||
|
// then no light can propagate downwards from it.
|
||||||
|
// therefore, this data point should be light level 0*
|
||||||
|
// and no more propagation needs to be performed for this column.
|
||||||
|
//
|
||||||
|
// *unless light propagates into it horizontally,
|
||||||
|
// but that is handled separately.
|
||||||
|
long above = dataPoints.getLong(index - 1);
|
||||||
|
if (airIDs.get(FullDataPointUtil.getId(above)))
|
||||||
|
{
|
||||||
|
skylight = FullDataPointUtil.getSkyLight(above);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the data point to contain the correct starting skylight level.
|
||||||
|
point = FullDataPointUtil.setSkyLight(point, skylight);
|
||||||
|
dataPoints.set(index, point);
|
||||||
|
// now for the propagation.
|
||||||
|
recursivelyLightAdjacentDataPoints(dataSource, airIDs, x, z, point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// at this point, all transparent data points have been lit,
|
||||||
|
// but opaque ones still have light level 0.
|
||||||
|
// in this loop we make opaque data points copy the light level
|
||||||
|
// above them if, and only if, the data point above is translucent.
|
||||||
|
// with one exception: if the data point above is only partially translucent,
|
||||||
|
// we use a slightly different way of computing how much light it absorbed.
|
||||||
|
// this is how we handle water and ocean floors.
|
||||||
|
// note that this alternate logic assumes the
|
||||||
|
// data point above is being lit from the top.
|
||||||
|
// this is a fine assumption for water and oceans.
|
||||||
|
for (LongArrayList list : dataSource.dataPoints)
|
||||||
|
{
|
||||||
|
if (list != null)
|
||||||
|
{
|
||||||
|
for (int index = 0, size = list.size(); index < size; index++)
|
||||||
|
{
|
||||||
|
long dataPoint = list.getLong(index);
|
||||||
|
if (index == 0)
|
||||||
|
{
|
||||||
|
// top data point, assume "above" has the max sky light.
|
||||||
|
dataPoint = FullDataPointUtil.setSkyLight(dataPoint, maxSkyLight);
|
||||||
|
list.set(index, dataPoint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// there is another data point above this one.
|
||||||
|
// check to see how opaque this data point is first.
|
||||||
|
// we will check the above one after that.
|
||||||
|
if (!airIDs.get(FullDataPointUtil.getId(dataPoint)))
|
||||||
|
{
|
||||||
|
// this data point is not transparent.
|
||||||
|
// it should be lit from above.
|
||||||
|
long above = list.getLong(index - 1);
|
||||||
|
int aboveLight = FullDataPointUtil.getSkyLight(above);
|
||||||
|
if (airIDs.get(FullDataPointUtil.getId(above)))
|
||||||
|
{
|
||||||
|
// the above data point is transparent,
|
||||||
|
// and does not absorb any light.
|
||||||
|
// its light level can be copied as-is.
|
||||||
|
dataPoint = FullDataPointUtil.setSkyLight(dataPoint, aboveLight);
|
||||||
|
list.set(index, dataPoint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// determine how much light should be absorbed by this column
|
||||||
|
int absorption = dataSource.mapping.getBlockStateWrapper(FullDataPointUtil.getId(above)).getOpacity() * FullDataPointUtil.getHeight(above);
|
||||||
|
if (absorption < aboveLight)
|
||||||
|
{
|
||||||
|
// the above data point is partially translucent,
|
||||||
|
// and absorbs some light. however, it did not absorb
|
||||||
|
// enough light to bring the light level down to 0.
|
||||||
|
// so, the remaining light can still be copied.
|
||||||
|
dataPoint = FullDataPointUtil.setSkyLight(dataPoint, aboveLight - absorption);
|
||||||
|
list.set(index, dataPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @author BuilderB0y */
|
||||||
|
public void recursivelyLightAdjacentDataPoints(
|
||||||
|
FullDataSourceV2 chunk,
|
||||||
|
BitSet airIDs,
|
||||||
|
int relativeX,
|
||||||
|
int relativeZ,
|
||||||
|
long dataPoint
|
||||||
|
)
|
||||||
|
{
|
||||||
|
int lightLevel = FullDataPointUtil.getSkyLight(dataPoint);
|
||||||
|
// early exit condition:
|
||||||
|
// in this case, propagating light is guaranteed to be 0 at adjacent positions,
|
||||||
|
// and therefore we do not need to waste time propagating it.
|
||||||
|
if (lightLevel <= 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int minY = FullDataPointUtil.getBottomY(dataPoint);
|
||||||
|
int maxY = FullDataPointUtil.getHeight(dataPoint) + minY;
|
||||||
|
// try to propagate in all 4 directions.
|
||||||
|
for (int offsetIndex = 0; offsetIndex < ADJACENT_DIRECTION_OFFSETS.length; )
|
||||||
|
{
|
||||||
|
int adjacentX = relativeX + ADJACENT_DIRECTION_OFFSETS[offsetIndex++];
|
||||||
|
int adjacentZ = relativeZ + ADJACENT_DIRECTION_OFFSETS[offsetIndex++];
|
||||||
|
|
||||||
|
// check if the adjacent position is within the bounds of this data source...
|
||||||
|
if (adjacentX >= 0 && adjacentX < FullDataSourceV2.WIDTH && adjacentZ >= 0 && adjacentZ < FullDataSourceV2.WIDTH)
|
||||||
|
{
|
||||||
|
LongArrayList adjacentDataPoints = chunk.get(adjacentX, adjacentZ);
|
||||||
|
// ...and also check to make sure we have some data points
|
||||||
|
// (potentially transparent ones) to propagate through in the adjacent column.
|
||||||
|
if (adjacentDataPoints != null)
|
||||||
|
{
|
||||||
|
// try to find adjacent data points we can propagate into.
|
||||||
|
// we go top-down for this, which will be important for some
|
||||||
|
// later conditions.
|
||||||
|
int size = adjacentDataPoints.size();
|
||||||
|
for (int adjacentIndex = 0; adjacentIndex < size; adjacentIndex++)
|
||||||
|
{
|
||||||
|
long adjacentDataPoint = adjacentDataPoints.getLong(adjacentIndex);
|
||||||
|
int adjacentMinY = FullDataPointUtil.getBottomY(adjacentDataPoint);
|
||||||
|
int adjacentMaxY = FullDataPointUtil.getHeight(adjacentDataPoint) + adjacentMinY;
|
||||||
|
if (adjacentMinY >= maxY)
|
||||||
|
{
|
||||||
|
// if the adjacent data point is completely above this one,
|
||||||
|
// then there is no overlap between this one and the adjacent one,
|
||||||
|
// and therefore light cannot propagate here.
|
||||||
|
// try to propagate to the next data point down from the adjacent one.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (adjacentMaxY <= minY)
|
||||||
|
{
|
||||||
|
// if the adjacent data point is completely below this one,
|
||||||
|
// then it also has no overlap and can't propagate,
|
||||||
|
// but since we're going top-down, neither can any subsequent adjacent data points.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (!airIDs.get(FullDataPointUtil.getId(adjacentDataPoint)))
|
||||||
|
{
|
||||||
|
// assume for now that we cannot propagate into non-transparent data points.
|
||||||
|
continue; // TODO how does this work with water? Do we care?
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// now we can try to propagate.
|
||||||
|
int adjacentLightLevel = FullDataPointUtil.getSkyLight(adjacentDataPoint);
|
||||||
|
// if the resulting light level after propagation would INCREASE
|
||||||
|
// the light level of the adjacent data point, then propagate to it.
|
||||||
|
// otherwise, don't do that.
|
||||||
|
if (lightLevel - 1 > adjacentLightLevel)
|
||||||
|
{
|
||||||
|
adjacentDataPoint = FullDataPointUtil.setSkyLight(adjacentDataPoint, lightLevel - 1);
|
||||||
|
adjacentDataPoints.set(adjacentIndex, adjacentDataPoint);
|
||||||
|
// if propagation succeeded, recursively propagate again starting at the adjacent data point.
|
||||||
|
recursivelyLightAdjacentDataPoints(chunk, airIDs, adjacentX, adjacentZ, adjacentDataPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===========//
|
||||||
|
// debugging //
|
||||||
|
//===========//
|
||||||
|
|
||||||
|
/** Draw a wireframe representing each block's light value */
|
||||||
|
private static void RenderDhLightValuesAsWireframe(AdjacentChunkHolder adjacentChunkHolder, boolean renderBlockLights)
|
||||||
|
{
|
||||||
|
for (IChunkWrapper chunk : adjacentChunkHolder.chunkArray)
|
||||||
|
{
|
||||||
|
if (chunk == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int chunkMinX = chunk.getMinBlockX();
|
||||||
|
int chunkMinZ = chunk.getMinBlockZ();
|
||||||
|
|
||||||
|
int minY = chunk.getMinNonEmptyHeight();
|
||||||
|
int maxY = chunk.getMaxNonEmptyHeight();
|
||||||
|
|
||||||
|
// check each position's light
|
||||||
|
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
|
||||||
|
{
|
||||||
|
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
|
||||||
|
{
|
||||||
|
for (int y = minY; y < maxY; y++)
|
||||||
|
{
|
||||||
|
int lightValue = renderBlockLights? chunk.getDhBlockLight(x, y, z) : chunk.getDhSkyLight(x, y, z);
|
||||||
|
if (lightValue != LodUtil.MIN_MC_LIGHT)
|
||||||
|
{
|
||||||
|
// hotter colors for more intense light
|
||||||
|
Color color;
|
||||||
|
if (lightValue >= 14)
|
||||||
|
{
|
||||||
|
color = Color.WHITE;
|
||||||
|
}
|
||||||
|
else if (lightValue >= 10)
|
||||||
|
{
|
||||||
|
color = Color.PINK;
|
||||||
|
}
|
||||||
|
else if (lightValue >= 6)
|
||||||
|
{
|
||||||
|
color = Color.YELLOW;
|
||||||
|
}
|
||||||
|
else if (lightValue >= 4)
|
||||||
|
{
|
||||||
|
color = Color.ORANGE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
color = Color.RED;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// a color can be set to null if you only want to troubleshoot up to a certain light level
|
||||||
|
if (color != null)
|
||||||
|
{
|
||||||
|
DebugRenderer.makeParticle(
|
||||||
|
new DebugRenderer.BoxParticle(
|
||||||
|
new DebugRenderer.Box(DhSectionPos.encode((byte) 0, chunkMinX + x, chunkMinZ + z), y, y + 1, 0.2f, color),
|
||||||
|
10.0, 0f
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
//================//
|
||||||
// helper classes //
|
// helper classes //
|
||||||
//================//
|
//================//
|
||||||
@@ -293,7 +699,7 @@ public class DhLightingEngine
|
|||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
interface ISetLightFunc { void setLight(IChunkWrapper chunk, DhBlockPos pos, int lightValue); }
|
interface ISetLightFunc { void setLight(IChunkWrapper chunk, DhBlockPos pos, int lightValue); }
|
||||||
|
|
||||||
private static class LightPos extends DhBlockPos
|
private static class LightPos extends DhBlockPosMutable
|
||||||
{
|
{
|
||||||
public int lightValue;
|
public int lightValue;
|
||||||
|
|
||||||
@@ -303,6 +709,11 @@ public class DhLightingEngine
|
|||||||
this.lightValue = lightValue;
|
this.lightValue = lightValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() { return this.lightValue+" - ["+ this.x +", "+ this.y +", "+ this.z +"]"; }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -402,9 +813,9 @@ public class DhLightingEngine
|
|||||||
{
|
{
|
||||||
int subIndex = this.index * INTS_PER_LIGHT_POS;
|
int subIndex = this.index * INTS_PER_LIGHT_POS;
|
||||||
|
|
||||||
pos.x = this.lightPositions.getInt(subIndex);
|
pos.setX(this.lightPositions.getInt(subIndex));
|
||||||
pos.y = this.lightPositions.getInt(subIndex + 1);
|
pos.setY(this.lightPositions.getInt(subIndex + 1));
|
||||||
pos.z = this.lightPositions.getInt(subIndex + 2);
|
pos.setZ(this.lightPositions.getInt(subIndex + 2));
|
||||||
pos.lightValue = this.lightPositions.getInt(subIndex + 3);
|
pos.lightValue = this.lightPositions.getInt(subIndex + 3);
|
||||||
|
|
||||||
this.index--;
|
this.index--;
|
||||||
|
|||||||
@@ -21,11 +21,12 @@ package com.seibel.distanthorizons.core.generation;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
||||||
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.render.LodQuadTree;
|
import com.seibel.distanthorizons.core.render.LodQuadTree;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,9 +50,17 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
|
|||||||
// getters //
|
// getters //
|
||||||
//=========//
|
//=========//
|
||||||
|
|
||||||
/** the largest numerical detail level */
|
/**
|
||||||
|
* The largest numerical detail level. <br>
|
||||||
|
* Detail level is absolute, not section;
|
||||||
|
* IE 0 = Block, 1 = 2x2 blocks, etc.
|
||||||
|
*/
|
||||||
byte lowestDataDetail();
|
byte lowestDataDetail();
|
||||||
/** the smallest numerical detail level */
|
/**
|
||||||
|
* The smallest numerical detail level. <br>
|
||||||
|
* Detail level is absolute, not section;
|
||||||
|
* IE 0 = Block, 1 = 2x2 blocks, etc.
|
||||||
|
*/
|
||||||
byte highestDataDetail();
|
byte highestDataDetail();
|
||||||
|
|
||||||
|
|
||||||
@@ -81,7 +90,7 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
|
|||||||
*/
|
*/
|
||||||
void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf);
|
void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf);
|
||||||
|
|
||||||
CompletableFuture<WorldGenResult> submitGenTask(long pos, byte requiredDataDetail, IWorldGenTaskTracker tracker);
|
CompletableFuture<WorldGenResult> submitRetrievalTask(long pos, byte requiredDataDetail, IWorldGenTaskTracker tracker);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -89,7 +98,10 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
|
|||||||
// shutdown //
|
// shutdown //
|
||||||
//==========//
|
//==========//
|
||||||
|
|
||||||
CompletableFuture<Void> startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning);
|
/** Can be used to let any lingering generation requests finish before fully shutting down the system */
|
||||||
|
CompletableFuture<Void> startClosingAsync(boolean cancelCurrentGeneration, boolean alsoInterruptRunning);
|
||||||
|
|
||||||
|
@Override
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
|
|
||||||
@@ -104,6 +116,8 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
|
|||||||
/** used for rendering to the F3 menu */
|
/** used for rendering to the F3 menu */
|
||||||
int getEstimatedTotalTaskCount();
|
int getEstimatedTotalTaskCount();
|
||||||
void setEstimatedTotalTaskCount(int newEstimate);
|
void setEstimatedTotalTaskCount(int newEstimate);
|
||||||
|
|
||||||
|
void addDebugMenuStringsToList(List<String> messageList);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package com.seibel.distanthorizons.core.generation;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
|
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
||||||
|
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
||||||
|
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.multiplayer.client.AbstractFullDataNetworkRequestQueue;
|
||||||
|
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
|
||||||
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
|
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
||||||
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable
|
||||||
|
{
|
||||||
|
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||||
|
|
||||||
|
private int estimatedTotalTaskCount;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
|
||||||
|
public RemoteWorldRetrievalQueue(ClientNetworkState networkState, IDhClientLevel level)
|
||||||
|
{ super(networkState, level, false, Config.Client.Advanced.Debugging.DebugWireframe.showWorldGenQueue); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===========================//
|
||||||
|
// retrieval queue overrides //
|
||||||
|
//===========================//
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startAndSetTargetPos(DhBlockPos2D targetPos) { super.tick(targetPos); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte lowestDataDetail() { return LodUtil.BLOCK_DETAIL_LEVEL; }
|
||||||
|
@Override
|
||||||
|
public byte highestDataDetail() { return LodUtil.BLOCK_DETAIL_LEVEL; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<WorldGenResult> submitRetrievalTask(long sectionPos, byte requiredDataDetail, IWorldGenTaskTracker tracker)
|
||||||
|
{
|
||||||
|
return super.submitRequest(sectionPos, tracker.getDataSourceConsumer())
|
||||||
|
.thenApply(retrievalSuccess -> retrievalSuccess
|
||||||
|
? WorldGenResult.CreateSuccess(sectionPos)
|
||||||
|
: WorldGenResult.CreateFail());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> startClosingAsync(boolean cancelCurrentGeneration, boolean alsoInterruptRunning)
|
||||||
|
{ return super.startClosingAsync(alsoInterruptRunning); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=================================//
|
||||||
|
// network request queue overrides //
|
||||||
|
//=================================//
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getRequestRateLimit() { return this.networkState.sessionConfig.getGenerationRequestRateLimit(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getQueueName() { return "World Remote Generation Queue"; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===============//
|
||||||
|
// debug display //
|
||||||
|
//===============//
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEstimatedTotalTaskCount() { return this.estimatedTotalTaskCount; }
|
||||||
|
@Override
|
||||||
|
public void setEstimatedTotalTaskCount(int newEstimate) { this.estimatedTotalTaskCount = newEstimate; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
|
|||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.*;
|
import com.seibel.distanthorizons.core.generation.tasks.*;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
@@ -47,6 +47,7 @@ import org.apache.logging.log4j.Logger;
|
|||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -55,6 +56,16 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||||
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines how many tasks can be queued per thread. <br><br>
|
||||||
|
*
|
||||||
|
* TODO the multiplier here should change dynamically based on how fast the generator is vs the queuing thread,
|
||||||
|
* if this is too high it may cause issues when moving,
|
||||||
|
* but if it is too low the generator threads won't have enough tasks to work on
|
||||||
|
*/
|
||||||
|
private static final int MAX_QUEUED_TASKS_PER_THREAD = 3;
|
||||||
|
|
||||||
|
|
||||||
private final IDhApiWorldGenerator generator;
|
private final IDhApiWorldGenerator generator;
|
||||||
|
|
||||||
/** contains the positions that need to be generated */
|
/** contains the positions that need to be generated */
|
||||||
@@ -78,7 +89,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
// TODO this logic isn't great and can cause a limit to how many threads could be used for world generation,
|
// TODO this logic isn't great and can cause a limit to how many threads could be used for world generation,
|
||||||
// however it won't cause duplicate requests or concurrency issues, so it will be good enough for now.
|
// however it won't cause duplicate requests or concurrency issues, so it will be good enough for now.
|
||||||
// A good long term fix may be to either:
|
// A good long term fix may be to either:
|
||||||
// 1. allow the generator to deal with larger sections (let the generator threads split up larger tasks into smaller one
|
// 1. allow the generator to deal with larger sections (let the generator threads split up larger tasks into smaller ones
|
||||||
// 2. batch requests better. instead of sending 4 individual tasks of detail level N, send 1 task of detail level n+1
|
// 2. batch requests better. instead of sending 4 individual tasks of detail level N, send 1 task of detail level n+1
|
||||||
private final ExecutorService queueingThread = ThreadUtil.makeSingleThreadPool("World Gen Queue");
|
private final ExecutorService queueingThread = ThreadUtil.makeSingleThreadPool("World Gen Queue");
|
||||||
private boolean generationQueueRunning = false;
|
private boolean generationQueueRunning = false;
|
||||||
@@ -131,7 +142,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
//=================//
|
//=================//
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<WorldGenResult> submitGenTask(long pos, byte requiredDataDetail, IWorldGenTaskTracker tracker)
|
public CompletableFuture<WorldGenResult> submitRetrievalTask(long pos, byte requiredDataDetail, IWorldGenTaskTracker tracker)
|
||||||
{
|
{
|
||||||
// the generator is shutting down, don't add new tasks
|
// the generator is shutting down, don't add new tasks
|
||||||
if (this.generatorClosingFuture != null)
|
if (this.generatorClosingFuture != null)
|
||||||
@@ -206,7 +217,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
|
|
||||||
// queue generation tasks until the generator is full, or there are no more tasks to generate
|
// queue generation tasks until the generator is full, or there are no more tasks to generate
|
||||||
boolean taskStarted = true;
|
boolean taskStarted = true;
|
||||||
while (!this.generator.isBusy() && taskStarted)
|
while (!this.isGeneratorBusy() && taskStarted)
|
||||||
{
|
{
|
||||||
taskStarted = this.startNextWorldGenTask(this.generationTargetPos);
|
taskStarted = this.startNextWorldGenTask(this.generationTargetPos);
|
||||||
if (!taskStarted)
|
if (!taskStarted)
|
||||||
@@ -226,10 +237,26 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LOGGER.error("queueing exception: " + e.getMessage(), e);
|
LOGGER.error("queueing exception: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
this.generationQueueRunning = false;
|
this.generationQueueRunning = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public boolean isGeneratorBusy()
|
||||||
|
{
|
||||||
|
ThreadPoolExecutor executor = ThreadPoolUtil.getWorldGenExecutor();
|
||||||
|
if (executor == null)
|
||||||
|
{
|
||||||
|
// shouldn't happen, but just in case, don't queue more tasks
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int worldGenThreadCount = Math.max(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads.get(), 1);
|
||||||
|
int maxWorldGenTaskCount = worldGenThreadCount * MAX_QUEUED_TASKS_PER_THREAD;
|
||||||
|
return executor.getQueue().size() > maxWorldGenTaskCount;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param targetPos the position to center the generation around
|
* @param targetPos the position to center the generation around
|
||||||
@@ -373,7 +400,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
// don't log the shutdown exceptions
|
// don't log the shutdown exceptions
|
||||||
if (!LodUtil.isInterruptOrReject(exception))
|
if (!LodUtil.isInterruptOrReject(exception))
|
||||||
{
|
{
|
||||||
LOGGER.error("Error generating data for section " + taskPos, exception);
|
LOGGER.error("Error generating data for pos: " + DhSectionPos.toString(taskPos), exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateFail()));
|
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateFail()));
|
||||||
@@ -383,11 +410,11 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(DhSectionPos.encode(granularity, DhSectionPos.getX(taskPos), DhSectionPos.getZ(taskPos)))));
|
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(DhSectionPos.encode(granularity, DhSectionPos.getX(taskPos), DhSectionPos.getZ(taskPos)))));
|
||||||
}
|
}
|
||||||
boolean worked = this.inProgressGenTasksByLodPos.remove(taskPos, newTaskGroup);
|
boolean worked = this.inProgressGenTasksByLodPos.remove(taskPos, newTaskGroup);
|
||||||
LodUtil.assertTrue(worked);
|
LodUtil.assertTrue(worked, "Unable to find in progress generator task with position ["+DhSectionPos.toString(taskPos)+"]");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LOGGER.error("Unexpected error completing world gen task: "+taskPos, e);
|
LOGGER.error("Unexpected error completing world gen task at pos: ["+DhSectionPos.toString(taskPos)+"].", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -430,8 +457,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
case VANILLA_CHUNKS:
|
case VANILLA_CHUNKS:
|
||||||
{
|
{
|
||||||
return this.generator.generateChunks(
|
return this.generator.generateChunks(
|
||||||
chunkPosMin.x,
|
chunkPosMin.getX(),
|
||||||
chunkPosMin.z,
|
chunkPosMin.getZ(),
|
||||||
granularity,
|
granularity,
|
||||||
targetDataDetail,
|
targetDataDetail,
|
||||||
generatorMode,
|
generatorMode,
|
||||||
@@ -441,7 +468,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
IChunkWrapper chunk = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray);
|
IChunkWrapper chunk = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray);
|
||||||
FullDataSourceV2 dataSource = LodDataBuilder.createGeneratedDataSource(chunk);
|
FullDataSourceV2 dataSource = LodDataBuilder.createFromChunk(chunk);
|
||||||
LodUtil.assertTrue(dataSource != null);
|
LodUtil.assertTrue(dataSource != null);
|
||||||
chunkDataConsumer.accept(dataSource);
|
chunkDataConsumer.accept(dataSource);
|
||||||
}
|
}
|
||||||
@@ -456,8 +483,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
case API_CHUNKS:
|
case API_CHUNKS:
|
||||||
{
|
{
|
||||||
return this.generator.generateApiChunks(
|
return this.generator.generateApiChunks(
|
||||||
chunkPosMin.x,
|
chunkPosMin.getX(),
|
||||||
chunkPosMin.z,
|
chunkPosMin.getZ(),
|
||||||
granularity,
|
granularity,
|
||||||
targetDataDetail,
|
targetDataDetail,
|
||||||
generatorMode,
|
generatorMode,
|
||||||
@@ -466,10 +493,10 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints);
|
FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints, this.generator.runApiChunkValidation());
|
||||||
chunkDataConsumer.accept(dataSource);
|
chunkDataConsumer.accept(dataSource);
|
||||||
}
|
}
|
||||||
catch (DataCorruptedException e)
|
catch (DataCorruptedException | IllegalArgumentException e)
|
||||||
{
|
{
|
||||||
LOGGER.error("World generator returned a corrupt chunk. Error: [" + e.getMessage() + "]. World generator disabled.", e);
|
LOGGER.error("World generator returned a corrupt chunk. Error: [" + e.getMessage() + "]. World generator disabled.", e);
|
||||||
Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false);
|
Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false);
|
||||||
@@ -509,13 +536,15 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
@Override
|
@Override
|
||||||
public void setEstimatedTotalTaskCount(int newEstimate) { this.estimatedTotalTaskCount = newEstimate; }
|
public void setEstimatedTotalTaskCount(int newEstimate) { this.estimatedTotalTaskCount = newEstimate; }
|
||||||
|
|
||||||
|
public void addDebugMenuStringsToList(List<String> messageList) { }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==========//
|
//==========//
|
||||||
// shutdown //
|
// shutdown //
|
||||||
//==========//
|
//==========//
|
||||||
|
|
||||||
public CompletableFuture<Void> startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning)
|
public CompletableFuture<Void> startClosingAsync(boolean cancelCurrentGeneration, boolean alsoInterruptRunning)
|
||||||
{
|
{
|
||||||
LOGGER.info("Closing world gen queue");
|
LOGGER.info("Closing world gen queue");
|
||||||
this.queueingThread.shutdownNow();
|
this.queueingThread.shutdownNow();
|
||||||
@@ -566,7 +595,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
|
|
||||||
if (this.generatorClosingFuture == null)
|
if (this.generatorClosingFuture == null)
|
||||||
{
|
{
|
||||||
this.startClosing(true, true);
|
this.startClosingAsync(true, true);
|
||||||
}
|
}
|
||||||
LodUtil.assertTrue(this.generatorClosingFuture != null);
|
LodUtil.assertTrue(this.generatorClosingFuture != null);
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.generation.tasks;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,6 +33,7 @@ public interface IWorldGenTaskTracker
|
|||||||
/** Returns true if the task hasn't been garbage collected. */
|
/** Returns true if the task hasn't been garbage collected. */
|
||||||
boolean isMemoryAddressValid();
|
boolean isMemoryAddressValid();
|
||||||
|
|
||||||
Consumer<FullDataSourceV2> getChunkDataConsumer();
|
@Nullable
|
||||||
|
Consumer<FullDataSourceV2> getDataSourceConsumer();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||