Compare commits

...

16 Commits

Author SHA1 Message Date
s809 b2f98bdf63 Add tests 2024-09-26 17:39:05 +05:00
James Seibel fd0a4c55ac Change FullDataSourceDTO checksum/hash logic to hopefully be more consistent 2024-09-25 21:57:49 -05:00
James Seibel b0aa211464 Fix default generic rendering instanced mode 2024-09-25 18:44:39 -05:00
James Seibel 14e7918ea0 fix readme logo filepaths 2024-09-22 17:25:14 -05:00
James Seibel 524ea77ce5 Update readme logos 2024-09-22 17:23:35 -05:00
James Seibel 9f763bcd6a Add new logos 2024-09-22 17:18:33 -05:00
James Seibel 166875283c Fix cloud debug colors 2024-09-22 16:53:27 -05:00
James Seibel abb264bdf0 Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core 2024-09-22 16:32:21 -05:00
James Seibel 70aa5724a9 Closes !68 (Allow toggling generic obj instanced rendering via config) 2024-09-22 16:32:10 -05:00
James Seibel 520d37f410 Add generic obj cube count to F3 menu 2024-09-22 16:31:06 -05:00
s809 29c2756e75 Merge branch 'feature/lan-support' 2024-09-23 00:46:30 +05:00
s809 6c278ea3b1 LAN multiplayer kinda works 2024-09-23 00:02:31 +05:00
s809 b18460b825 Abstract away serverside parts of world & level 2024-09-22 03:28:53 +05:00
James Seibel d0b50d9633 Add Sqlite library validation to Initializer 2024-09-21 11:45:44 -05:00
James Seibel c051b3584f Replace "jdbc:sqlite" with a constant 2024-09-21 11:43:58 -05:00
James Seibel d4cad8f718 Fix config file handler corruption due to reading/writing concurrently 2024-09-20 07:29:32 -05:00
71 changed files with 1430 additions and 897 deletions
+1
View File
@@ -0,0 +1 @@
Distant Horizons logos © 2024 by Pankakes are licensed under CC BY-SA 4.0
+2 -2
View File
@@ -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
+44
View File
@@ -0,0 +1,44 @@
# Distant Horizons brand guidelines
To keep our look consistent and recognizable, weve 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.
| ![Distant Horizons primary logo](./PNG/Distant-Horizons.png) | ![Distant Horizons Core logo](./PNG/Distant-Horizons-Core.png) | ![Distant Horizons API logo](./PNG/Distant-Horizons-API.png) |
|--|--|--|
|Primary &nbsp; &nbsp; &nbsp; &nbsp; [.png](/PNG/Distant-Horizons.png) &nbsp; &nbsp; &nbsp; &nbsp; [.svg](./SVG/Distant-Horizons-Logo.svg)| Core &nbsp; &nbsp; &nbsp; &nbsp; [.png](/PNG/Distant-Horizons-Core.png) &nbsp; &nbsp; &nbsp; &nbsp; [.svg](./SVG/Distant-Horizons-Core.svg) | API &nbsp; &nbsp; &nbsp; &nbsp; [.png](./PNG/Distant-Horizons-API.png) &nbsp; &nbsp; &nbsp; &nbsp; [.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.*
<!-- | ![Distant Horizons primary mark](./PNG/Distant-Horizons-Mark.png) | ![Distant Horizons Core mark](./PNG/Distant-Horizons-Core-Mark.png) | ![Distant Horizons API mark](./PNG/Distant-Horizons-API-Mark.png) |
|--|--|--|
|Primary &nbsp; &nbsp; &nbsp; &nbsp; [.png](/PNG/Distant-Horizons-Mark.png.png) &nbsp; &nbsp; &nbsp; &nbsp; [.svg](./SVG/Distant-Horizons-Mark.svg.svg)| Core &nbsp; &nbsp; &nbsp; &nbsp; [.png](/PNG/Distant-Horizons-Core-Mark.png.png) &nbsp; &nbsp; &nbsp; &nbsp; [.svg](./SVG/Distant-Horizons-Core-Mark.svg.svg) | API &nbsp; &nbsp; &nbsp; &nbsp; [.png](./PNG/Distant-Horizons-API-Mark.png.png) &nbsp; &nbsp; &nbsp; &nbsp; [.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 &nbsp; &nbsp; &nbsp; &nbsp; [.png](/PNG/Distant-Horizons-Mark.png.png) &nbsp; &nbsp; &nbsp; &nbsp; [.svg](./SVG/Distant-Horizons-Mark.svg.svg)| Core &nbsp; &nbsp; &nbsp; &nbsp; [.png](/PNG/Distant-Horizons-Core-Mark.png.png) &nbsp; &nbsp; &nbsp; &nbsp; [.svg](./SVG/Distant-Horizons-Core-Mark.svg.svg) | API &nbsp; &nbsp; &nbsp; &nbsp; [.png](./PNG/Distant-Horizons-API-Mark.png.png) &nbsp; &nbsp; &nbsp; &nbsp; [.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.*
| ![Distant Horizons logo spacing](./Guidelines_images//Distant-Horizons-Logo-Spacing2.png) | ![Distant Horizons mark spacing](./Guidelines_images//Distant-Horizons-Mark-Spacing2.png) |
|--|--|
___
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
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

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 &lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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 &lt;Path&gt;"><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 &lt;Path&gt;"><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 &lt;Path&gt;"><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 &lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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

File diff suppressed because one or more lines are too long

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 &lt;Path&gt;"><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 &lt;Path&gt;"><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 &lt;Path&gt;"><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 &lt;Path&gt;"><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 &lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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="&lt;Path&gt;"><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

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

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
Binary file not shown.

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":
Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

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;
}
}
}