Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b2f98bdf63 | |||
| fd0a4c55ac | |||
| b0aa211464 | |||
| 14e7918ea0 | |||
| 524ea77ce5 | |||
| 9f763bcd6a | |||
| 166875283c | |||
| abb264bdf0 | |||
| 70aa5724a9 | |||
| 520d37f410 | |||
| 29c2756e75 | |||
| 6c278ea3b1 | |||
| b18460b825 | |||
| d0b50d9633 | |||
| c051b3584f | |||
| d4cad8f718 |
@@ -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.
|
||||
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:
|
||||
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 |
@@ -33,9 +33,12 @@ import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
|
||||
import net.jpountz.lz4.LZ4FrameOutputStream;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.sqlite.SQLiteJDBCLoader;
|
||||
import org.sqlite.util.OSInfo;
|
||||
import org.tukaani.xz.XZOutputStream;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
|
||||
/** Handles first time Core setup. */
|
||||
public class Initializer
|
||||
@@ -57,12 +60,44 @@ public class Initializer
|
||||
Class<?> config = com.electronwill.nightconfig.core.Config.class;
|
||||
Class<?> oldFastUtil = it.unimi.dsi.fastutil.longs.LongArrayList.class; // available in 8.2.1
|
||||
//Class<?> newFastUtil = it.unimi.dsi.fastutil.ints.IntUnaryOperator.class; // available in 8.5.13
|
||||
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)
|
||||
{
|
||||
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
|
||||
throw e;
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// confirm the resource directory is present
|
||||
|
||||
@@ -149,37 +149,37 @@ public class ServerApi
|
||||
public void serverPlayerJoinEvent(IServerPlayerWrapper player)
|
||||
{
|
||||
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.info("Player [" + player.getName()+ "] joined.");
|
||||
((DhServerWorld) serverWorld).addPlayer(player);
|
||||
serverWorld.addPlayer(player);
|
||||
}
|
||||
}
|
||||
public void serverPlayerDisconnectEvent(IServerPlayerWrapper player)
|
||||
{
|
||||
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.info("Player [" + player.getName() + "] disconnected.");
|
||||
((DhServerWorld) serverWorld).removePlayer(player);
|
||||
serverWorld.removePlayer(player);
|
||||
}
|
||||
}
|
||||
public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
|
||||
{
|
||||
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
|
||||
if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well
|
||||
LOGGER.info("Player [${player.getName()}] changed level: [${originLevel.getKeyedLevelDimensionName()}] -> [${destinationLevel.getKeyedLevelDimensionName()}].");
|
||||
if (serverWorld != null)
|
||||
{
|
||||
LOGGER.info("Player [" + player.getName() + "] changed level: ["+originLevel.getKeyedLevelDimensionName()+"] -> ["+destinationLevel.getKeyedLevelDimensionName()+"].");
|
||||
((DhServerWorld) serverWorld).changePlayerLevel(player, originLevel, destinationLevel);
|
||||
serverWorld.changePlayerLevel(player, originLevel, destinationLevel);
|
||||
}
|
||||
}
|
||||
|
||||
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message)
|
||||
{
|
||||
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
|
||||
if (serverWorld instanceof DhServerWorld) // TODO add support for DhClientServerWorld's (lan worlds) as well
|
||||
if (serverWorld != null)
|
||||
{
|
||||
((DhServerWorld) serverWorld).remotePlayerConnectionHandler.handlePluginMessage(player, message);
|
||||
serverWorld.getServerPlayerStateManager().handlePluginMessage(player, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,11 +117,11 @@ public class SharedApi
|
||||
|
||||
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; }
|
||||
/** 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} */
|
||||
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} */
|
||||
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; }
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -574,6 +574,14 @@ public class Config
|
||||
+ "")
|
||||
.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
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.apache.logging.log4j.Logger;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Handles reading and writing config files.
|
||||
@@ -53,6 +54,9 @@ public class ConfigFileHandling
|
||||
/** This is the object for night-config */
|
||||
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();
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
@@ -72,40 +76,49 @@ public class ConfigFileHandling
|
||||
|
||||
|
||||
/** Saves the entire config to the file */
|
||||
public void saveToFile()
|
||||
{
|
||||
saveToFile(this.nightConfig);
|
||||
}
|
||||
public void saveToFile() { this.saveToFile(this.nightConfig); }
|
||||
/** Saves the entire config to the file */
|
||||
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
|
||||
{
|
||||
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
|
||||
SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).crashMinecraft("Failed to save config at [" + configPath.toString() + "]", e);
|
||||
this.readWriteLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,69 +129,77 @@ public class ConfigFileHandling
|
||||
*/
|
||||
public void loadFromFile()
|
||||
{
|
||||
int currentCfgVersion = this.configBase.configVersion;
|
||||
try
|
||||
{
|
||||
// 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 ignored) { }
|
||||
|
||||
if (currentCfgVersion == this.configBase.configVersion)
|
||||
{
|
||||
// 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");
|
||||
this.readWriteLock.lock();
|
||||
|
||||
int currentCfgVersion = this.configBase.configVersion;
|
||||
try
|
||||
{
|
||||
Files.delete(this.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)
|
||||
{
|
||||
this.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();
|
||||
}
|
||||
|
||||
this.loadFromFile(this.nightConfig);
|
||||
this.nightConfig.set("_version", this.configBase.configVersion);
|
||||
}
|
||||
/**
|
||||
* Loads the entire config from the file
|
||||
*
|
||||
* @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
|
||||
if (Files.exists(configPath))
|
||||
if (Files.exists(this.configPath))
|
||||
{
|
||||
loadNightConfig(nightConfig);
|
||||
this.loadNightConfig(nightConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
reCreateFile(configPath);
|
||||
reCreateFile(this.configPath);
|
||||
}
|
||||
|
||||
|
||||
// Load all the entries
|
||||
for (AbstractConfigType<?, ?> entry : this.configBase.entries)
|
||||
{
|
||||
if (
|
||||
ConfigEntry.class.isAssignableFrom(entry.getClass()) &&
|
||||
entry.getAppearance().showInFile
|
||||
)
|
||||
if (ConfigEntry.class.isAssignableFrom(entry.getClass())
|
||||
&& entry.getAppearance().showInFile)
|
||||
{
|
||||
createComment((ConfigEntry<?>) entry, nightConfig);
|
||||
loadEntry((ConfigEntry<?>) entry, nightConfig);
|
||||
this.createComment((ConfigEntry<?>) entry, nightConfig);
|
||||
this.loadEntry((ConfigEntry<?>) entry, nightConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +211,7 @@ public class ConfigFileHandling
|
||||
catch (Exception e)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -923,6 +923,7 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
|
||||
@Override
|
||||
public String toString() { return DhSectionPos.toString(this.pos); }
|
||||
|
||||
/** Only includes the base data in this object, not the mapping */
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO;
|
||||
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
|
||||
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV1Repo;
|
||||
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
@@ -64,7 +65,7 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
||||
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
||||
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
||||
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
|
||||
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
|
||||
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
|
||||
@@ -47,14 +48,11 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Handles reading/writing {@link FullDataSourceV2}
|
||||
@@ -160,7 +158,7 @@ public class FullDataSourceProviderV2
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -28,8 +28,10 @@ import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
||||
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.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.IDebugRenderable;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
@@ -418,9 +420,13 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
|
||||
|
||||
/** used by external event listeners */
|
||||
@FunctionalInterface
|
||||
public interface IOnWorldGenCompleteListener
|
||||
{
|
||||
boolean shouldDoWorldGen();
|
||||
|
||||
@Nullable
|
||||
DhBlockPos2D getTargetPosForGeneration();
|
||||
|
||||
/** Fired whenever a section has completed generating */
|
||||
void onWorldGenTaskComplete(long pos);
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.seibel.distanthorizons.core.render.renderer.generic.CloudRenderHandle
|
||||
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
|
||||
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
||||
import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO;
|
||||
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
|
||||
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
|
||||
import com.seibel.distanthorizons.core.sql.repo.ChunkHashRepo;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
@@ -85,7 +86,7 @@ public abstract class AbstractDhLevel implements IDhLevel
|
||||
ChunkHashRepo newChunkHashRepo = null;
|
||||
try
|
||||
{
|
||||
newChunkHashRepo = new ChunkHashRepo("jdbc:sqlite", databaseFile);
|
||||
newChunkHashRepo = new ChunkHashRepo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, databaseFile);
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
@@ -98,7 +99,7 @@ public abstract class AbstractDhLevel implements IDhLevel
|
||||
BeaconBeamRepo newBeaconBeamRepo = null;
|
||||
try
|
||||
{
|
||||
newBeaconBeamRepo = new BeaconBeamRepo("jdbc:sqlite", databaseFile);
|
||||
newBeaconBeamRepo = new BeaconBeamRepo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, databaseFile);
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,509 @@
|
||||
package com.seibel.distanthorizons.core.level;
|
||||
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
|
||||
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
|
||||
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
|
||||
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
|
||||
import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException;
|
||||
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
|
||||
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
||||
import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage;
|
||||
import com.seibel.distanthorizons.core.network.messages.ILevelRelatedMessage;
|
||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
|
||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPayload;
|
||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
|
||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage;
|
||||
import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.math.Vec3d;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public abstract class AbstractDhServerLevel extends AbstractDhLevel implements IDhServerLevel
|
||||
{
|
||||
protected static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
private static final ConfigBasedLogger NETWORK_LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
|
||||
() -> Config.Client.Advanced.Logging.logNetworkEvent.get());
|
||||
|
||||
/** 1 Mebibyte minus 576 bytes for other info */
|
||||
public static final int FULL_DATA_SPLIT_SIZE_IN_BYTES = 1_048_000;
|
||||
|
||||
public final ServerLevelModule serverside;
|
||||
protected final IServerLevelWrapper serverLevelWrapper;
|
||||
|
||||
protected final ServerPlayerStateManager serverPlayerStateManager;
|
||||
|
||||
/**
|
||||
* This queue is used for ensuring fair generation speed for each player. <br>
|
||||
* Every tick the first player gets used for centering generation, and then is immediately moved into the back of the queue. <br>
|
||||
* TODO only add players that actually have something to generate
|
||||
*/
|
||||
protected final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenPlayerCenteringQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private final ConcurrentMap<Long, DataSourceRequestGroup> requestGroupByPos = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Long, DataSourceRequestGroup> requestGroupByFutureId = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public AbstractDhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, ServerPlayerStateManager serverPlayerStateManager)
|
||||
{
|
||||
this(saveStructure, serverLevelWrapper, serverPlayerStateManager, true);
|
||||
}
|
||||
public AbstractDhServerLevel(
|
||||
AbstractSaveStructure saveStructure,
|
||||
IServerLevelWrapper serverLevelWrapper,
|
||||
ServerPlayerStateManager serverPlayerStateManager,
|
||||
boolean runRepoReliantSetup
|
||||
)
|
||||
{
|
||||
if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs())
|
||||
{
|
||||
LOGGER.warn("unable to create data folder.");
|
||||
}
|
||||
this.serverLevelWrapper = serverLevelWrapper;
|
||||
this.serverside = new ServerLevelModule(this, saveStructure);
|
||||
this.createAndSetSupportingRepos(this.serverside.fullDataFileHandler.repo.databaseFile);
|
||||
if (runRepoReliantSetup)
|
||||
{
|
||||
this.runRepoReliantSetup();
|
||||
}
|
||||
|
||||
LOGGER.info("Started ${this.getClass().getSimpleName()} for $serverLevelWrapper at $saveStructure.");
|
||||
|
||||
this.serverPlayerStateManager = serverPlayerStateManager;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=======//
|
||||
// ticks //
|
||||
//=======//
|
||||
|
||||
@Override
|
||||
public void serverTick()
|
||||
{
|
||||
// Send finished data source requests
|
||||
for (Map.Entry<Long, DataSourceRequestGroup> entry : this.requestGroupByPos.entrySet())
|
||||
{
|
||||
DataSourceRequestGroup requestGroup = entry.getValue();
|
||||
|
||||
if (requestGroup.fullDataSource == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NETWORK_LOGGER.debug("[${this.serverLevelWrapper.getDimensionName()}] Fulfilled request group [${entry.getKey()}]");
|
||||
|
||||
// Make this group unavailable for adding into
|
||||
this.requestGroupByPos.remove(entry.getKey());
|
||||
requestGroup.requestRemoveSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
|
||||
requestGroup.requestAddSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
|
||||
|
||||
ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
|
||||
if (executor == null)
|
||||
{
|
||||
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
|
||||
continue;
|
||||
}
|
||||
CompletableFuture.runAsync(() ->
|
||||
{
|
||||
FullDataPayload payload = new FullDataPayload(requestGroup.fullDataSource);
|
||||
for (FullDataSourceRequestMessage msg : requestGroup.requestMessages.values())
|
||||
{
|
||||
this.requestGroupByFutureId.remove(msg.futureId);
|
||||
|
||||
ServerPlayerState serverPlayerState = this.serverPlayerStateManager.getConnectedPlayer(msg.serverPlayer());
|
||||
if (serverPlayerState == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
serverPlayerState.getRateLimiterSet(this).generationRequestRateLimiter.release();
|
||||
payload.splitAndSend(FULL_DATA_SPLIT_SIZE_IN_BYTES, msg.getSession()::sendMessage);
|
||||
msg.sendResponse(new FullDataSourceResponseMessage(payload));
|
||||
}
|
||||
}, executor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldDoWorldGen()
|
||||
{
|
||||
return Config.Client.Advanced.WorldGenerator.enableDistantGeneration.get() && !this.worldGenPlayerCenteringQueue.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public DhBlockPos2D getTargetPosForGeneration()
|
||||
{
|
||||
IServerPlayerWrapper firstPlayer = this.worldGenPlayerCenteringQueue.peek();
|
||||
if (firstPlayer == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Put first player in back before removing from front, so it can be removed by other thread without blocking
|
||||
// - if it gets removed, remove() below will remove the item we just put instead
|
||||
this.worldGenPlayerCenteringQueue.add(firstPlayer);
|
||||
this.worldGenPlayerCenteringQueue.remove(firstPlayer);
|
||||
|
||||
Vec3d position = firstPlayer.getPosition();
|
||||
return new DhBlockPos2D((int) position.x, (int) position.z);
|
||||
}
|
||||
|
||||
@Override public void worldGenTick()
|
||||
{
|
||||
this.serverside.worldGenModule.worldGenTick();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==================//
|
||||
// network handling //
|
||||
//==================//
|
||||
|
||||
public void registerNetworkHandlers(ServerPlayerState serverPlayerState)
|
||||
{
|
||||
serverPlayerState.networkSession.registerHandler(FullDataSourceRequestMessage.class, (message) ->
|
||||
{
|
||||
if (!this.messagePlayerInThisLevel(message))
|
||||
{
|
||||
// we can't handle players in other levels, don't continue
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ServerPlayerState.RateLimiterSet rateLimiterSet = serverPlayerState.getRateLimiterSet(this);
|
||||
|
||||
if (message.clientTimestamp == null)
|
||||
{
|
||||
this.queueWorldGenForRequestMessage(serverPlayerState, message, rateLimiterSet);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.queueLodSyncForRequestMessage(serverPlayerState, message, rateLimiterSet);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
serverPlayerState.networkSession.registerHandler(CancelMessage.class, msg ->
|
||||
{
|
||||
DataSourceRequestGroup requestGroup = this.requestGroupByFutureId.remove(msg.futureId);
|
||||
if (requestGroup == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If this fails, the group is being removed and completing cancellation is not necessary
|
||||
if (requestGroup.requestRemoveSemaphore.tryAcquire())
|
||||
{
|
||||
// Prevent adding requests in case the group will be removed by this cancellation
|
||||
requestGroup.requestAddSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
|
||||
requestGroup.requestRemoveSemaphore.release();
|
||||
|
||||
serverPlayerState.getRateLimiterSet(this).generationRequestRateLimiter.release();
|
||||
|
||||
FullDataSourceRequestMessage requestMessage = requestGroup.requestMessages.remove(msg.futureId);
|
||||
if (requestGroup.requestMessages.isEmpty())
|
||||
{
|
||||
NETWORK_LOGGER.debug("[${this.serverLevelWrapper.getDimensionName()}] Cancelled request group [${DhSectionPos.toString(requestMessage.sectionPos)}].");
|
||||
this.requestGroupByPos.remove(requestMessage.sectionPos);
|
||||
this.serverside.fullDataFileHandler.removeRetrievalRequestIf(pos -> pos == requestMessage.sectionPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
requestGroup.requestAddSemaphore.release(Short.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
private void queueLodSyncForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
|
||||
{
|
||||
if (!serverPlayerState.sessionConfig.getSynchronizeOnLogin())
|
||||
{
|
||||
message.sendResponse(new RequestRejectedException("Operation is disabled in config."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rateLimiterSet.syncOnLoginRateLimiter.tryAcquire(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// the client timestamp will be null if we want to retrieve the LOD regardless of when it was last updated
|
||||
long clientTimestamp = (message.clientTimestamp != null) ? message.clientTimestamp : -1;
|
||||
// the server timestamp will be null if no LOD data exists for this position
|
||||
Long serverTimestamp = this.serverside.fullDataFileHandler.getTimestampForPos(message.sectionPos);
|
||||
if (serverTimestamp == null
|
||||
|| serverTimestamp <= clientTimestamp)
|
||||
{
|
||||
// either no data exists to sync, or the client is already up to date
|
||||
rateLimiterSet.syncOnLoginRateLimiter.release();
|
||||
message.sendResponse(new FullDataSourceResponseMessage(null));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
|
||||
if (executor == null)
|
||||
{
|
||||
// shouldn't normally happen, but just in case
|
||||
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
|
||||
return;
|
||||
}
|
||||
|
||||
this.serverside.fullDataFileHandler.getAsync(message.sectionPos).thenAcceptAsync(fullDataSource ->
|
||||
{
|
||||
rateLimiterSet.syncOnLoginRateLimiter.release();
|
||||
|
||||
FullDataPayload payload = new FullDataPayload(fullDataSource);
|
||||
payload.splitAndSend(FULL_DATA_SPLIT_SIZE_IN_BYTES, message.getSession()::sendMessage);
|
||||
message.sendResponse(new FullDataSourceResponseMessage(payload));
|
||||
}, executor);
|
||||
}
|
||||
private void queueWorldGenForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
|
||||
{
|
||||
if (!serverPlayerState.sessionConfig.isDistantGenerationEnabled())
|
||||
{
|
||||
message.sendResponse(new RequestRejectedException("Operation is disabled in config."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rateLimiterSet.generationRequestRateLimiter.tryAcquire(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
DataSourceRequestGroup requestGroup = this.requestGroupByPos.computeIfAbsent(message.sectionPos, pos ->
|
||||
{
|
||||
DataSourceRequestGroup newGroup = new DataSourceRequestGroup();
|
||||
this.tryFulfillDataSourceRequestGroup(newGroup, pos);
|
||||
NETWORK_LOGGER.debug("[${serverLevelWrapper.getDimensionName()}] Created request group for pos [${DhSectionPos.toString(pos)}].");
|
||||
return newGroup;
|
||||
});
|
||||
|
||||
// If this fails, loop until either a permit is acquired or the group is removed to create another one
|
||||
if (!requestGroup.requestAddSemaphore.tryAcquire())
|
||||
{
|
||||
Thread.yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
this.requestGroupByFutureId.put(message.futureId, requestGroup);
|
||||
requestGroup.requestMessages.put(message.futureId, message);
|
||||
requestGroup.requestAddSemaphore.release();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** May send an error message in response if the message is a {@link AbstractTrackableMessage} */
|
||||
private <T extends AbstractNetworkMessage> boolean messagePlayerInThisLevel(T message)
|
||||
{
|
||||
if (!(message instanceof ILevelRelatedMessage))
|
||||
{
|
||||
LodUtil.assertNotReach("Received message [$message] does not implement [${ILevelRelatedMessage.class.getSimpleName()}]");
|
||||
}
|
||||
|
||||
// Only handle requests for this level
|
||||
if (!((ILevelRelatedMessage) message).isSameLevelAs(this.getServerLevelWrapper()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LodUtil.assertTrue(message.getSession().serverPlayer != null);
|
||||
|
||||
// Check if the player is in this dimension,
|
||||
// since handling multiple dimensions isn't allowed
|
||||
if (message.getSession().serverPlayer.getLevel() != this.getLevelWrapper())
|
||||
{
|
||||
// If the message can be replied to - reply with an error, otherwise just ignore
|
||||
if (message instanceof AbstractTrackableMessage)
|
||||
{
|
||||
((AbstractTrackableMessage) message).sendResponse(
|
||||
new InvalidLevelException(
|
||||
"Generation not allowed. " +
|
||||
"Requested dimension: [${((ILevelRelatedMessage) message).getLevelName()}], " +
|
||||
"player dimension: [${message.getSession().serverPlayer.getLevel().getDimensionName()}], " +
|
||||
"handler dimension: [${this.getLevelWrapper().getDimensionName()}]"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// world gen //
|
||||
//===========//
|
||||
|
||||
@Override
|
||||
public void onWorldGenTaskComplete(long pos)
|
||||
{
|
||||
DataSourceRequestGroup requestGroup = this.requestGroupByPos.get(pos);
|
||||
if (requestGroup != null)
|
||||
{
|
||||
this.tryFulfillDataSourceRequestGroup(requestGroup, pos);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryFulfillDataSourceRequestGroup(DataSourceRequestGroup requestGroup, long pos)
|
||||
{
|
||||
this.serverside.fullDataFileHandler.getAsync(pos).thenAccept(fullDataSource ->
|
||||
{
|
||||
if (this.serverside.fullDataFileHandler.isFullyGenerated(fullDataSource.columnGenerationSteps))
|
||||
{
|
||||
requestGroup.fullDataSource = fullDataSource;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.serverside.fullDataFileHandler.queuePositionForRetrieval(pos);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// player handling //
|
||||
//=================//
|
||||
|
||||
public void addPlayer(IServerPlayerWrapper serverPlayer) { this.worldGenPlayerCenteringQueue.add(serverPlayer); }
|
||||
public void removePlayer(IServerPlayerWrapper serverPlayer) { this.worldGenPlayerCenteringQueue.remove(serverPlayer); }
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data)
|
||||
{
|
||||
if (!Config.Client.Advanced.Multiplayer.ServerNetworking.enableRealTimeUpdates.get())
|
||||
{
|
||||
return this.getFullDataProvider().updateDataSourceAsync(data);
|
||||
}
|
||||
|
||||
ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
|
||||
if (executor == null)
|
||||
{
|
||||
LOGGER.warn("Unable to send FullDataPartialUpdateMessage - getNetworkCompressionExecutor() is null");
|
||||
return this.getFullDataProvider().updateDataSourceAsync(data);
|
||||
}
|
||||
CompletableFuture.runAsync(() ->
|
||||
{
|
||||
FullDataPayload payload = new FullDataPayload(data);
|
||||
for (ServerPlayerState serverPlayerState : this.serverPlayerStateManager.getConnectedPlayers())
|
||||
{
|
||||
if (serverPlayerState.getServerPlayer().getLevel() != this.serverLevelWrapper)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!serverPlayerState.sessionConfig.isRealTimeUpdatesEnabled())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
|
||||
int distanceFromPlayer = DhSectionPos.getManhattanBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
|
||||
if (distanceFromPlayer >= serverPlayerState.getServerPlayer().getViewDistance()
|
||||
&& distanceFromPlayer <= serverPlayerState.sessionConfig.getRenderDistanceRadius())
|
||||
{
|
||||
payload.splitAndSend(FULL_DATA_SPLIT_SIZE_IN_BYTES, serverPlayerState.networkSession::sendMessage);
|
||||
serverPlayerState.networkSession.sendMessage(new FullDataPartialUpdateMessage(this.serverLevelWrapper, payload));
|
||||
}
|
||||
}
|
||||
}, executor);
|
||||
|
||||
|
||||
return this.getFullDataProvider().updateDataSourceAsync(data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// getters //
|
||||
//=========//
|
||||
|
||||
@Override
|
||||
public int getMinY() { return this.getLevelWrapper().getMinHeight(); }
|
||||
|
||||
@Override
|
||||
public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; }
|
||||
|
||||
@Override
|
||||
public ILevelWrapper getLevelWrapper() { return this.getServerLevelWrapper(); }
|
||||
|
||||
@Override
|
||||
public FullDataSourceProviderV2 getFullDataProvider() { return this.serverside.fullDataFileHandler; }
|
||||
|
||||
@Override
|
||||
public AbstractSaveStructure getSaveStructure() { return this.serverside.saveStructure; }
|
||||
|
||||
@Override
|
||||
public boolean hasSkyLight() { return this.serverLevelWrapper.hasSkyLight(); }
|
||||
|
||||
|
||||
|
||||
//==========//
|
||||
// shutdown //
|
||||
//==========//
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
super.close();
|
||||
this.serverside.close();
|
||||
LOGGER.info("Closed DHLevel for [" + this.getLevelWrapper() + "].");
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
//================//
|
||||
|
||||
private static class DataSourceRequestGroup
|
||||
{
|
||||
public final ConcurrentMap<Long, FullDataSourceRequestMessage> requestMessages = new ConcurrentHashMap<>();
|
||||
|
||||
@CheckForNull
|
||||
public FullDataSourceV2 fullDataSource;
|
||||
|
||||
/**
|
||||
* These two Semaphores are used to prevent all threads from locking on the group after it being fulfilled,
|
||||
* as opposed to ReentrantReadWriteLocks which would allow the locking thread continue using it anyway. <br>
|
||||
* Short.MAX_VALUE is chosen as a large enough number so non-exclusive accesses never block each other.
|
||||
*/
|
||||
public final Semaphore requestAddSemaphore = new Semaphore(Short.MAX_VALUE, true);
|
||||
/** @see DataSourceRequestGroup#requestAddSemaphore */
|
||||
public final Semaphore requestRemoveSemaphore = new Semaphore(Short.MAX_VALUE, true);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -52,6 +52,7 @@ import javax.annotation.CheckForNull;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/** The level used when connected to a server */
|
||||
@@ -108,7 +109,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
||||
|
||||
this.dataFileHandler = new RemoteFullDataSourceProvider(this, saveStructure, fullDataSaveDirOverride, this.syncOnLoginRequestQueue);
|
||||
this.worldGeneratorEnabledConfig = new AppliedConfigState<>(Config.Client.Advanced.WorldGenerator.enableDistantGeneration);
|
||||
this.worldGenModule = new WorldGenModule(this);
|
||||
this.worldGenModule = new WorldGenModule(this, this.dataFileHandler, () -> new WorldGenState(this, networkState));
|
||||
|
||||
this.clientside = new ClientLevelModule(this);
|
||||
|
||||
@@ -171,7 +172,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
||||
}
|
||||
|
||||
@Override
|
||||
public void worldGenTick()
|
||||
public boolean shouldDoWorldGen()
|
||||
{
|
||||
ClientNetworkState networkState = this.networkState;
|
||||
|
||||
@@ -182,33 +183,23 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
||||
isAllowedDimension = MC_CLIENT.getWrappedClientLevel() == this.levelWrapper;
|
||||
}
|
||||
|
||||
boolean shouldDoWorldGen = isClientUsable
|
||||
return isClientUsable
|
||||
&& networkState.sessionConfig.isDistantGenerationEnabled()
|
||||
&& isAllowedDimension
|
||||
&& this.clientside.isRendering();
|
||||
|
||||
boolean isWorldGenRunning = this.worldGenModule.isWorldGenRunning();
|
||||
if (shouldDoWorldGen && !isWorldGenRunning)
|
||||
{
|
||||
// start world gen
|
||||
this.worldGenModule.startWorldGen(this.dataFileHandler, new WorldGenState(this, networkState));
|
||||
|
||||
// populate the queue based on the current rendering tree
|
||||
ClientLevelModule.ClientRenderState renderState = this.clientside.ClientRenderStateRef.get();
|
||||
renderState.quadtree.leafNodeIterator().forEachRemaining(node -> {
|
||||
this.dataFileHandler.getAsync(node.sectionPos);
|
||||
});
|
||||
}
|
||||
else if (!shouldDoWorldGen && isWorldGenRunning)
|
||||
{
|
||||
// stop world gen
|
||||
this.worldGenModule.stopWorldGen(this.dataFileHandler);
|
||||
}
|
||||
|
||||
if (this.worldGenModule.isWorldGenRunning())
|
||||
{
|
||||
this.worldGenModule.worldGenTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public DhBlockPos2D getTargetPosForGeneration()
|
||||
{
|
||||
return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void worldGenTick()
|
||||
{
|
||||
this.worldGenModule.worldGenTick();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -22,59 +22,44 @@ package com.seibel.distanthorizons.core.level;
|
||||
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
|
||||
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
|
||||
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
||||
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
||||
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/** The level used on a singleplayer world */
|
||||
public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLevel, IDhServerLevel
|
||||
public class DhClientServerLevel extends AbstractDhServerLevel implements IDhClientLevel
|
||||
{
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||
|
||||
public final ServerLevelModule serverside;
|
||||
public final ClientLevelModule clientside;
|
||||
|
||||
private final IServerLevelWrapper serverLevelWrapper;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public DhClientServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper)
|
||||
public DhClientServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, ServerPlayerStateManager serverPlayerStateManager)
|
||||
{
|
||||
if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs())
|
||||
{
|
||||
LOGGER.warn("unable to create data folder.");
|
||||
}
|
||||
this.serverLevelWrapper = serverLevelWrapper;
|
||||
this.serverLevelWrapper.setParentLevel(this);
|
||||
this.serverside = new ServerLevelModule(this, saveStructure);
|
||||
this.clientside = new ClientLevelModule(this);
|
||||
this.createAndSetSupportingRepos(this.serverside.fullDataFileHandler.repo.databaseFile);
|
||||
this.runRepoReliantSetup();
|
||||
super(saveStructure, serverLevelWrapper, serverPlayerStateManager, false);
|
||||
|
||||
LOGGER.info("Started " + DhClientServerLevel.class.getSimpleName() + " for " + serverLevelWrapper + " with saves at " + saveStructure);
|
||||
this.serverLevelWrapper.setParentLevel(this);
|
||||
this.clientside = new ClientLevelModule(this);
|
||||
this.runRepoReliantSetup();
|
||||
}
|
||||
|
||||
|
||||
@@ -94,34 +79,6 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
|
||||
public void renderDeferred(DhApiRenderParam renderEventParam, IProfilerWrapper profiler)
|
||||
{ this.clientside.renderDeferred(renderEventParam, profiler); }
|
||||
|
||||
@Override
|
||||
public void serverTick() { }
|
||||
|
||||
@Override
|
||||
public void worldGenTick()
|
||||
{
|
||||
this.serverside.worldGeneratorEnabledConfig.pollNewValue(); // if not called the get() line below may not
|
||||
boolean shouldDoWorldGen = this.serverside.worldGeneratorEnabledConfig.get() && this.clientside.isRendering();
|
||||
boolean isWorldGenRunning = this.serverside.worldGenModule.isWorldGenRunning();
|
||||
if (shouldDoWorldGen && !isWorldGenRunning)
|
||||
{
|
||||
// start world gen
|
||||
this.serverside.worldGenModule.startWorldGen(this.serverside.fullDataFileHandler, new ServerLevelModule.WorldGenState(this));
|
||||
}
|
||||
else if (!shouldDoWorldGen && isWorldGenRunning)
|
||||
{
|
||||
// stop world gen
|
||||
this.serverside.worldGenModule.stopWorldGen(this.serverside.fullDataFileHandler);
|
||||
}
|
||||
|
||||
if (isWorldGenRunning)
|
||||
{
|
||||
this.serverside.worldGenModule.worldGenTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//========//
|
||||
// render //
|
||||
//========//
|
||||
@@ -157,28 +114,14 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
|
||||
public void clearRenderCache() { this.clientside.clearRenderCache(); }
|
||||
|
||||
@Override
|
||||
public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; }
|
||||
@Override
|
||||
public ILevelWrapper getLevelWrapper() { return this.getServerLevelWrapper(); }
|
||||
|
||||
@Override
|
||||
public FullDataSourceProviderV2 getFullDataProvider() { return this.serverside.fullDataFileHandler; }
|
||||
|
||||
@Override
|
||||
public AbstractSaveStructure getSaveStructure()
|
||||
public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data)
|
||||
{
|
||||
return this.serverside.saveStructure;
|
||||
return CompletableFuture.allOf(
|
||||
super.updateDataSourcesAsync(data),
|
||||
this.clientside.updateDataSourcesAsync(data)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSkyLight() { return this.serverLevelWrapper.hasSkyLight(); }
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data) { return this.clientside.updateDataSourcesAsync(data); }
|
||||
|
||||
@Override
|
||||
public int getMinY() { return this.getLevelWrapper().getMinHeight(); }
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
@@ -247,6 +190,8 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
|
||||
@Override
|
||||
public void onWorldGenTaskComplete(long pos)
|
||||
{
|
||||
super.onWorldGenTaskComplete(pos);
|
||||
|
||||
DebugRenderer.makeParticle(
|
||||
new DebugRenderer.BoxParticle(
|
||||
new DebugRenderer.Box(pos, 128f, 156f, 0.09f, Color.red.darker()),
|
||||
|
||||
@@ -19,86 +19,23 @@
|
||||
|
||||
package com.seibel.distanthorizons.core.level;
|
||||
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
|
||||
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.multiplayer.server.RemotePlayerConnectionHandler;
|
||||
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
|
||||
import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException;
|
||||
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
|
||||
import com.seibel.distanthorizons.core.network.messages.ILevelRelatedMessage;
|
||||
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
||||
import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage;
|
||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
|
||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPayload;
|
||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
|
||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage;
|
||||
import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
|
||||
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
|
||||
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.math.Vec3d;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
|
||||
public class DhServerLevel extends AbstractDhServerLevel
|
||||
{
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
private static final ConfigBasedLogger NETWORK_LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
|
||||
() -> Config.Client.Advanced.Logging.logNetworkEvent.get());
|
||||
|
||||
/** 1 Mebibyte minus 576 bytes for other info */
|
||||
public static final int FULL_DATA_SPLIT_SIZE_IN_BYTES = 1_048_000;
|
||||
|
||||
public final ServerLevelModule serverside;
|
||||
private final IServerLevelWrapper serverLevelWrapper;
|
||||
|
||||
private final RemotePlayerConnectionHandler remotePlayerConnectionHandler;
|
||||
|
||||
/**
|
||||
* This queue is used for ensuring fair generation speed for each player. <br>
|
||||
* Every tick the first player gets used for centering generation, and then is immediately moved into the back of the queue. <br>
|
||||
* TODO only add players that actually have something to generate
|
||||
*/
|
||||
private final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenPlayerCenteringQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private final ConcurrentMap<Long, DataSourceRequestGroup> requestGroupByPos = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Long, DataSourceRequestGroup> requestGroupByFutureId = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public DhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, RemotePlayerConnectionHandler remotePlayerConnectionHandler)
|
||||
public DhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, ServerPlayerStateManager serverPlayerStateManager)
|
||||
{
|
||||
if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs())
|
||||
{
|
||||
LOGGER.warn("unable to create data folder.");
|
||||
}
|
||||
this.serverLevelWrapper = serverLevelWrapper;
|
||||
this.serverside = new ServerLevelModule(this, saveStructure);
|
||||
this.createAndSetSupportingRepos(this.serverside.fullDataFileHandler.repo.databaseFile);
|
||||
this.runRepoReliantSetup();
|
||||
|
||||
LOGGER.info("Started DHLevel for ["+serverLevelWrapper+"] at ["+saveStructure+"].");
|
||||
|
||||
this.remotePlayerConnectionHandler = remotePlayerConnectionHandler;
|
||||
super(saveStructure, serverLevelWrapper, serverPlayerStateManager);
|
||||
}
|
||||
|
||||
|
||||
@@ -108,355 +45,9 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
|
||||
//=======//
|
||||
|
||||
@Override
|
||||
public void serverTick()
|
||||
public boolean shouldDoWorldGen()
|
||||
{
|
||||
// Send finished data source requests
|
||||
for (Map.Entry<Long, DataSourceRequestGroup> entry : this.requestGroupByPos.entrySet())
|
||||
{
|
||||
DataSourceRequestGroup requestGroup = entry.getValue();
|
||||
|
||||
if (requestGroup.fullDataSource == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NETWORK_LOGGER.debug("["+this.serverLevelWrapper.getDimensionName()+"] Fulfilled request group ["+entry.getKey()+"]");
|
||||
|
||||
// Make this group unavailable for adding into
|
||||
this.requestGroupByPos.remove(entry.getKey());
|
||||
requestGroup.requestRemoveSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
|
||||
requestGroup.requestAddSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
|
||||
|
||||
ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
|
||||
if (executor == null)
|
||||
{
|
||||
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
|
||||
continue;
|
||||
}
|
||||
CompletableFuture.runAsync(() ->
|
||||
{
|
||||
FullDataPayload payload = new FullDataPayload(requestGroup.fullDataSource);
|
||||
for (FullDataSourceRequestMessage msg : requestGroup.requestMessages.values())
|
||||
{
|
||||
this.requestGroupByFutureId.remove(msg.futureId);
|
||||
|
||||
ServerPlayerState serverPlayerState = this.remotePlayerConnectionHandler.getConnectedPlayer(msg.serverPlayer());
|
||||
if (serverPlayerState == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
serverPlayerState.getRateLimiterSet(this).generationRequestRateLimiter.release();
|
||||
payload.splitAndSend(FULL_DATA_SPLIT_SIZE_IN_BYTES, msg.getSession()::sendMessage);
|
||||
msg.sendResponse(new FullDataSourceResponseMessage(payload));
|
||||
}
|
||||
}, executor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void worldGenTick()
|
||||
{
|
||||
boolean shouldDoWorldGen = true; //todo;
|
||||
boolean isWorldGenRunning = this.serverside.worldGenModule.isWorldGenRunning();
|
||||
if (shouldDoWorldGen && !isWorldGenRunning)
|
||||
{
|
||||
// start world gen
|
||||
this.serverside.worldGenModule.startWorldGen(this.serverside.fullDataFileHandler, new ServerLevelModule.WorldGenState(this));
|
||||
}
|
||||
else if (!shouldDoWorldGen && isWorldGenRunning)
|
||||
{
|
||||
// stop world gen
|
||||
this.serverside.worldGenModule.stopWorldGen(this.serverside.fullDataFileHandler);
|
||||
}
|
||||
|
||||
if (this.serverside.worldGenModule.isWorldGenRunning())
|
||||
{
|
||||
IServerPlayerWrapper firstPlayer = this.worldGenPlayerCenteringQueue.peek();
|
||||
if (firstPlayer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Put first player in back before removing from front, so it can be removed by other thread without blocking
|
||||
// - if it gets removed, remove() below will remove the item we just put instead
|
||||
this.worldGenPlayerCenteringQueue.add(firstPlayer);
|
||||
this.worldGenPlayerCenteringQueue.remove(firstPlayer);
|
||||
|
||||
Vec3d position = firstPlayer.getPosition();
|
||||
this.serverside.worldGenModule.worldGenTick(new DhBlockPos2D((int) position.x, (int) position.z));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==================//
|
||||
// network handling //
|
||||
//==================//
|
||||
|
||||
public void registerNetworkHandlers(ServerPlayerState serverPlayerState)
|
||||
{
|
||||
serverPlayerState.networkSession.registerHandler(FullDataSourceRequestMessage.class, (message) ->
|
||||
{
|
||||
if (!this.messagePlayerInThisLevel(message))
|
||||
{
|
||||
// we can't handle players in other levels, don't continue
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ServerPlayerState.RateLimiterSet rateLimiterSet = serverPlayerState.getRateLimiterSet(this);
|
||||
|
||||
if (message.clientTimestamp == null)
|
||||
{
|
||||
this.queueWorldGenForRequestMessage(serverPlayerState, message, rateLimiterSet);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.queueLodSyncForRequestMessage(serverPlayerState, message, rateLimiterSet);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
serverPlayerState.networkSession.registerHandler(CancelMessage.class, msg ->
|
||||
{
|
||||
DataSourceRequestGroup requestGroup = this.requestGroupByFutureId.remove(msg.futureId);
|
||||
if (requestGroup == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If this fails, the group is being removed and completing cancellation is not necessary
|
||||
if (requestGroup.requestRemoveSemaphore.tryAcquire())
|
||||
{
|
||||
// Prevent adding requests in case the group will be removed by this cancellation
|
||||
requestGroup.requestAddSemaphore.acquireUninterruptibly(Short.MAX_VALUE);
|
||||
requestGroup.requestRemoveSemaphore.release();
|
||||
|
||||
serverPlayerState.getRateLimiterSet(this).generationRequestRateLimiter.release();
|
||||
|
||||
FullDataSourceRequestMessage requestMessage = requestGroup.requestMessages.remove(msg.futureId);
|
||||
if (requestGroup.requestMessages.isEmpty())
|
||||
{
|
||||
NETWORK_LOGGER.debug("["+this.serverLevelWrapper.getDimensionName()+"] Cancelled request group ["+DhSectionPos.toString(requestMessage.sectionPos)+"].");
|
||||
this.requestGroupByPos.remove(requestMessage.sectionPos);
|
||||
this.serverside.fullDataFileHandler.removeRetrievalRequestIf(pos -> pos == requestMessage.sectionPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
requestGroup.requestAddSemaphore.release(Short.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
private void queueLodSyncForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
|
||||
{
|
||||
if (!serverPlayerState.sessionConfig.getSynchronizeOnLogin())
|
||||
{
|
||||
message.sendResponse(new RequestRejectedException("Operation is disabled in config."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rateLimiterSet.syncOnLoginRateLimiter.tryAcquire(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// the client timestamp will be null if we want to retrieve the LOD regardless of when it was last updated
|
||||
long clientTimestamp = (message.clientTimestamp != null) ? message.clientTimestamp : -1;
|
||||
// the server timestamp will be null if no LOD data exists for this position
|
||||
Long serverTimestamp = this.serverside.fullDataFileHandler.getTimestampForPos(message.sectionPos);
|
||||
if (serverTimestamp == null
|
||||
|| serverTimestamp <= clientTimestamp)
|
||||
{
|
||||
// either no data exists to sync, or the client is already up to date
|
||||
rateLimiterSet.syncOnLoginRateLimiter.release();
|
||||
message.sendResponse(new FullDataSourceResponseMessage(null));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
|
||||
if (executor == null)
|
||||
{
|
||||
// shouldn't normally happen, but just in case
|
||||
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
|
||||
return;
|
||||
}
|
||||
|
||||
this.serverside.fullDataFileHandler.getAsync(message.sectionPos).thenAcceptAsync(fullDataSource ->
|
||||
{
|
||||
rateLimiterSet.syncOnLoginRateLimiter.release();
|
||||
|
||||
FullDataPayload payload = new FullDataPayload(fullDataSource);
|
||||
payload.splitAndSend(FULL_DATA_SPLIT_SIZE_IN_BYTES, message.getSession()::sendMessage);
|
||||
message.sendResponse(new FullDataSourceResponseMessage(payload));
|
||||
}, executor);
|
||||
}
|
||||
private void queueWorldGenForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
|
||||
{
|
||||
if (!serverPlayerState.sessionConfig.isDistantGenerationEnabled())
|
||||
{
|
||||
message.sendResponse(new RequestRejectedException("Operation is disabled in config."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rateLimiterSet.generationRequestRateLimiter.tryAcquire(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
DataSourceRequestGroup requestGroup = this.requestGroupByPos.computeIfAbsent(message.sectionPos, pos ->
|
||||
{
|
||||
DataSourceRequestGroup newGroup = new DataSourceRequestGroup();
|
||||
this.tryFulfillDataSourceRequestGroup(newGroup, pos);
|
||||
NETWORK_LOGGER.debug("["+this.serverLevelWrapper.getDimensionName()+"] Created request group for pos ["+DhSectionPos.toString(pos)+"].");
|
||||
return newGroup;
|
||||
});
|
||||
|
||||
// If this fails, loop until either a permit is acquired or the group is removed to create another one
|
||||
if (!requestGroup.requestAddSemaphore.tryAcquire())
|
||||
{
|
||||
Thread.yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
this.requestGroupByFutureId.put(message.futureId, requestGroup);
|
||||
requestGroup.requestMessages.put(message.futureId, message);
|
||||
requestGroup.requestAddSemaphore.release();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** May send an error message in response if the message is a {@link AbstractTrackableMessage} */
|
||||
private <T extends AbstractNetworkMessage> boolean messagePlayerInThisLevel(T message)
|
||||
{
|
||||
if (!(message instanceof ILevelRelatedMessage))
|
||||
{
|
||||
LodUtil.assertNotReach("Received message ["+ILevelRelatedMessage.class.getSimpleName()+"] does not implement ["+message.getClass().getSimpleName()+"]");
|
||||
}
|
||||
|
||||
// Only handle requests for this level
|
||||
if (!((ILevelRelatedMessage) message).isSameLevelAs(this.getServerLevelWrapper()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LodUtil.assertTrue(message.getSession().serverPlayer != null);
|
||||
|
||||
// Check if the player is in this dimension,
|
||||
// since handling multiple dimensions isn't allowed
|
||||
if (message.getSession().serverPlayer.getLevel() != this.getLevelWrapper())
|
||||
{
|
||||
// If the message can be replied to - reply with an error, otherwise just ignore
|
||||
if (message instanceof AbstractTrackableMessage)
|
||||
{
|
||||
((AbstractTrackableMessage) message).sendResponse(
|
||||
new InvalidLevelException(
|
||||
"Generation not allowed. " +
|
||||
"Requested dimension: ["+((ILevelRelatedMessage) message).getLevelName()+"], " +
|
||||
"player dimension: ["+message.getSession().serverPlayer.getLevel().getDimensionName()+"], " +
|
||||
"handler dimension: ["+this.getLevelWrapper().getDimensionName()+"]"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// world gen //
|
||||
//===========//
|
||||
|
||||
@Override
|
||||
public void onWorldGenTaskComplete(long pos)
|
||||
{
|
||||
DataSourceRequestGroup requestGroup = this.requestGroupByPos.get(pos);
|
||||
if (requestGroup != null)
|
||||
{
|
||||
this.tryFulfillDataSourceRequestGroup(requestGroup, pos);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryFulfillDataSourceRequestGroup(DataSourceRequestGroup requestGroup, long pos)
|
||||
{
|
||||
this.serverside.fullDataFileHandler.getAsync(pos).thenAccept(fullDataSource ->
|
||||
{
|
||||
if (this.serverside.fullDataFileHandler.isFullyGenerated(fullDataSource.columnGenerationSteps))
|
||||
{
|
||||
requestGroup.fullDataSource = fullDataSource;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.serverside.fullDataFileHandler.queuePositionForRetrieval(pos);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// player handling //
|
||||
//=================//
|
||||
|
||||
public void addPlayer(IServerPlayerWrapper serverPlayer) { this.worldGenPlayerCenteringQueue.add(serverPlayer); }
|
||||
public void removePlayer(IServerPlayerWrapper serverPlayer) { this.worldGenPlayerCenteringQueue.remove(serverPlayer); }
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> updateDataSourcesAsync(FullDataSourceV2 data)
|
||||
{
|
||||
if (!Config.Client.Advanced.Multiplayer.ServerNetworking.enableRealTimeUpdates.get())
|
||||
{
|
||||
return this.getFullDataProvider().updateDataSourceAsync(data);
|
||||
}
|
||||
|
||||
ThreadPoolExecutor executor = ThreadPoolUtil.getNetworkCompressionExecutor();
|
||||
if (executor == null)
|
||||
{
|
||||
LOGGER.warn("Unable to send FullDataPartialUpdateMessage - getNetworkCompressionExecutor() is null");
|
||||
return this.getFullDataProvider().updateDataSourceAsync(data);
|
||||
}
|
||||
CompletableFuture.runAsync(() ->
|
||||
{
|
||||
FullDataPayload payload = new FullDataPayload(data);
|
||||
for (ServerPlayerState serverPlayerState : this.remotePlayerConnectionHandler.getConnectedPlayers())
|
||||
{
|
||||
if (serverPlayerState.getServerPlayer().getLevel() != this.serverLevelWrapper)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!serverPlayerState.sessionConfig.isRealTimeUpdatesEnabled())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
|
||||
int distanceFromPlayer = DhSectionPos.getManhattanBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
|
||||
if (distanceFromPlayer >= serverPlayerState.getServerPlayer().getViewDistance()
|
||||
&& distanceFromPlayer <= serverPlayerState.sessionConfig.getRenderDistanceRadius())
|
||||
{
|
||||
payload.splitAndSend(FULL_DATA_SPLIT_SIZE_IN_BYTES, serverPlayerState.networkSession::sendMessage);
|
||||
serverPlayerState.networkSession.sendMessage(new FullDataPartialUpdateMessage(this.serverLevelWrapper, payload));
|
||||
}
|
||||
}
|
||||
}, executor);
|
||||
|
||||
|
||||
return this.getFullDataProvider().updateDataSourceAsync(data);
|
||||
return true; //todo;
|
||||
}
|
||||
|
||||
|
||||
@@ -465,24 +56,6 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
|
||||
// getters //
|
||||
//=========//
|
||||
|
||||
@Override
|
||||
public int getMinY() { return this.getLevelWrapper().getMinHeight(); }
|
||||
|
||||
@Override
|
||||
public IServerLevelWrapper getServerLevelWrapper() { return this.serverLevelWrapper; }
|
||||
|
||||
@Override
|
||||
public ILevelWrapper getLevelWrapper() { return this.getServerLevelWrapper(); }
|
||||
|
||||
@Override
|
||||
public FullDataSourceProviderV2 getFullDataProvider() { return this.serverside.fullDataFileHandler; }
|
||||
|
||||
@Override
|
||||
public AbstractSaveStructure getSaveStructure() { return this.serverside.saveStructure; }
|
||||
|
||||
@Override
|
||||
public boolean hasSkyLight() { return this.serverLevelWrapper.hasSkyLight(); }
|
||||
|
||||
@Override
|
||||
public GenericObjectRenderer getGenericRenderer()
|
||||
{
|
||||
@@ -505,8 +78,7 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
|
||||
@Override
|
||||
public void addDebugMenuStringsToList(List<String> messageList)
|
||||
{
|
||||
String dimName = this.serverLevelWrapper.getDimensionName();
|
||||
messageList.add("["+dimName+"]");
|
||||
messageList.add("[${this.serverLevelWrapper.getDimensionName()}]");
|
||||
}
|
||||
|
||||
|
||||
@@ -520,33 +92,7 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
|
||||
{
|
||||
super.close();
|
||||
this.serverside.close();
|
||||
LOGGER.info("Closed DHLevel for ["+this.getLevelWrapper()+"].");
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
//================//
|
||||
|
||||
private static class DataSourceRequestGroup
|
||||
{
|
||||
public final ConcurrentMap<Long, FullDataSourceRequestMessage> requestMessages = new ConcurrentHashMap<>();
|
||||
|
||||
@CheckForNull
|
||||
public FullDataSourceV2 fullDataSource;
|
||||
|
||||
/**
|
||||
* These semaphores prevent a given thread from accidentally locking on the same group
|
||||
* multiple times, as the semaphore is tied to the given thread. <br>
|
||||
* Reentrant Lock isn't used since it would allow the thread to lock on the same group. <br>
|
||||
* the Short.MAX_VALUE is just a very large number that should be larger than the number of
|
||||
* threads we'll have.
|
||||
*/
|
||||
public final Semaphore requestAddSemaphore = new Semaphore(Short.MAX_VALUE, true);
|
||||
/** @see DataSourceRequestGroup#requestAddSemaphore */
|
||||
public final Semaphore requestRemoveSemaphore = new Semaphore(Short.MAX_VALUE, true);
|
||||
|
||||
LOGGER.info("Closed DHLevel for [${this.getLevelWrapper()}].");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,10 +34,9 @@ public class ServerLevelModule implements AutoCloseable
|
||||
{
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
|
||||
public final IDhServerLevel parentServerLevel;
|
||||
private final IDhServerLevel parentServerLevel;
|
||||
public final AbstractSaveStructure saveStructure;
|
||||
public final GeneratedFullDataSourceProvider fullDataFileHandler;
|
||||
public final AppliedConfigState<Boolean> worldGeneratorEnabledConfig;
|
||||
|
||||
public final WorldGenModule worldGenModule;
|
||||
|
||||
@@ -52,8 +51,7 @@ public class ServerLevelModule implements AutoCloseable
|
||||
this.parentServerLevel = parentServerLevel;
|
||||
this.saveStructure = saveStructure;
|
||||
this.fullDataFileHandler = new GeneratedFullDataSourceProvider(parentServerLevel, saveStructure);
|
||||
this.worldGeneratorEnabledConfig = new AppliedConfigState<>(Config.Client.Advanced.WorldGenerator.enableDistantGeneration);
|
||||
this.worldGenModule = new WorldGenModule(this.parentServerLevel);
|
||||
this.worldGenModule = new WorldGenModule(this.parentServerLevel, this.fullDataFileHandler, () -> new ServerLevelModule.WorldGenState(this.parentServerLevel));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -25,11 +25,15 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Handles both single-player/server-side world gen and client side LOD requests.
|
||||
@@ -41,6 +45,9 @@ public class WorldGenModule implements Closeable
|
||||
|
||||
private final GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener;
|
||||
|
||||
private final GeneratedFullDataSourceProvider dataSourceProvider;
|
||||
private final Supplier<? extends AbstractWorldGenState> worldGenStateSupplier;
|
||||
|
||||
private final AtomicReference<AbstractWorldGenState> worldGenStateRef = new AtomicReference<>();
|
||||
|
||||
|
||||
@@ -49,9 +56,15 @@ public class WorldGenModule implements Closeable
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public WorldGenModule(GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener)
|
||||
public WorldGenModule(
|
||||
GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener,
|
||||
GeneratedFullDataSourceProvider dataSourceProvider,
|
||||
Supplier<? extends AbstractWorldGenState> worldGenStateSupplier
|
||||
)
|
||||
{
|
||||
this.onWorldGenCompleteListener = onWorldGenCompleteListener;
|
||||
this.dataSourceProvider = dataSourceProvider;
|
||||
this.worldGenStateSupplier = worldGenStateSupplier;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,13 +108,33 @@ public class WorldGenModule implements Closeable
|
||||
dataFileHandler.removeWorldGenCompleteListener(this.onWorldGenCompleteListener);
|
||||
}
|
||||
|
||||
/** @param targetPosForGeneration the position that world generation should be centered around */
|
||||
public void worldGenTick(DhBlockPos2D targetPosForGeneration)
|
||||
public void worldGenTick()
|
||||
{
|
||||
AbstractWorldGenState worldGenState = this.worldGenStateRef.get();
|
||||
if (worldGenState != null)
|
||||
boolean shouldDoWorldGen = this.onWorldGenCompleteListener.shouldDoWorldGen();
|
||||
|
||||
boolean isWorldGenRunning = this.isWorldGenRunning();
|
||||
if (shouldDoWorldGen && !isWorldGenRunning)
|
||||
{
|
||||
worldGenState.startGenerationQueueAndSetTargetPos(targetPosForGeneration);
|
||||
// start world gen
|
||||
this.startWorldGen(this.dataSourceProvider, this.worldGenStateSupplier.get());
|
||||
}
|
||||
else if (!shouldDoWorldGen && isWorldGenRunning)
|
||||
{
|
||||
// stop world gen
|
||||
this.stopWorldGen(this.dataSourceProvider);
|
||||
}
|
||||
|
||||
if (this.isWorldGenRunning())
|
||||
{
|
||||
AbstractWorldGenState worldGenState = this.worldGenStateRef.get();
|
||||
if (worldGenState != null)
|
||||
{
|
||||
DhBlockPos2D targetPosForGeneration = this.onWorldGenCompleteListener.getTargetPosForGeneration();
|
||||
if (targetPosForGeneration != null)
|
||||
{
|
||||
worldGenState.startGenerationQueueAndSetTargetPos(targetPosForGeneration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.seibel.distanthorizons.core.multiplayer.server;
|
||||
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
|
||||
import com.seibel.distanthorizons.core.level.DhServerLevel;
|
||||
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
|
||||
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
|
||||
import com.seibel.distanthorizons.core.network.messages.base.CurrentLevelKeyMessage;
|
||||
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
|
||||
@@ -33,8 +33,8 @@ public class ServerPlayerState implements Closeable
|
||||
@NotNull
|
||||
public final SessionConfig sessionConfig = new SessionConfig();
|
||||
|
||||
private final ConcurrentHashMap<DhServerLevel, RateLimiterSet> rateLimiterSets = new ConcurrentHashMap<>();
|
||||
public RateLimiterSet getRateLimiterSet(DhServerLevel level) { return this.rateLimiterSets.computeIfAbsent(level, ignored -> new RateLimiterSet()); }
|
||||
private final ConcurrentHashMap<AbstractDhServerLevel, RateLimiterSet> rateLimiterSets = new ConcurrentHashMap<>();
|
||||
public RateLimiterSet getRateLimiterSet(AbstractDhServerLevel level) { return this.rateLimiterSets.computeIfAbsent(level, ignored -> new RateLimiterSet()); }
|
||||
public void clearRateLimiterSets() { this.rateLimiterSets.clear(); }
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.seibel.distanthorizons.core.multiplayer.server;
|
||||
|
||||
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
||||
import com.seibel.distanthorizons.core.network.session.NetworkSession;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -9,11 +8,12 @@ import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class RemotePlayerConnectionHandler
|
||||
public class ServerPlayerStateManager
|
||||
{
|
||||
private final ConcurrentMap<IServerPlayerWrapper, ServerPlayerState> connectedPlayerStateByPlayerWrapper = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<IServerPlayerWrapper, Queue<AbstractNetworkMessage>> messageQueueByPlayerWrapper = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<IServerPlayerWrapper, MessageQueueState> messageQueueByPlayerWrapper = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
|
||||
@@ -25,19 +25,6 @@ public class RemotePlayerConnectionHandler
|
||||
{
|
||||
ServerPlayerState playerState = new ServerPlayerState(serverPlayer);
|
||||
this.connectedPlayerStateByPlayerWrapper.put(serverPlayer, playerState);
|
||||
|
||||
Queue<AbstractNetworkMessage> queuedMessages = this.messageQueueByPlayerWrapper.get(serverPlayer);
|
||||
if (queuedMessages != null)
|
||||
{
|
||||
NetworkSession networkSession = playerState.networkSession;
|
||||
for (AbstractNetworkMessage message : queuedMessages)
|
||||
{
|
||||
networkSession.tryHandleMessage(message);
|
||||
}
|
||||
|
||||
this.messageQueueByPlayerWrapper.remove(serverPlayer);
|
||||
}
|
||||
|
||||
return playerState;
|
||||
}
|
||||
|
||||
@@ -58,14 +45,30 @@ public class RemotePlayerConnectionHandler
|
||||
|
||||
public void handlePluginMessage(IServerPlayerWrapper player, AbstractNetworkMessage message)
|
||||
{
|
||||
MessageQueueState messageQueue = this.messageQueueByPlayerWrapper.computeIfAbsent(player, k -> new MessageQueueState());
|
||||
messageQueue.messageQueue.add(message);
|
||||
|
||||
ServerPlayerState playerState = this.connectedPlayerStateByPlayerWrapper.get(player);
|
||||
if (playerState != null)
|
||||
{
|
||||
playerState.networkSession.tryHandleMessage(message);
|
||||
this.handlePluginMessagesFromQueue(playerState, messageQueue);
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
public void handlePluginMessagesFromQueue(ServerPlayerState playerState)
|
||||
{
|
||||
MessageQueueState messageQueue = this.messageQueueByPlayerWrapper.computeIfAbsent(playerState.getServerPlayer(), k -> new MessageQueueState());
|
||||
this.handlePluginMessagesFromQueue(playerState, messageQueue);
|
||||
}
|
||||
|
||||
private void handlePluginMessagesFromQueue(ServerPlayerState playerState, MessageQueueState messageQueueState)
|
||||
{
|
||||
while (!messageQueueState.messageQueue.isEmpty() && messageQueueState.isBeingDrained.compareAndSet(false, true))
|
||||
{
|
||||
this.messageQueueByPlayerWrapper.computeIfAbsent(player, k -> new ConcurrentLinkedQueue<>()).add(message);
|
||||
AbstractNetworkMessage message = messageQueueState.messageQueue.poll();
|
||||
playerState.networkSession.tryHandleMessage(message);
|
||||
|
||||
messageQueueState.isBeingDrained.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +83,12 @@ public class RemotePlayerConnectionHandler
|
||||
public Iterable<ServerPlayerState> getConnectedPlayers() { return this.connectedPlayerStateByPlayerWrapper.values(); }
|
||||
|
||||
|
||||
private static class MessageQueueState
|
||||
{
|
||||
public final Queue<AbstractNetworkMessage> messageQueue = new ConcurrentLinkedQueue<>();
|
||||
public final AtomicBoolean isBeingDrained = new AtomicBoolean();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -23,13 +23,11 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
|
||||
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
|
||||
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
|
||||
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
|
||||
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
|
||||
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||
@@ -197,6 +195,15 @@ public class CloudRenderHandler
|
||||
|
||||
// this color is changed at render time based on the level time
|
||||
Color color = new Color(255,255,255,255);
|
||||
if (DEBUG_BORDER_COLORS)
|
||||
{
|
||||
// equals is included so the boarder is 2 blocks wide, making it easier to see
|
||||
if (x <= 1) { color = Color.RED; }
|
||||
else if (x >= textureWidth - 2) { color = Color.GREEN; }
|
||||
if (z <= 1) { color = Color.BLUE; }
|
||||
else if (z >= textureWidth - 2) { color = Color.BLACK; }
|
||||
}
|
||||
|
||||
DhApiRenderableBox box = new DhApiRenderableBox(
|
||||
new DhApiVec3d(minXBlockPos, 0, minZBlockPos),
|
||||
new DhApiVec3d(maxXBlockPos, CLOUD_BOX_THICKNESS, maxZBlockPos),
|
||||
@@ -263,7 +270,7 @@ public class CloudRenderHandler
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.renderer.getUseInstancedRendering())
|
||||
if (!this.renderer.getInstancedRenderingAvailable())
|
||||
{
|
||||
if (!this.disabledWarningLogged)
|
||||
{
|
||||
@@ -283,20 +290,15 @@ public class CloudRenderHandler
|
||||
// FIXME transparency sorting makes having transparent clouds impossible
|
||||
// maybe someday we could add the option to cull individual faces? a single bit for each direction should be enough
|
||||
|
||||
// cloud color changes based on the time of day and weather so we need to get it from the level
|
||||
Color cloudColor = this.level.getClientLevelWrapper().getCloudColor(renderParam.partialTicks);
|
||||
if (DEBUG_BORDER_COLORS)
|
||||
// if debug colors are enabled don't change them
|
||||
if (!DEBUG_BORDER_COLORS)
|
||||
{
|
||||
// equals is included so the board is 2 blocks wide, it makes it easier to see
|
||||
if (cloudParams.instanceOffsetX <= 1) { cloudColor = Color.RED; }
|
||||
else if (cloudParams.instanceOffsetX >= cloudParams.textureWidth - 2) { cloudColor = Color.GREEN; }
|
||||
if (cloudParams.instanceOffsetZ <= 1) { cloudColor = Color.BLUE; }
|
||||
else if (cloudParams.instanceOffsetZ >= cloudParams.textureWidth - 2) { cloudColor = Color.BLACK; }
|
||||
}
|
||||
|
||||
for (DhApiRenderableBox box : boxGroup)
|
||||
{
|
||||
box.color = cloudColor;
|
||||
// cloud color changes based on the time of day and weather so we need to get it from the level
|
||||
Color cloudColor = this.level.getClientLevelWrapper().getCloudColor(renderParam.partialTicks);
|
||||
for (DhApiRenderableBox box : boxGroup)
|
||||
{
|
||||
box.color = cloudColor;
|
||||
}
|
||||
}
|
||||
boxGroup.triggerBoxChange();
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
|
||||
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
|
||||
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
|
||||
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.jar.EPlatform;
|
||||
@@ -44,14 +45,12 @@ import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||
import com.seibel.distanthorizons.core.util.math.Vec3d;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModAccessor;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.ISodiumAccessor;
|
||||
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||
import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.opengl.ARBInstancedArrays;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
@@ -59,7 +58,6 @@ import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.awt.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -87,11 +85,12 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
|
||||
// rendering setup
|
||||
private boolean init = false;
|
||||
|
||||
private IDhApiGenericObjectShaderProgram shaderProgram;
|
||||
private IDhApiGenericObjectShaderProgram instancedShaderProgram;
|
||||
private IDhApiGenericObjectShaderProgram directShaderProgram;
|
||||
private GLVertexBuffer boxVertexBuffer;
|
||||
private GLElementBuffer boxIndexBuffer;
|
||||
|
||||
private boolean useInstancedRendering;
|
||||
private boolean instancedRenderingAvailable;
|
||||
private boolean vertexAttribDivisorSupported;
|
||||
private boolean instancedArraysSupported;
|
||||
|
||||
@@ -186,8 +185,8 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
|
||||
|
||||
this.vertexAttribDivisorSupported = GLProxy.getInstance().vertexAttribDivisorSupported;
|
||||
this.instancedArraysSupported = GLProxy.getInstance().instancedArraysSupported;
|
||||
this.useInstancedRendering = this.vertexAttribDivisorSupported || this.instancedArraysSupported;
|
||||
if (!this.useInstancedRendering)
|
||||
this.instancedRenderingAvailable = this.vertexAttribDivisorSupported || this.instancedArraysSupported;
|
||||
if (!this.instancedRenderingAvailable)
|
||||
{
|
||||
LOGGER.warn("Instanced rendering not supported by this GPU, falling back to direct rendering. Generic object rendering will be slow and some effects may be disabled.");
|
||||
}
|
||||
@@ -196,8 +195,7 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
|
||||
boolean isMac = (EPlatform.get() == EPlatform.MACOS);
|
||||
if (isMac && SODIUM != null)
|
||||
{
|
||||
this.useInstancedRendering = false;
|
||||
LOGGER.warn("Instanced rendering is broken on Mac when Sodium is present, falling back to direct rendering. Generic object rendering will be slow and some effects may be disabled.");
|
||||
LOGGER.warn("There have been reports of instanced rendering causing crashes on macOS when Sodium is present. Instanced rendering can be disabled via the DH config.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +205,8 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
|
||||
// startup the renderer //
|
||||
//======================//
|
||||
|
||||
this.shaderProgram = new GenericObjectShaderProgram(this.useInstancedRendering);
|
||||
this.instancedShaderProgram = new GenericObjectShaderProgram(true);
|
||||
this.directShaderProgram = new GenericObjectShaderProgram(false);
|
||||
|
||||
this.createBuffers();
|
||||
|
||||
@@ -392,6 +391,9 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
|
||||
GLState glState = new GLState();
|
||||
this.init();
|
||||
|
||||
boolean useInstancedRendering = this.instancedRenderingAvailable
|
||||
&& Config.Client.Advanced.Graphics.GenericRendering.enableInstancedRendering.get();
|
||||
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeGenericRenderSetupEvent.class, renderEventParam);
|
||||
|
||||
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
|
||||
@@ -401,7 +403,7 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
|
||||
GL32.glBlendEquation(GL32.GL_FUNC_ADD);
|
||||
GL32.glBlendFuncSeparate(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA, GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
IDhApiGenericObjectShaderProgram shaderProgram = this.shaderProgram;
|
||||
IDhApiGenericObjectShaderProgram shaderProgram = useInstancedRendering ? this.instancedShaderProgram : this.directShaderProgram;
|
||||
IDhApiGenericObjectShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiGenericObjectShaderProgram.class);
|
||||
if (shaderProgramOverride != null && shaderProgram.overrideThisFrame())
|
||||
{
|
||||
@@ -437,7 +439,7 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
|
||||
profiler.popPush("rendering");
|
||||
profiler.push(boxGroup.getResourceLocationNamespace());
|
||||
profiler.push(boxGroup.getResourceLocationPath());
|
||||
if (this.useInstancedRendering)
|
||||
if (useInstancedRendering)
|
||||
{
|
||||
this.renderBoxGroupInstanced(shaderProgram, renderEventParam, boxGroup, camPos);
|
||||
}
|
||||
@@ -593,14 +595,14 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
|
||||
//=========//
|
||||
|
||||
/** @throws IllegalStateException if {@link #init()} function hasn't been called yet */
|
||||
public boolean getUseInstancedRendering() throws IllegalStateException
|
||||
public boolean getInstancedRenderingAvailable() throws IllegalStateException
|
||||
{
|
||||
if (!this.init)
|
||||
{
|
||||
throw new IllegalStateException("GL initialization hasn't been completed.");
|
||||
}
|
||||
|
||||
return this.useInstancedRendering;
|
||||
return this.instancedRenderingAvailable;
|
||||
}
|
||||
|
||||
|
||||
@@ -612,21 +614,26 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
|
||||
public String getVboRenderDebugMenuString()
|
||||
{
|
||||
// get counts
|
||||
int totalCount = this.boxGroupById.size();
|
||||
int activeCount = 0;
|
||||
int totalGroupCount = this.boxGroupById.size();
|
||||
int totalBoxCount = 0;
|
||||
|
||||
int activeGroupCount = 0;
|
||||
int activeBoxCount = 0;
|
||||
|
||||
for (long key : this.boxGroupById.keySet())
|
||||
{
|
||||
RenderableBoxGroup renderGroup = this.boxGroupById.get(key);
|
||||
if (renderGroup.active)
|
||||
{
|
||||
activeCount++;
|
||||
activeGroupCount++;
|
||||
activeBoxCount += renderGroup.size();
|
||||
}
|
||||
totalBoxCount += renderGroup.size();
|
||||
}
|
||||
|
||||
|
||||
String totalCountText = F3Screen.NUMBER_FORMAT.format(totalCount);
|
||||
String activeCountText = F3Screen.NUMBER_FORMAT.format(activeCount);
|
||||
return LodUtil.formatLog("Generic Obj Count: " + activeCountText + "/" + totalCountText);
|
||||
return "Generic Obj #: " + F3Screen.NUMBER_FORMAT.format(activeGroupCount) + "/" + F3Screen.NUMBER_FORMAT.format(totalGroupCount) + ", " +
|
||||
"Cube #: " + F3Screen.NUMBER_FORMAT.format(activeBoxCount) + "/" + F3Screen.NUMBER_FORMAT.format(totalBoxCount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.google.common.base.MoreObjects;
|
||||
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
|
||||
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
|
||||
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
@@ -79,14 +80,16 @@ public class FullDataSourceV2DTO implements IBaseDTO<Long>, INetworkObject
|
||||
|
||||
public static FullDataSourceV2DTO CreateFromDataSource(FullDataSourceV2 dataSource, EDhApiDataCompressionMode compressionModeEnum) throws IOException
|
||||
{
|
||||
CheckedByteArray checkedDataPointArray = writeDataSourceDataArrayToBlob(dataSource.dataPoints, compressionModeEnum);
|
||||
byte[] dataPointByteArray = writeDataSourceDataArrayToBlob(dataSource.dataPoints, compressionModeEnum);
|
||||
byte[] compressedWorldGenStepByteArray = writeGenerationStepsToBlob(dataSource.columnGenerationSteps, compressionModeEnum);
|
||||
byte[] compressedWorldCompressionModeByteArray = writeWorldCompressionModeToBlob(dataSource.columnWorldCompressionMode, compressionModeEnum);
|
||||
byte[] mappingByteArray = writeDataMappingToBlob(dataSource.mapping, compressionModeEnum);
|
||||
|
||||
int checksum = (dataSource.mapping.hashCode() * 4217) + dataSource.hashCode();
|
||||
|
||||
return new FullDataSourceV2DTO(
|
||||
dataSource.getPos(),
|
||||
checkedDataPointArray.checksum, compressedWorldGenStepByteArray, compressedWorldCompressionModeByteArray, FullDataSourceV2.DATA_FORMAT_VERSION, compressionModeEnum.value, checkedDataPointArray.byteArray,
|
||||
dataSource.getPos(),
|
||||
checksum, compressedWorldGenStepByteArray, compressedWorldCompressionModeByteArray, FullDataSourceV2.DATA_FORMAT_VERSION, compressionModeEnum.value, dataPointByteArray,
|
||||
dataSource.lastModifiedUnixDateTime, dataSource.createdUnixDateTime,
|
||||
mappingByteArray, dataSource.applyToParent,
|
||||
dataSource.levelMinY
|
||||
@@ -204,16 +207,14 @@ public class FullDataSourceV2DTO implements IBaseDTO<Long>, INetworkObject
|
||||
// (de)serializing //
|
||||
//=================//
|
||||
|
||||
private static CheckedByteArray writeDataSourceDataArrayToBlob(LongArrayList[] dataArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException
|
||||
private static byte[] writeDataSourceDataArrayToBlob(LongArrayList[] dataArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException
|
||||
{
|
||||
// write the outputs to a stream to prep for writing to the database
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
|
||||
// the order of these streams is important, otherwise the checksum won't be calculated
|
||||
CheckedOutputStream checkedOut = new CheckedOutputStream(byteArrayOutputStream, new Adler32());
|
||||
// normally a DhStream should be the topmost stream to prevent closing the stream accidentally,
|
||||
// but since this stream will be closed immediately after writing anyway, it won't be an issue
|
||||
DhDataOutputStream compressedOut = new DhDataOutputStream(checkedOut, compressionModeEnum);
|
||||
DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum);
|
||||
|
||||
|
||||
// write the data
|
||||
@@ -238,10 +239,9 @@ public class FullDataSourceV2DTO implements IBaseDTO<Long>, INetworkObject
|
||||
|
||||
// generate the checksum
|
||||
compressedOut.flush();
|
||||
int checksum = (int) checkedOut.getChecksum().getValue();
|
||||
byteArrayOutputStream.close();
|
||||
|
||||
return new CheckedByteArray(checksum, byteArrayOutputStream.toByteArray());
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
private static LongArrayList[] readBlobToDataSourceDataArray(byte[] compressedDataByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
|
||||
{
|
||||
@@ -452,22 +452,4 @@ public class FullDataSourceV2DTO implements IBaseDTO<Long>, INetworkObject
|
||||
|
||||
public EDhApiDataCompressionMode getCompressionMode() throws IllegalArgumentException { return EDhApiDataCompressionMode.getFromValue(this.compressionModeValue); }
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
//================//
|
||||
|
||||
private static class CheckedByteArray
|
||||
{
|
||||
public final int checksum;
|
||||
public final byte[] byteArray;
|
||||
|
||||
public CheckedByteArray(int checksum, byte[] byteArray)
|
||||
{
|
||||
this.checksum = checksum;
|
||||
this.byteArray = byteArray;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,13 +40,17 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
*/
|
||||
public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implements AutoCloseable
|
||||
{
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
|
||||
public static final String DEFAULT_DATABASE_TYPE = "jdbc:sqlite";
|
||||
/** a value of 0 means there's no timeout */
|
||||
public static final int TIMEOUT_SECONDS = 0;
|
||||
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
private static final ConcurrentHashMap<String, Connection> CONNECTIONS_BY_CONNECTION_STRING = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentHashMap<AbstractDhRepo<?, ?>, String> ACTIVE_CONNECTION_STRINGS_BY_REPO = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
|
||||
private final String connectionString;
|
||||
private final Connection connection;
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.seibel.distanthorizons.core.world;
|
||||
|
||||
import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure;
|
||||
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
|
||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
|
||||
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhServerLevel> extends AbstractDhWorld implements IDhServerWorld
|
||||
{
|
||||
protected final HashMap<ILevelWrapper, TDhServerLevel> dhLevelByLevelWrapper = new HashMap<>();
|
||||
public final LocalSaveStructure saveStructure = new LocalSaveStructure();
|
||||
|
||||
private final ServerPlayerStateManager serverPlayerStateManager;
|
||||
|
||||
public AbstractDhServerWorld(EWorldEnvironment worldEnvironment)
|
||||
{
|
||||
super(worldEnvironment);
|
||||
this.serverPlayerStateManager = new ServerPlayerStateManager();
|
||||
}
|
||||
|
||||
|
||||
//=================//
|
||||
// player handling //
|
||||
//=================//
|
||||
|
||||
|
||||
@Override
|
||||
public ServerPlayerStateManager getServerPlayerStateManager()
|
||||
{
|
||||
return this.serverPlayerStateManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPlayer(IServerPlayerWrapper serverPlayer)
|
||||
{
|
||||
ServerPlayerState playerState = this.serverPlayerStateManager.registerJoinedPlayer(serverPlayer);
|
||||
this.getLevel(serverPlayer.getLevel()).addPlayer(serverPlayer);
|
||||
|
||||
for (TDhServerLevel level : (Iterable<? extends TDhServerLevel>) this.dhLevelByLevelWrapper.values().stream().distinct()::iterator)
|
||||
{
|
||||
level.registerNetworkHandlers(playerState);
|
||||
}
|
||||
|
||||
this.serverPlayerStateManager.handlePluginMessagesFromQueue(playerState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePlayer(IServerPlayerWrapper serverPlayer)
|
||||
{
|
||||
this.getLevel(serverPlayer.getLevel()).removePlayer(serverPlayer);
|
||||
this.serverPlayerStateManager.unregisterLeftPlayer(serverPlayer);
|
||||
|
||||
// If player's left, session is already closed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changePlayerLevel(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
|
||||
{
|
||||
this.getLevel(destinationLevel).addPlayer(player);
|
||||
this.getLevel(originLevel).removePlayer(player);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// level handling //
|
||||
//================//
|
||||
|
||||
@Override
|
||||
public TDhServerLevel getLevel(@NotNull ILevelWrapper wrapper) { return this.dhLevelByLevelWrapper.get(wrapper); }
|
||||
@Override
|
||||
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.dhLevelByLevelWrapper.values(); }
|
||||
@Override
|
||||
public int getLoadedLevelCount() { return this.dhLevelByLevelWrapper.size(); }
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// tick methods //
|
||||
//==============//
|
||||
|
||||
@Override
|
||||
public void serverTick() { this.dhLevelByLevelWrapper.values().forEach(TDhServerLevel::serverTick); }
|
||||
|
||||
@Override
|
||||
public void worldGenTick() { this.dhLevelByLevelWrapper.values().forEach(TDhServerLevel::worldGenTick); }
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// base overrides //
|
||||
//================//
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
for (TDhServerLevel level : this.dhLevelByLevelWrapper.values())
|
||||
{
|
||||
LOGGER.info("Unloading level [" + level.getLevelWrapper().getDimensionName() + "].");
|
||||
|
||||
// level wrapper shouldn't be null, but just in case
|
||||
IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
|
||||
if (serverLevelWrapper != null)
|
||||
{
|
||||
serverLevelWrapper.onUnload();
|
||||
}
|
||||
|
||||
level.close();
|
||||
}
|
||||
|
||||
this.dhLevelByLevelWrapper.clear();
|
||||
LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
package com.seibel.distanthorizons.core.world;
|
||||
|
||||
import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure;
|
||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||
import com.seibel.distanthorizons.core.level.DhClientServerLevel;
|
||||
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.EventLoop;
|
||||
@@ -31,15 +29,14 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapp
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWorld, IDhServerWorld
|
||||
public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLevel> implements IDhClientWorld
|
||||
{
|
||||
private final HashMap<ILevelWrapper, DhClientServerLevel> levelWrapperByDhLevel = new HashMap<>();
|
||||
private final HashSet<DhClientServerLevel> dhLevels = new HashSet<>();
|
||||
public final LocalSaveStructure saveStructure = new LocalSaveStructure();
|
||||
private final Set<DhClientServerLevel> dhLevels = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client Server World Ticker Thread", 2);
|
||||
public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop
|
||||
@@ -67,18 +64,18 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
|
||||
{
|
||||
if (wrapper instanceof IServerLevelWrapper)
|
||||
{
|
||||
return this.levelWrapperByDhLevel.computeIfAbsent(wrapper, (levelWrapper) ->
|
||||
return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper, (levelWrapper) ->
|
||||
{
|
||||
File levelFile = this.saveStructure.getLevelFolder(levelWrapper);
|
||||
LodUtil.assertTrue(levelFile != null);
|
||||
DhClientServerLevel level = new DhClientServerLevel(this.saveStructure, (IServerLevelWrapper) levelWrapper);
|
||||
DhClientServerLevel level = new DhClientServerLevel(this.saveStructure, (IServerLevelWrapper) levelWrapper, this.getServerPlayerStateManager());
|
||||
this.dhLevels.add(level);
|
||||
return level;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.levelWrapperByDhLevel.computeIfAbsent(wrapper, (levelWrapper) ->
|
||||
return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper, (levelWrapper) ->
|
||||
{
|
||||
IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper;
|
||||
IServerLevelWrapper serverLevelWrapper = clientLevelWrapper.tryGetServerSideWrapper();
|
||||
@@ -89,7 +86,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
|
||||
}
|
||||
|
||||
|
||||
DhClientServerLevel level = this.levelWrapperByDhLevel.get(serverLevelWrapper);
|
||||
DhClientServerLevel level = this.dhLevelByLevelWrapper.get(serverLevelWrapper);
|
||||
if (level == null)
|
||||
{
|
||||
return null;
|
||||
@@ -102,25 +99,17 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DhClientServerLevel getLevel(@NotNull ILevelWrapper wrapper) { return this.levelWrapperByDhLevel.get(wrapper); }
|
||||
|
||||
@Override
|
||||
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.dhLevels; }
|
||||
@Override
|
||||
public int getLoadedLevelCount() { return this.dhLevels.size(); }
|
||||
|
||||
@Override
|
||||
public void unloadLevel(@NotNull ILevelWrapper wrapper)
|
||||
{
|
||||
if (this.levelWrapperByDhLevel.containsKey(wrapper))
|
||||
if (this.dhLevelByLevelWrapper.containsKey(wrapper))
|
||||
{
|
||||
if (wrapper instanceof IServerLevelWrapper)
|
||||
{
|
||||
LOGGER.info("Unloading level " + this.levelWrapperByDhLevel.get(wrapper));
|
||||
LOGGER.info("Unloading level " + this.dhLevelByLevelWrapper.get(wrapper));
|
||||
wrapper.onUnload();
|
||||
|
||||
DhClientServerLevel clientServerLevel = this.levelWrapperByDhLevel.remove(wrapper);
|
||||
DhClientServerLevel clientServerLevel = this.dhLevelByLevelWrapper.remove(wrapper);
|
||||
clientServerLevel.close();
|
||||
this.dhLevels.remove(clientServerLevel);
|
||||
}
|
||||
@@ -129,7 +118,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
|
||||
// If the level wrapper is a Client Level Wrapper, then that means the client side leaves the level,
|
||||
// but note that the server side still has the level loaded. So, we don't want to unload the level,
|
||||
// we just want to stop rendering it.
|
||||
this.levelWrapperByDhLevel.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere.
|
||||
this.dhLevelByLevelWrapper.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,16 +129,12 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
|
||||
this.dhLevels.forEach(DhClientServerLevel::clientTick);
|
||||
}
|
||||
|
||||
public void clientTick()
|
||||
@Override public void clientTick()
|
||||
{
|
||||
//LOGGER.info("Client world tick");
|
||||
this.eventLoop.tick();
|
||||
}
|
||||
|
||||
public void serverTick() { this.dhLevels.forEach(DhClientServerLevel::serverTick); }
|
||||
|
||||
public void worldGenTick() { this.dhLevels.forEach(DhClientServerLevel::worldGenTick); }
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
@@ -160,25 +145,25 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
|
||||
@Override
|
||||
public synchronized void close()
|
||||
{
|
||||
// clear dhLevels to prevent concurrent modification errors
|
||||
HashSet<DhClientServerLevel> levelsToClose = new HashSet<>(this.dhLevels);
|
||||
this.dhLevels.clear();
|
||||
// close each level
|
||||
for (DhClientServerLevel level : levelsToClose)
|
||||
synchronized (this.dhLevels)
|
||||
{
|
||||
LOGGER.info("Unloading level " + level.getServerLevelWrapper().getDimensionName());
|
||||
|
||||
// level wrapper shouldn't be null, but just in case
|
||||
IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
|
||||
if (serverLevelWrapper != null)
|
||||
// close each level
|
||||
for (DhClientServerLevel level : this.dhLevels)
|
||||
{
|
||||
serverLevelWrapper.onUnload();
|
||||
LOGGER.info("Unloading level " + level.getServerLevelWrapper().getDimensionName());
|
||||
|
||||
// level wrapper shouldn't be null, but just in case
|
||||
IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
|
||||
if (serverLevelWrapper != null)
|
||||
{
|
||||
serverLevelWrapper.onUnload();
|
||||
}
|
||||
|
||||
level.close();
|
||||
}
|
||||
|
||||
level.close();
|
||||
}
|
||||
|
||||
this.levelWrapperByDhLevel.clear();
|
||||
this.dhLevelByLevelWrapper.clear();
|
||||
this.eventLoop.close();
|
||||
LOGGER.info("Closed DhWorld of type " + this.environment);
|
||||
}
|
||||
|
||||
@@ -19,29 +19,16 @@
|
||||
|
||||
package com.seibel.distanthorizons.core.world;
|
||||
|
||||
import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure;
|
||||
import com.seibel.distanthorizons.core.level.DhServerLevel;
|
||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||
import com.seibel.distanthorizons.core.multiplayer.server.RemotePlayerConnectionHandler;
|
||||
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
|
||||
public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
|
||||
{
|
||||
private final HashMap<IServerLevelWrapper, DhServerLevel> levels;
|
||||
public final LocalSaveStructure saveStructure;
|
||||
|
||||
public final RemotePlayerConnectionHandler remotePlayerConnectionHandler;
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// constructors //
|
||||
//==============//
|
||||
@@ -49,48 +36,10 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
|
||||
public DhServerWorld()
|
||||
{
|
||||
super(EWorldEnvironment.Server_Only);
|
||||
|
||||
this.saveStructure = new LocalSaveStructure();
|
||||
this.levels = new HashMap<>();
|
||||
|
||||
this.remotePlayerConnectionHandler = new RemotePlayerConnectionHandler();
|
||||
|
||||
LOGGER.info("Started ["+DhServerWorld.class.getSimpleName()+"] of type ["+this.environment+"].");
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// player handling //
|
||||
//=================//
|
||||
|
||||
public void addPlayer(IServerPlayerWrapper serverPlayer)
|
||||
{
|
||||
ServerPlayerState playerState = this.remotePlayerConnectionHandler.registerJoinedPlayer(serverPlayer);
|
||||
this.getLevel(serverPlayer.getLevel()).addPlayer(serverPlayer);
|
||||
|
||||
for (DhServerLevel level : this.levels.values())
|
||||
{
|
||||
level.registerNetworkHandlers(playerState);
|
||||
}
|
||||
}
|
||||
|
||||
public void removePlayer(IServerPlayerWrapper serverPlayer)
|
||||
{
|
||||
this.getLevel(serverPlayer.getLevel()).removePlayer(serverPlayer);
|
||||
this.remotePlayerConnectionHandler.unregisterLeftPlayer(serverPlayer);
|
||||
|
||||
// If player's left, session is already closed
|
||||
}
|
||||
|
||||
public void changePlayerLevel(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
|
||||
{
|
||||
this.getLevel(destinationLevel).addPlayer(player);
|
||||
this.getLevel(originLevel).removePlayer(player);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// level handling //
|
||||
//================//
|
||||
@@ -103,30 +52,14 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.levels.computeIfAbsent((IServerLevelWrapper) wrapper, (serverLevelWrapper) ->
|
||||
return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper, (serverLevelWrapper) ->
|
||||
{
|
||||
File levelFile = this.saveStructure.getLevelFolder(wrapper);
|
||||
LodUtil.assertTrue(levelFile != null);
|
||||
return new DhServerLevel(this.saveStructure, serverLevelWrapper, this.remotePlayerConnectionHandler);
|
||||
return new DhServerLevel(this.saveStructure, (IServerLevelWrapper) serverLevelWrapper, this.getServerPlayerStateManager());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public DhServerLevel getLevel(@NotNull ILevelWrapper wrapper)
|
||||
{
|
||||
if (!(wrapper instanceof IServerLevelWrapper))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.levels.get(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.levels.values(); }
|
||||
@Override
|
||||
public int getLoadedLevelCount() { return this.levels.size(); }
|
||||
|
||||
@Override
|
||||
public void unloadLevel(@NotNull ILevelWrapper wrapper)
|
||||
{
|
||||
@@ -135,51 +68,12 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.levels.containsKey(wrapper))
|
||||
if (this.dhLevelByLevelWrapper.containsKey(wrapper))
|
||||
{
|
||||
LOGGER.info("Unloading level {} ", this.levels.get(wrapper));
|
||||
LOGGER.info("Unloading level {} ", this.dhLevelByLevelWrapper.get(wrapper));
|
||||
wrapper.onUnload();
|
||||
this.levels.remove(wrapper).close();
|
||||
this.dhLevelByLevelWrapper.remove(wrapper).close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// tick methods //
|
||||
//==============//
|
||||
|
||||
@Override
|
||||
public void serverTick() { this.levels.values().forEach(DhServerLevel::serverTick); }
|
||||
|
||||
@Override
|
||||
public void worldGenTick() { this.levels.values().forEach(DhServerLevel::worldGenTick); }
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// base overrides //
|
||||
//================//
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
for (DhServerLevel level : this.levels.values())
|
||||
{
|
||||
LOGGER.info("Unloading level [" + level.getLevelWrapper().getDimensionName() + "].");
|
||||
|
||||
// level wrapper shouldn't be null, but just in case
|
||||
IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
|
||||
if (serverLevelWrapper != null)
|
||||
{
|
||||
serverLevelWrapper.onUnload();
|
||||
}
|
||||
|
||||
level.close();
|
||||
}
|
||||
|
||||
this.levels.clear();
|
||||
LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,11 +20,18 @@
|
||||
package com.seibel.distanthorizons.core.world;
|
||||
|
||||
import com.seibel.distanthorizons.core.level.IDhServerLevel;
|
||||
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||
|
||||
/** Used both for dedicated server and singleplayer worlds */
|
||||
public interface IDhServerWorld extends IDhWorld
|
||||
{
|
||||
ServerPlayerStateManager getServerPlayerStateManager();
|
||||
void addPlayer(IServerPlayerWrapper serverPlayer);
|
||||
void removePlayer(IServerPlayerWrapper serverPlayer);
|
||||
void changePlayerLevel(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel);
|
||||
void serverTick();
|
||||
|
||||
default IDhServerLevel getOrLoadServerLevel(ILevelWrapper levelWrapper) { return (IDhServerLevel) this.getOrLoadLevel(levelWrapper); }
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Distant Horizons logos © 2024 by Pankakes are licensed under CC BY-SA 4.0
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 40 KiB |
@@ -330,6 +330,10 @@
|
||||
"Enable Cloud Rendering",
|
||||
"distanthorizons.config.client.advanced.graphics.genericRendering.enableCloudRendering.@tooltip":
|
||||
"If true LOD clouds will be rendered.",
|
||||
"distanthorizons.config.client.advanced.graphics.genericRendering.enableInstancedRendering":
|
||||
"Enable Instanced Rendering",
|
||||
"distanthorizons.config.client.advanced.graphics.genericRendering.enableInstancedRendering.@tooltip":
|
||||
"Can be disabled to use much slower but more compatible direct rendering. \nDisabling this can be used to fix some crashes on Mac.",
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.worldGenerator":
|
||||
|
||||
|
Before Width: | Height: | Size: 277 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 573 B After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* 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 tests;
|
||||
|
||||
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
|
||||
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
|
||||
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
||||
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
|
||||
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
|
||||
public class FullDataChecksumStabilityTest
|
||||
{
|
||||
public static String DB_FILE_NAME_PREFIX = "DistantHorizonsTest-";
|
||||
public static String DB_FILE_NAME_SUFFIX = ".sqlite";
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// testing methods //
|
||||
//=================//
|
||||
|
||||
@Test
|
||||
public void testIdenticalDataSources()
|
||||
{
|
||||
System.out.println("--- FullDataChecksumStabilityTest - Identical data sources ---");
|
||||
|
||||
try
|
||||
{
|
||||
int checksum1, checksum2;
|
||||
|
||||
{
|
||||
File uncompressedDatabaseFile = File.createTempFile(DB_FILE_NAME_PREFIX, DB_FILE_NAME_SUFFIX);
|
||||
System.out.println("First database location: " + uncompressedDatabaseFile.getAbsolutePath());
|
||||
Assert.assertTrue(uncompressedDatabaseFile.exists());
|
||||
|
||||
try (FullDataSourceV2Repo uncompressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile))
|
||||
{
|
||||
FullDataSourceV2 uncompressedDataSource = FullDataSourceV2.createEmpty(0);
|
||||
|
||||
int mappingEntryId1 = uncompressedDataSource.mapping.addIfNotPresentAndGetId(new TestBiomeWrapper("1"), new TestBlockStateWrapper());
|
||||
int mappingEntryId2 = uncompressedDataSource.mapping.addIfNotPresentAndGetId(new TestBiomeWrapper("2"), new TestBlockStateWrapper());
|
||||
|
||||
uncompressedDataSource.setSingleColumn(
|
||||
new LongArrayList(new long[]{
|
||||
FullDataPointUtil.encode(mappingEntryId1, 1, 0, (byte) 15, (byte) 15),
|
||||
FullDataPointUtil.encode(mappingEntryId2, 1, 1, (byte) 15, (byte) 15),
|
||||
}),
|
||||
0, 0,
|
||||
EDhApiWorldGenerationStep.FEATURES,
|
||||
EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS
|
||||
);
|
||||
|
||||
uncompressedRepo.save(FullDataSourceV2DTO.CreateFromDataSource(uncompressedDataSource, EDhApiDataCompressionMode.UNCOMPRESSED));
|
||||
|
||||
checksum1 = uncompressedRepo.getByKey(0L).dataChecksum;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
File uncompressedDatabaseFile = File.createTempFile(DB_FILE_NAME_PREFIX, DB_FILE_NAME_SUFFIX);
|
||||
System.out.println("Second database location: " + uncompressedDatabaseFile.getAbsolutePath());
|
||||
Assert.assertTrue(uncompressedDatabaseFile.exists());
|
||||
|
||||
try (FullDataSourceV2Repo uncompressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile))
|
||||
{
|
||||
FullDataSourceV2 uncompressedDataSource = FullDataSourceV2.createEmpty(0);
|
||||
|
||||
int mappingEntryId1 = uncompressedDataSource.mapping.addIfNotPresentAndGetId(new TestBiomeWrapper("1"), new TestBlockStateWrapper());
|
||||
int mappingEntryId2 = uncompressedDataSource.mapping.addIfNotPresentAndGetId(new TestBiomeWrapper("2"), new TestBlockStateWrapper());
|
||||
|
||||
uncompressedDataSource.setSingleColumn(
|
||||
new LongArrayList(new long[]{
|
||||
FullDataPointUtil.encode(mappingEntryId1, 1, 0, (byte) 15, (byte) 15),
|
||||
FullDataPointUtil.encode(mappingEntryId2, 1, 1, (byte) 15, (byte) 15),
|
||||
}),
|
||||
0, 0,
|
||||
EDhApiWorldGenerationStep.FEATURES,
|
||||
EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS
|
||||
);
|
||||
|
||||
uncompressedRepo.save(FullDataSourceV2DTO.CreateFromDataSource(uncompressedDataSource, EDhApiDataCompressionMode.UNCOMPRESSED));
|
||||
|
||||
checksum2 = uncompressedRepo.getByKey(0L).dataChecksum;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertEquals(checksum1, checksum2);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
Assert.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentMappingOrder()
|
||||
{
|
||||
System.out.println("--- FullDataChecksumStabilityTest - Different mapping order ---");
|
||||
|
||||
try
|
||||
{
|
||||
int checksum1, checksum2;
|
||||
|
||||
{
|
||||
File uncompressedDatabaseFile = File.createTempFile(DB_FILE_NAME_PREFIX, DB_FILE_NAME_SUFFIX);
|
||||
System.out.println("First database location: " + uncompressedDatabaseFile.getAbsolutePath());
|
||||
Assert.assertTrue(uncompressedDatabaseFile.exists());
|
||||
|
||||
try (FullDataSourceV2Repo uncompressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile))
|
||||
{
|
||||
FullDataSourceV2 uncompressedDataSource = FullDataSourceV2.createEmpty(0);
|
||||
|
||||
int mappingEntryId1 = uncompressedDataSource.mapping.addIfNotPresentAndGetId(new TestBiomeWrapper("2"), new TestBlockStateWrapper());
|
||||
int mappingEntryId2 = uncompressedDataSource.mapping.addIfNotPresentAndGetId(new TestBiomeWrapper("1"), new TestBlockStateWrapper());
|
||||
|
||||
uncompressedDataSource.setSingleColumn(
|
||||
new LongArrayList(new long[]{
|
||||
FullDataPointUtil.encode(mappingEntryId1, 1, 0, (byte) 15, (byte) 15),
|
||||
FullDataPointUtil.encode(mappingEntryId2, 1, 1, (byte) 15, (byte) 15),
|
||||
}),
|
||||
0, 0,
|
||||
EDhApiWorldGenerationStep.FEATURES,
|
||||
EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS
|
||||
);
|
||||
|
||||
uncompressedRepo.save(FullDataSourceV2DTO.CreateFromDataSource(uncompressedDataSource, EDhApiDataCompressionMode.UNCOMPRESSED));
|
||||
|
||||
checksum1 = uncompressedRepo.getByKey(0L).dataChecksum;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
File uncompressedDatabaseFile = File.createTempFile(DB_FILE_NAME_PREFIX, DB_FILE_NAME_SUFFIX);
|
||||
System.out.println("Second database location: " + uncompressedDatabaseFile.getAbsolutePath());
|
||||
Assert.assertTrue(uncompressedDatabaseFile.exists());
|
||||
|
||||
try (FullDataSourceV2Repo uncompressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile))
|
||||
{
|
||||
FullDataSourceV2 uncompressedDataSource = FullDataSourceV2.createEmpty(0);
|
||||
|
||||
int mappingEntryId1 = uncompressedDataSource.mapping.addIfNotPresentAndGetId(new TestBiomeWrapper("1"), new TestBlockStateWrapper());
|
||||
int mappingEntryId2 = uncompressedDataSource.mapping.addIfNotPresentAndGetId(new TestBiomeWrapper("2"), new TestBlockStateWrapper());
|
||||
|
||||
uncompressedDataSource.setSingleColumn(
|
||||
new LongArrayList(new long[]{
|
||||
FullDataPointUtil.encode(mappingEntryId1, 1, 0, (byte) 15, (byte) 15),
|
||||
FullDataPointUtil.encode(mappingEntryId2, 1, 1, (byte) 15, (byte) 15),
|
||||
}),
|
||||
0, 0,
|
||||
EDhApiWorldGenerationStep.FEATURES,
|
||||
EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS
|
||||
);
|
||||
|
||||
uncompressedRepo.save(FullDataSourceV2DTO.CreateFromDataSource(uncompressedDataSource, EDhApiDataCompressionMode.UNCOMPRESSED));
|
||||
|
||||
checksum2 = uncompressedRepo.getByKey(0L).dataChecksum;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertEquals(checksum1, checksum2);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
Assert.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtraMappingEntries()
|
||||
{
|
||||
System.out.println("--- FullDataChecksumStabilityTest - Different mapping order ---");
|
||||
|
||||
try
|
||||
{
|
||||
int checksum1, checksum2;
|
||||
|
||||
{
|
||||
File uncompressedDatabaseFile = File.createTempFile(DB_FILE_NAME_PREFIX, DB_FILE_NAME_SUFFIX);
|
||||
System.out.println("First database location: " + uncompressedDatabaseFile.getAbsolutePath());
|
||||
Assert.assertTrue(uncompressedDatabaseFile.exists());
|
||||
|
||||
try (FullDataSourceV2Repo uncompressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile))
|
||||
{
|
||||
FullDataSourceV2 uncompressedDataSource = FullDataSourceV2.createEmpty(0);
|
||||
|
||||
int mappingEntryId1 = uncompressedDataSource.mapping.addIfNotPresentAndGetId(new TestBiomeWrapper("1"), new TestBlockStateWrapper());
|
||||
int mappingEntryId2 = uncompressedDataSource.mapping.addIfNotPresentAndGetId(new TestBiomeWrapper("2"), new TestBlockStateWrapper());
|
||||
uncompressedDataSource.mapping.addIfNotPresentAndGetId(new TestBiomeWrapper("3"), new TestBlockStateWrapper());
|
||||
|
||||
uncompressedDataSource.setSingleColumn(
|
||||
new LongArrayList(new long[]{
|
||||
FullDataPointUtil.encode(mappingEntryId1, 1, 0, (byte) 15, (byte) 15),
|
||||
FullDataPointUtil.encode(mappingEntryId2, 1, 1, (byte) 15, (byte) 15),
|
||||
}),
|
||||
0, 0,
|
||||
EDhApiWorldGenerationStep.FEATURES,
|
||||
EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS
|
||||
);
|
||||
|
||||
uncompressedRepo.save(FullDataSourceV2DTO.CreateFromDataSource(uncompressedDataSource, EDhApiDataCompressionMode.UNCOMPRESSED));
|
||||
|
||||
checksum1 = uncompressedRepo.getByKey(0L).dataChecksum;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
File uncompressedDatabaseFile = File.createTempFile(DB_FILE_NAME_PREFIX, DB_FILE_NAME_SUFFIX);
|
||||
System.out.println("Second database location: " + uncompressedDatabaseFile.getAbsolutePath());
|
||||
Assert.assertTrue(uncompressedDatabaseFile.exists());
|
||||
|
||||
try (FullDataSourceV2Repo uncompressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile))
|
||||
{
|
||||
FullDataSourceV2 uncompressedDataSource = FullDataSourceV2.createEmpty(0);
|
||||
|
||||
int mappingEntryId1 = uncompressedDataSource.mapping.addIfNotPresentAndGetId(new TestBiomeWrapper("1"), new TestBlockStateWrapper());
|
||||
int mappingEntryId2 = uncompressedDataSource.mapping.addIfNotPresentAndGetId(new TestBiomeWrapper("2"), new TestBlockStateWrapper());
|
||||
|
||||
uncompressedDataSource.setSingleColumn(
|
||||
new LongArrayList(new long[]{
|
||||
FullDataPointUtil.encode(mappingEntryId1, 1, 0, (byte) 15, (byte) 15),
|
||||
FullDataPointUtil.encode(mappingEntryId2, 1, 1, (byte) 15, (byte) 15),
|
||||
}),
|
||||
0, 0,
|
||||
EDhApiWorldGenerationStep.FEATURES,
|
||||
EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS
|
||||
);
|
||||
|
||||
uncompressedRepo.save(FullDataSourceV2DTO.CreateFromDataSource(uncompressedDataSource, EDhApiDataCompressionMode.UNCOMPRESSED));
|
||||
|
||||
checksum2 = uncompressedRepo.getByKey(0L).dataChecksum;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertEquals(checksum1, checksum2);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
Assert.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestBiomeWrapper implements IBiomeWrapper
|
||||
{
|
||||
private final String name;
|
||||
private TestBiomeWrapper(String nameSuffix)
|
||||
{
|
||||
this.name = "distanthorizons:testbiome" + nameSuffix;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
@Override
|
||||
public String getSerialString()
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
@Override
|
||||
public Object getWrappedMcObject()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestBlockStateWrapper implements IBlockStateWrapper
|
||||
{
|
||||
@Override
|
||||
public String getSerialString()
|
||||
{
|
||||
return "BLOCKSTATE";
|
||||
}
|
||||
@Override
|
||||
public int getOpacity()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public int getLightEmission()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public byte getMaterialId()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public boolean isBeaconBlock()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean isBeaconTintBlock()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean isBeaconBaseBlock()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public Color getMapColor()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public Color getBeaconTintColor()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public boolean isAir()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean isSolid()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean isLiquid()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public Object getWrappedMcObject()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||