Compare commits

...

110 Commits

Author SHA1 Message Date
Spottedleaf
a6cf977c5f Set version to 0.2.0-beta.5 2024-12-01 16:00:29 -08:00
Spottedleaf
8ed23bc8f3 Fix several off-by-one errors in view distance calculations
1. For NearbyPlayers, we need to be using the view distance, and
   not the load distance (which is +1 of the view distance).
2. Correctly clamp tick distance to view distance. Since
   load distance is +1 of view distance, we need to subtract
   one from the load distance when clamping.

Additionally, add checks inside ViewDistances to ensure that
the inputs are in range to catch future errors.
2024-12-01 15:30:33 -08:00
Spottedleaf
7034995878 Clamp simulation distance
Values above MAX_VIEW_DISTANCE do not make sense to configure,
as it is clamped to the load view distance.
2024-12-01 15:29:49 -08:00
Spottedleaf
ca3d776562 Adjust constant collision shape determination
Our previous hack did not actually catch every case. For now,
it will only assume a constant collision shape of EMPTY for
air blocks.

Fixes https://github.com/PaperMC/Paper/issues/11697
2024-12-01 12:00:31 -08:00
Spottedleaf
1a077389c2 Force LazyEntityCollisionContext#getEntity() to delegate
By delegating when the entity is retrieved, we can correctly catch
cases where the collision method is inspecting some entity state.
2024-12-01 12:00:18 -08:00
Spottedleaf
49bcc60cf5 Only call config initialisers if reload succeeded 2024-12-01 11:31:16 -08:00
Spottedleaf
8b1f31ade8 Drop RegionFileStorage.RegionFileSizeException diff in MoonriseRegionFileIO
This diff should be implemented in Paper. Previously, the diff was
in the chunk system patch in Paper - but it should be in its own
separate patch.
2024-11-28 17:53:36 -08:00
Spottedleaf
4d88e04e3c Do not call modifyEntityTrackingRange on own range
The range is already modified, so this call will not do anything.
2024-11-27 12:20:50 -08:00
Spottedleaf
33889c3850 Replace Level/Entity randoms with ThreadUnsafeRandom
This avoids the expensive CAS logic at the expense of losing the
thread check.
2024-11-27 05:56:34 -08:00
Spottedleaf
37ac3003cb Replace SimpleRandom with (Simple)ThreadUnsafeRandom
ThreadUnsafeRandom is a random implementation that is
identical to LegacyRandomSource behaviourally, but
without the thread checks.

SimpleThreadUnsafeRandom is ThreadUnsafeRandom except with
its nextInt(int) function replaced with a faster
but more biased implementation when bound is very large.
2024-11-27 05:56:34 -08:00
Jason Penilla
28128d06c1 Disable immediate loading screen closure by default 2024-11-26 15:51:35 -07:00
Spottedleaf
6f9620787b Use https for YamlConfig repo
SSH isn't setup in actions so it cannot clone via SSH.
2024-11-25 09:30:49 -08:00
Spottedleaf
cbf6c118dc Mark install_deps.sh as executable 2024-11-25 09:25:32 -08:00
Spottedleaf
d44fa1f8aa Move YamlConfig to own project 2024-11-25 09:17:11 -08:00
Spottedleaf
3d9ae3f018 Fix non block ticking chunks not sending block/light updates
Needed to redirect the getTickingChunk call in
broadcastChangedChunks to use the chunk to send method.
2024-11-18 13:24:24 -08:00
Spottedleaf
d24f6c2874 Do not create unneccessary callback in ChunkTaskScheduler#scheduleChunkLoad
If the parameter has addTicket = false and onComplete = null,
then the loadCallback would do no work and as a result does
not need to be created.
2024-11-17 14:09:00 -08:00
Jason Penilla
f190cdd8cb Back to 0.2.0-SNAPSHOT 2024-11-17 11:31:37 -07:00
Jason Penilla
e7510eda16 0.2.0-beta.4 2024-11-17 10:49:05 -07:00
Jason Penilla
d9442c1492 Update lithium overrides 2024-11-17 10:28:38 -07:00
Jason Penilla
93eb2786f2 Update ServerExplosionMixin for NeoForge changes 2024-11-17 10:27:31 -07:00
Jason Penilla
1bef6823c5 Update lithium, NeoForge, loom 2024-11-16 17:35:27 -07:00
Jason Penilla
01152eec95 Update NeoForge lithium overrides 2024-11-16 17:34:44 -07:00
Spottedleaf
1e39f5370a Fix experimental minecart collisions on sloped rails
We are supposed to ignore some collisions on the sloped
rail.
2024-11-14 13:31:22 -08:00
Jason Penilla
ea50ba38ea Apply FerriteCore config overrides automatically
New versions of FC added this mechanism

closes #66
2024-11-05 16:12:37 -07:00
Spottedleaf
c00b9fcd7b Move back to 0.2.0-SNAPSHOT 2024-11-02 16:54:15 -07:00
Spottedleaf
bad5cae4d8 0.2.0-beta.3 2024-11-02 16:50:24 -07:00
Spottedleaf
e3b1502bb6 0.2.0-beta.9 2024-11-02 16:48:24 -07:00
Spottedleaf
54fc964987 Handle corrupt light data gracefully
First, if the light data is not marked as correct, we should not be
parsing it in the first place. This will eliminate errors from
parsing possibly different versioned light data.

Secondly, if parsing the light data throws an exception (from
the SWMRNibbleArray constructor), then we can simply mark
the returned chunk as having incorrect light data - rather than
propagating the exception and causing the chunk to be re-generated.
2024-11-02 16:14:05 -07:00
Spottedleaf
0cbc9aa1a1 Update README to reflect official nature of the patches 2024-10-31 12:05:59 -07:00
Jason Penilla
19e2136ae1 Use declaration order for state holder property iteration
Mostly an aesthetic change for serialization, should not have any impact on performance or correctness.
2024-10-27 18:32:04 -07:00
Jason Penilla
126cc03747 Back to 0.2.0-SNAPSHOT 2024-10-26 11:02:43 -07:00
Jason Penilla
ceb4936d9d 0.2.0-beta.2 2024-10-26 10:58:58 -07:00
Jason Penilla
3cb888e894 Update Fabric API and call ServerChunkEvents.CHUNK_GENERATE 2024-10-26 09:51:34 -07:00
Jason Penilla
7bedc1a7de Back to 0.2.0-SNAPSHOT 2024-10-24 11:54:52 -07:00
Jason Penilla
718f6e1369 0.2.0-beta.1 2024-10-24 11:48:32 -07:00
Spottedleaf
da9ab708a6 Import diff
The ChunkTaskScheduler one is actually needed in Paper to compile.
2024-10-24 11:39:02 -07:00
Spottedleaf
f22335f0b6 Move logic in anyPlayerCloseEnoughForSpawning overwrite to correct place
The internal function is responsible for the actual player iteration.
2024-10-24 10:00:23 -07:00
Spottedleaf
a3f2328000 Redirect chunk holder retrieval in ChunkMap#forEachSpawnCandidateChunk
The old chunk holder field is not maintained so this would
NPE.
2024-10-24 09:50:35 -07:00
Spottedleaf
529b9a44bb Add missing overwrite for DistanceManager#getTickingChunks 2024-10-24 09:37:16 -07:00
Spottedleaf
1e9a6504a1 Add world parameter to configAutoSaveInterval/configMaxAutoSavePerTick
Paper needs the world parameter to access the config values,
but in Moonrise we do not.
2024-10-24 08:29:57 -07:00
Spottedleaf
9c46dcbb94 Remove unused read/write methods on ChunkSystemSectionStorage
As with the last commit, these were only used in the legacy
region file I/O code and as such there is no reason to maintain
them.
2024-10-24 07:10:48 -07:00
Spottedleaf
9a1e04389a Adjust SectionStorageMixin to destroy hooks
In the legacy implementation of the region file I/O (before
the chunk system), these hooks were actually used. Now, they are not
and so there is no point in maintaining them.
2024-10-24 07:01:46 -07:00
Spottedleaf
29084d8e3f Properly sync on dimension data during ServerChunkCache#close
Note that this data was still saved, but we did not block until
it was finished.
2024-10-23 22:57:27 -07:00
Spottedleaf
41790ecf1a Implement overwrite for DistanceManager#getSpawnCandidateChunks 2024-10-23 22:53:34 -07:00
Spottedleaf
8af7bccdfd Adjust PlatformHooks#convertNBT to take TypeReference
DataFixTypes has a limited number of types, which will limit its
usage on Paper.
2024-10-23 22:10:41 -07:00
Spottedleaf
5f9b3571f8 Fix compile
Did not implement the other half of changes required in the last
commit...
2024-10-23 21:36:36 -07:00
Spottedleaf
9adfb2514d Change PlatformHooks#onChunkHolderTicketChange to take ChunkHolder
This makes compatibility on Paper easier to implement
as NewChunkHolder is not always available.
2024-10-23 21:33:54 -07:00
Jason Penilla
bf2cd1c571 fabric: fix crash when fabric-lifecycle-events-v1 not present 2024-10-23 18:23:42 -07:00
Jason Penilla
dad9a5c2eb Update to 1.21.3 and update cloth config api 2024-10-23 10:34:20 -07:00
Jason Penilla
a3acd46ee1 Fix explosion mixin
The field is a ServerLevel not a Level
2024-10-22 13:45:05 -07:00
Spottedleaf
3e8cb80336 Update to 1.21.2 2024-10-22 10:16:03 -07:00
Spottedleaf
5c3e713be7 Misc changes reviewing 1.21.2 update for Paper 2024-10-22 09:30:56 -07:00
Spottedleaf
284631c321 Use ThreadLocal in FlowingFluidMixin#COLLISION_OCCLUSION_CACHE
While the cache is safe to access from multiple threads, doing
so may incur false/true cache line sharing and reduce the
performance of the cache.
2024-10-22 09:30:54 -07:00
Spottedleaf
666c4cb1a3 Update to 1.21.2-rc1 2024-10-22 09:30:12 -07:00
Spottedleaf
19b523eecd Add async chunk writing
The RegionFile IO scheduler is now capable of taking in
a Completable<CompoundTag> for the chunk data instead of
CompoundTag. This allows writes to be scheduled without
the write value immediately.
2024-10-22 09:28:49 -07:00
Spottedleaf
4a748778dc Update to 1.21.2-pre1
For collisions, need to check out the new applyEffectsFromBlocks
function and see if there are any improvements we can make.

For the chunk system, we need to implement async chunk saving.
The current async unload structure will not work, as it is designed
to only handle 1 pending save at any given time.
2024-10-22 09:28:49 -07:00
Jason Penilla
a70073ae3e Setup issue templates (#53)
* Setup issue templates

* try to change order
2024-10-22 09:21:13 -07:00
Jason Penilla
6724814c02 Apply Lithium overrides on NeoForge (#57)
* Apply Lithium overrides on NeoForge

closes #56

* Update readme

* Sort overrides
2024-10-22 09:20:40 -07:00
Jason Penilla
f32a08738e Adjust min/max section optimizations (#55)
* Change min/max section optimisations

* Correctly init dimension type

We need to initialise the field as early as possible in the constructor
to avoid problems.

Also, do not cache min/max section in EntityLookup. Note that mods
that implement worlds with variable heights will not work still,
as the entity slices expect a fixed height.

* Cache calculated height values

---------

Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
2024-10-17 18:02:22 -07:00
Jason Penilla
56e48ed069 Back to 0.1.0-SNAPSHOT 2024-10-15 13:04:47 -07:00
Jason Penilla
9da99576a6 0.1.0-beta.6 2024-10-15 12:56:32 -07:00
Jason Penilla
dfbe1bcf8b Fix Mixin application failure in production
Work around https://github.com/FabricMC/tiny-remapper/issues/137
2024-10-15 12:45:22 -07:00
Jason Penilla
ae29196221 Back to 0.1.0-SNAPSHOT 2024-10-14 19:45:01 -07:00
Jason Penilla
f1eb61cc51 0.1.0-beta.5 2024-10-14 19:17:38 -07:00
Jason Penilla
58c933938f fabric: Call ServerChunkEvents.CHUNK_LOAD/CHUNK_UNLOAD, add and call FabricHooks.ON_EXPLOSION event (#52)
fixes #16
related to #45
2024-10-14 19:06:58 -07:00
Jason Penilla
1dc3cb5f14 Improve LeafProfiler output formatting (#28)
make it readable
2024-10-14 18:03:04 -07:00
Jason Penilla
2acfc6a68e Add missing require = 3 to util_threading_detector PalettedContainer constructor injection, and simplify fast_palette PalettedContainer constructor injection. 2024-10-02 10:54:52 -07:00
Jason Penilla
c22538c364 Back to 0.1.0-SNAPSHOT 2024-09-30 19:22:12 -07:00
Jason Penilla
e244c60375 0.1.0-beta.4 2024-09-30 19:10:12 -07:00
Jason Penilla
65253d5199 Re-fix dev-time conflict with lithium height overrides
See cfa80c44 and 2acd5cc2
2024-09-30 19:00:00 -07:00
Jason Penilla
6e82b034b7 Fix typo in previous commit
Also for some reason the issue resolved by cfa80c44 seems to be back...
2024-09-30 18:52:01 -07:00
Jason Penilla
a7cd78e63b Disable lithium mixin.util.entity_movement_tracking mixins, see #33 2024-09-30 18:43:25 -07:00
Jason Penilla
05ba7066d9 Add NeoForge/Mojang names for redirects in NaturalSpawnerMixin 2024-09-30 10:17:28 -07:00
Spottedleaf
340ac4e8f5 Fix incorrect fluid pushing velocity
Forgot to re-assign the flow vector variable with the scaled
flow.
2024-09-30 04:39:05 -07:00
Spottedleaf
a4770aca2b Fix performance regression in Entity#updateFluidHeightAndDoFluidPushing
The upper bounds from Vanilla are exclusive, but we were treating
them as inclusive which changes behavior and increases the number
of blocks read.
2024-09-30 04:39:05 -07:00
Spottedleaf
a8d4ce526b Optimise block/chunk reading in Entity#updateFluidHeightAndDoFluidPushing
Like other block reading code, a lot of time gets spent retrieving
the chunk and retrieving the section which we can avoid.
2024-09-30 04:39:05 -07:00
Spottedleaf
2acd5cc213 Optimise Level#isOutsideBuildHeight
We can cache the min/max build height to avoid the indirection
required to compute them.
2024-09-30 04:39:05 -07:00
Spottedleaf
07dce0ffe6 Change ShapesMixin#or to perform unoptimised intermediate joins
We can delay the shape optimisation to the final operation, which
should provide a perform boost for merging many shapes.
2024-09-30 04:39:05 -07:00
Spottedleaf
c2e2f0b9f2 Optimise countEntries for low size SimpleBitStorage
We can use a simple array lookup table by palette id when the
number of palette entries is low. This eliminates the need for
the map lookup on each entry.
2024-09-30 04:39:05 -07:00
Spottedleaf
1374ea34ee Replace ticking block coordinate set with ShortList
We can store a position (ranging from 0-4095) into a short,
so we can use ShortList to cut the memory usage in half.
2024-09-30 04:39:05 -07:00
Spottedleaf
fef4872a3a Do not calculate block counts on the client
It appears that this call is very expensive. We instead substitute
an exact check with setting the special colliding blocks to whether
the palette initially contains a special colliding block, and after
on block updating by whether any special colliding block was set.
2024-09-30 04:39:04 -07:00
Spottedleaf
a9678c76eb Fix missing range scale for passenger entities in entity tracker 2024-09-30 04:39:04 -07:00
Spottedleaf
bafa195546 Ensure relative block position methods return an immutable instance
If the relative distance is 0, then we would return the current
instance - which may be a mutable instance. However, Vanilla overrides
the relative function to return an immutable instance even when
the distance is zero. To ensure that this behavior is kept, we return
an immutable instance when the distance is zero.
2024-09-30 04:39:04 -07:00
Spottedleaf
04c6e6c01a Track broadcast chunks separately from player ticking chunks
Not all broadcast chunks are ticking in the new chunk system,
so we need to track them separately to ensure updates take place
in non-simulation range chunks.
2024-09-30 04:39:04 -07:00
Spottedleaf
7351872730 Avoid performing biome lookup for mob cost when entity has no cost
Most mob types do not have a cost with any biomes, so there is
no point in performing the lookup.
2024-09-30 04:39:04 -07:00
Spottedleaf
77b977ac17 Reduce chunk tick iteration count
By only iterating over chunks that are both ticking AND near players
or marked for ticking, we can reduce the number of chunks to iterate
over in higher SD settings.
2024-09-30 04:39:04 -07:00
Spottedleaf
f0d122f358 Optimise MobCounts implementation
Use of a map for category->count is inefficient as the key
is an enum. We can use a simple int array, which reduces
the get() and compute() calls to an array access.
2024-09-30 02:29:22 -07:00
Spottedleaf
aa240eb16d Modify isInWall to use same block retrieval as CollisionUtil 2024-09-30 02:25:24 -07:00
Spottedleaf
f9c99d1e32 Avoid streams for block retrieval in Entity#move
Profiling shows that the streams alone account for half of the time
for this specific check (inside fire/lava).
2024-09-30 02:25:24 -07:00
Spottedleaf
3fa0ff67a7 Optimise checkInsideBlocks
Use the same block retrieving algorithm as CollisionUtil
2024-09-30 02:25:24 -07:00
Spottedleaf
f7d9e5b422 Optimise findSupportingBlock to be like CollisionUtil
CollisionUtil's block iteration supports using the special
colliding blocks, as well as a superior (but more verbose)
chunk iteration.
2024-09-30 02:25:24 -07:00
Spottedleaf
5d1975154b Add dev override for max view distance 2024-09-30 02:25:24 -07:00
Spottedleaf
d21fa9f48c Use SimpleRandom for chunk tick shuffling 2024-09-30 02:25:24 -07:00
Spottedleaf
1c150afe83 Add and use reference counted ChunkData
ChunkData is stored directly on chunk holders, entity slices,
and on entities. Currently, this allows access to
NearbyPlayers$TrackedChunk without performing a map lookup.
2024-09-30 02:25:24 -07:00
Spottedleaf
d9988c86f4 Add direct lookup by chunk for NearbyPlayers
This simplifies the lookup code by reducing the number of "random"
accesses to 1 (get() call) assuming that the directByChunk array
is cached.
2024-09-30 02:25:24 -07:00
Jason Penilla
0958439e54 Improve mod compat for ShapesMixin (#36)
Use an inject instead of overwrite to avoid mixin conflicts - obviously this will still disregard any changes made by other
mixins, but at least it won't conflict immediately. This is useful because some library mods mixin here when only the content
mod actually needs the change.

Fixes incompatibility with CreativeCore but not LittleTiles
2024-09-29 18:32:04 -07:00
Jason Penilla
8d9d6488e8 Improve mod compat for EntityTickListMixin (#35)
Replaces an overwrite with a cancellable inject at head.

Fixes compat with mod AllTheLeaks
2024-09-29 18:23:17 -07:00
Jason Penilla
8ba83bc9c2 Adjust fluid state cache init for better compatibility (#31)
Some mods rely on data initialized in their fluid subclass constructor for the methods we call, which won't be available at the superclass constructor return.

Injecting into the registry add is a little ugly, but should be fine.

Fixes compat with Immersive Engineering.
2024-09-29 18:18:02 -07:00
Jason Penilla
0c60a6ac08 Update dependencies
Newer NeoForge has moved their coremods from JS to Java, which should slightly improve startup times (as the JS engine won't load without another mod that uses it), making this update relevant for development
2024-09-28 12:20:46 -07:00
Jason Penilla
d404dbc6c5 Fix entity cramming (#32)
We want to count non-passengers, not passengers
2024-09-24 15:29:11 -07:00
Jason Penilla
8d456890f1 Back to 0.1.0-SNAPSHOT 2024-09-17 16:58:55 -07:00
Jason Penilla
23eddfe918 0.1.0-beta.3 2024-09-15 13:53:16 -07:00
Jason Penilla
55ff406372 Update readme and mod metadata for Radium changes (#18) 2024-09-15 13:47:10 -07:00
Jason Penilla
3e25a2f4aa Update publishing metadata 2024-09-15 13:40:43 -07:00
Jason Penilla
cfa80c4488 Raise priority of collisions LevelMixin to apply after Lithium
Lithium replaces some of the height-related methods used by WorldUtil to return fields initialized in their constructor hook. We need to apply afterward to have them return valid data.

Given the application-order sensitive nature of the issue, it only presented in-dev.
2024-09-15 13:36:29 -07:00
Jason Penilla
453b635ef2 Fix explosion conflict with lithium 2024-09-15 12:43:13 -07:00
Jason Penilla
e07e4fdcc4 Reorder lithium overrides 2024-09-15 12:42:44 -07:00
Jason Penilla
81bb9701da Reformat FMJ to match editorconfig settings 2024-09-15 12:41:20 -07:00
Jason Penilla
d0a7f9af62 Add readme badges (#23) 2024-09-14 18:58:52 -07:00
Jason Penilla
fe7bcfc56a Back to 0.1.0-SNAPSHOT 2024-09-07 18:01:51 -07:00
174 changed files with 4015 additions and 3222 deletions

View File

@@ -0,0 +1,53 @@
name: Mod Incompatibility
description: Report an incompatibility with another mod.
title: '[Incompatibility: <NeoForge/Fabric/NeoForge & Fabric>]: Mod Name'
labels:
- mod-incompatibility
- needs-triage
body:
- type: input
id: mc_ver
attributes:
label: Minecraft Version
placeholder: e.g. 1.21.1 or 1.21.2
validations:
required: true
- type: input
id: moonrise_ver
attributes:
label: Moonrise Version
placeholder: e.g. 0.1.0-beta.6 or Git commit hash
validations:
required: true
- type: dropdown
id: mod_loader
attributes:
label: Mod Loader
description: >-
The mod loader(s) this conflict happens with. When selecting 'Both' it
is expected you will submit logs for both as well. Be sure to update the
issue title.
options:
- NeoForge
- Fabric
- Both (NeoForge and Fabric)
validations:
required: true
- type: textarea
id: logs_crashes
attributes:
label: Logs and Crash Reports
description: >-
Include links to all relevant logs and crash reports (prefer using
https://gist.github.com/ or https://mclo.gs/). In case this is not
applicable fill in 'N/A' (it is expected you will provide context
below).
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context
description: Provide any relevant context, including version of the conflicting mod.
validations:
required: true

55
.github/ISSUE_TEMPLATE/b_bug.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Bug Report
description: Report a bug or Vanilla behavior parity issue.
title: '[Bug: <NeoForge/Fabric/NeoForge & Fabric>]: Short description'
labels:
- bug
- needs-triage
body:
- type: input
id: mc_ver
attributes:
label: Minecraft Version
placeholder: e.g. 1.21.1 or 1.21.2
validations:
required: true
- type: input
id: moonrise_ver
attributes:
label: Moonrise Version
placeholder: e.g. 0.1.0-beta.6 or Git commit hash
validations:
required: true
- type: dropdown
id: mod_loader
attributes:
label: Mod Loader
description: >-
The mod loader(s) this bug happens with. When selecting 'Both' it
is expected you will submit logs for both as well. Be sure to update the
issue title.
options:
- NeoForge
- Fabric
- Both (NeoForge and Fabric)
validations:
required: true
- type: textarea
id: logs_crashes
attributes:
label: Logs and Crash Reports
description: >-
Include links to all relevant logs and crash reports (prefer using
https://gist.github.com/ or https://mclo.gs/). In case this is not
applicable fill in 'N/A' (it is expected you will provide context
below).
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context
description: >-
Provide any relevant context, including expected vs. actual behavior,
and reproduction steps.
validations:
required: true

View File

@@ -0,0 +1,65 @@
name: Performance Problem
description: Report a performance problem that doesn't fall under the bug category.
title: '[Performance: <NeoForge/Fabric/NeoForge & Fabric>]: Short description'
labels:
- performance
- needs-triage
body:
- type: input
id: mc_ver
attributes:
label: Minecraft Version
placeholder: e.g. 1.21.1, 1.21.2
validations:
required: true
- type: input
id: moonrise_ver
attributes:
label: Moonrise Version
placeholder: e.g. 0.1.0-beta.6 or Git commit hash
validations:
required: true
- type: dropdown
id: mod_loader
attributes:
label: Mod Loader
description: >-
The mod loader(s) this bug happens with. When selecting 'Both' it is
expected you have tested both as well. Be sure to update the issue
title.
options:
- NeoForge
- Fabric
- Both (NeoForge and Fabric)
validations:
required: true
- type: textarea
id: logs_crashes
attributes:
label: Logs and Crash Reports
description: >-
Include links to all relevant logs and crash reports (prefer using
https://gist.github.com/ or https://mclo.gs/). In case this is not
applicable fill in 'N/A' (it is expected you will provide context
below).
validations:
required: true
- type: textarea
id: profiler
attributes:
label: Profiler Data
description: >-
Provide profiler data showing the problem, from Spark or other tools. In
case this is not applicable fill in 'N/A' (it is expected you will
provide context below).
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context
description: >-
Provide any relevant context, including reproduction steps, world data,
videos, etc.
validations:
required: true

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Discord
url: https://discord.gg/tuinity
about: If you are unsure about something or have general questions, ask in our Discord before opening an issue.

34
.github/ISSUE_TEMPLATE/d_feature.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Feature Request
description: Request a new feature.
title: '[Feature Request: <NeoForge/Fabric/NeoForge & Fabric>]: Short description'
labels:
- enhancement
- needs-triage
body:
- type: input
id: mc_ver
attributes:
label: Minecraft Version
placeholder: e.g. 1.21.1, 1.21.2
validations:
required: true
- type: dropdown
id: mod_loader
attributes:
label: Mod Loader
description: The mod loader(s) this feature request is relevant for.
options:
- NeoForge
- Fabric
- Both (NeoForge and Fabric)
validations:
required: true
- type: textarea
id: context
attributes:
label: Description
description: >-
Describe the feature request in detail, including alternatives
considered.
validations:
required: true

View File

@@ -31,8 +31,8 @@ jobs:
key: ${{ runner.os }}-project-local-gradle-caches-${{ hashFiles('**/libs.versions.toml', '**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-project-local-gradle-caches-
- name: "setup concurrentutil"
run: ./installConcurrentUtil.sh
- name: "setup dependencies"
run: ./install_deps.sh
- name: "execute gradle build"
run: ./gradlew build
- name: Determine Snapshot Status

3
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "ConcurrentUtil"]
path = ConcurrentUtil
url = https://github.com/Spottedleaf/ConcurrentUtil.git
[submodule "YamlConfig"]
path = YamlConfig
url = https://github.com/Spottedleaf/YamlConfig.git

View File

@@ -1,5 +1,10 @@
Moonrise
==
[![Modrinth](https://img.shields.io/badge/Modrinth-gray?logo=modrinth)](https://modrinth.com/mod/moonrise-opt)
[![CurseForge](https://img.shields.io/badge/CurseForge-gray?logo=curseforge)](https://www.curseforge.com/minecraft/mc-mods/moonrise)
[![Release](https://img.shields.io/github/v/release/Tuinity/Moonrise?include_prereleases)](https://github.com/Tuinity/Moonrise/releases)
[![License](https://img.shields.io/github/license/Tuinity/Moonrise)](LICENSE.md)
Fabric/NeoForge mod for optimising performance of the integrated (singleplayer/LAN) and dedicated server.
@@ -7,20 +12,20 @@ Fabric/NeoForge mod for optimising performance of the integrated (singleplayer/L
Moonrise aims to optimise the game *without changing Vanilla behavior*. If you find that there are changes to Vanilla behavior,
please open an issue.
Moonrise ports several important [Paper](https://github.com/PaperMC/Paper/)
Moonrise is an official port of several important [Paper](https://github.com/PaperMC/Paper/)
patches. Listed below are notable patches:
- [Starlight](https://github.com/PaperMC/Starlight/)
- Chunk system rewrite
- Collision optimisations
- Entity tracker optimisations
- Random ticking optimisations
- [Starlight](https://github.com/PaperMC/Starlight/)
## Known Compatibility Issues
| Mod | Status |
|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Lithium | <details><summary>✅ compatible</summary>Lithium optimises many of the same parts of the game as Moonrise, for example the chunk system. Moonrise will automatically disable conflicting parts of Lithium. This mechanism needs to be manually validated for each Moonrise and Lithium release.</details> |
| FerriteCore | <details><summary>📝 requires config changes</summary>In `config/ferritecore-mixin.toml`:<br/>Set `replaceNeighborLookup` and `replacePropertyMap` to `false`</details> |
| C2ME | <details><summary>❌ incompatible</summary>C2ME is based around modifications to the chunk system, which Moonrise replaces wholesale. This makes them fundamentally incompatible.</details> |
| FerriteCore | <details><summary>✅ compatible</summary>FerriteCore optimises some of the same parts of the game as Moonrise. Moonrise will automatically disable conflicting parts of FerriteCore. This mechanism needs to be manually validated for each Moonrise and FerriteCore release.</details> |
| C2ME | <details><summary>❌ incompatible</summary>C2ME is based around modifications to the chunk system, which Moonrise replaces wholesale. This makes them fundamentally incompatible.</details> |
## Configuration
Moonrise provides documented configuration options for tuning the chunk system and enabling bugfixes in the config file `$mcdir$/config/moonrise.yml`.

1
YamlConfig Submodule

Submodule YamlConfig added at 67552e7707

View File

@@ -23,6 +23,7 @@ dependencies {
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
api("ca.spottedleaf:concurrentutil:${rootProject.concurrentutil_version}")
api("ca.spottedleaf:yamlconfig:${rootProject.yamlconfig_version}")
api("org.yaml:snakeyaml:${rootProject.snakeyaml_version}")
modImplementation "me.shedaniel.cloth:cloth-config:${rootProject.cloth_version}"
@@ -48,6 +49,7 @@ allprojects {
mavenLocal {
mavenContent {
includeModule("ca.spottedleaf", "concurrentutil")
includeModule("ca.spottedleaf", "yamlconfig")
}
}
maven {
@@ -98,7 +100,8 @@ subprojects {
}
loom.runs.all {
ideConfigGenerated true
// property "mixin.debug", "true"
property "mixin.debug", "true"
property "Moonrise.MaxViewDistance", "128"
}
plugins.apply("me.modmuss50.mod-publish-plugin")
@@ -112,18 +115,45 @@ subprojects {
}
changelog = providers.environmentVariable("RELEASE_NOTES")
List<String> supportedMcVersions = rootProject.supported_minecraft_versions.split(',')
modrinth {
projectId = "KOHu7RCS"
accessToken = providers.environmentVariable("MODRINTH_TOKEN")
minecraftVersions = [rootProject.minecraft_version]
minecraftVersions = supportedMcVersions
}
curseforge {
projectId = "1096335"
accessToken = providers.environmentVariable("CURSEFORGE_TOKEN")
minecraftVersions = [rootProject.minecraft_version]
minecraftVersions = supportedMcVersions
}
}
// Setup a run with lithium for compatibility testing
sourceSets.create("lithium")
configurations.create("lithium")
loom {
createRemapConfigurations(sourceSets.lithium)
runs {
register("lithiumClient") {
client()
property "mixin.debug", "true"
}
}
}
tasks.named("runLithiumClient", net.fabricmc.loom.task.RunGameTask.class) {
getClasspath().from(configurations.modRuntimeClasspathLithiumMapped)
}
dependencies {
String coordinates = "maven.modrinth:lithium:"
if (getProject().name == "Moonrise-NeoForge") {
coordinates += rootProject.neo_lithium_version
} else {
coordinates += rootProject.fabric_lithium_version
}
modLithiumRuntimeOnly coordinates
}
}
loom.runs.all {

View File

@@ -18,13 +18,15 @@ dependencies {
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
libs("ca.spottedleaf:concurrentutil:${rootProject.concurrentutil_version}")
libs("ca.spottedleaf:yamlconfig:${rootProject.yamlconfig_version}")
libs("org.yaml:snakeyaml:${rootProject.snakeyaml_version}")
modImplementation "me.shedaniel.cloth:cloth-config-fabric:${rootProject.cloth_version}"
include "me.shedaniel.cloth:cloth-config-fabric:${rootProject.cloth_version}"
modImplementation "com.terraformersmc:modmenu:11.0.1"
modImplementation "com.terraformersmc:modmenu:${rootProject.modmenu_version}"
modImplementation fabricApiLibs.command.api.v2
modImplementation fabricApiLibs.lifecycle.events.v1
include fabricApiLibs.command.api.v2
include fabricApiLibs.base
}
@@ -42,6 +44,7 @@ shadowJar {
destinationDirectory = layout.buildDirectory.dir("libs")
configurations = [project.configurations.shadow]
relocate 'ca.spottedleaf.concurrentutil', 'ca.spottedleaf.moonrise.libs.ca.spottedleaf.concurrentutil'
relocate 'ca.spottedleaf.yamlconfig', 'ca.spottedleaf.moonrise.libs.ca.spottedleaf.yamlconfig'
relocate 'org.yaml.snakeyaml', 'ca.spottedleaf.moonrise.libs.org.yaml.snakeyaml'
}
@@ -59,26 +62,7 @@ publishMods {
incompatible(
"not-enough-crashes",
"starlight",
"c2me-fabric"
"c2me"
)
}
}
// Setup a run with lithium for compatibility testing
sourceSets.create("lithium")
configurations.create("lithium")
loom {
createRemapConfigurations(sourceSets.lithium)
runs {
register("lithiumClient") {
client()
property "mixin.debug", "true"
}
}
}
tasks.named("runLithiumClient", net.fabricmc.loom.task.RunGameTask.class) {
getClasspath().from(configurations.modRuntimeClasspathLithiumMapped)
}
dependencies {
modLithiumRuntimeOnly "maven.modrinth:lithium:${rootProject.lithium_version}"
}

View File

@@ -2,29 +2,38 @@ package ca.spottedleaf.moonrise.fabric;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.util.ConfigHolder;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.DataFixer;
import com.mojang.serialization.Dynamic;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.GenerationChunkHolder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.status.ChunkStatusTasks;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import java.util.List;
import java.util.function.Predicate;
public final class FabricHooks implements PlatformHooks {
private static final boolean HAS_FABRIC_LIFECYCLE_EVENTS = FabricLoader.getInstance().isModLoaded("fabric-lifecycle-events-v1");
@Override
public String getBrand() {
return "Moonrise";
@@ -42,16 +51,6 @@ public final class FabricHooks implements PlatformHooks {
};
}
@Override
public void onExplosion(final Level world, final Explosion explosion, final List<Entity> possiblyAffecting, final double diameter) {
}
@Override
public Vec3 modifyExplosionKnockback(final Level world, final Explosion explosion, final Entity entity, final Vec3 original) {
return original;
}
@Override
public boolean hasCurrentlyLoadingChunk() {
return false;
@@ -69,7 +68,12 @@ public final class FabricHooks implements PlatformHooks {
@Override
public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) {
if (HAS_FABRIC_LIFECYCLE_EVENTS) {
ServerChunkEvents.CHUNK_LOAD.invoker().onChunkLoad((ServerLevel)newChunk.getLevel(), newChunk);
if (!(original instanceof ImposterProtoChunk)) {
ServerChunkEvents.CHUNK_GENERATE.invoker().onChunkGenerate((ServerLevel)newChunk.getLevel(), newChunk);
}
}
}
@Override
@@ -78,17 +82,19 @@ public final class FabricHooks implements PlatformHooks {
}
@Override
public void onChunkHolderTicketChange(final ServerLevel world, final NewChunkHolder holder, final int oldLevel, final int newLevel) {
public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) {
}
@Override
public void chunkUnloadFromWorld(final LevelChunk chunk) {
if (HAS_FABRIC_LIFECYCLE_EVENTS) {
ServerChunkEvents.CHUNK_UNLOAD.invoker().onChunkUnload((ServerLevel)chunk.getLevel(), chunk);
}
}
@Override
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final CompoundTag data) {
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) {
}
@@ -160,12 +166,12 @@ public final class FabricHooks implements PlatformHooks {
}
@Override
public long configAutoSaveInterval() {
public long configAutoSaveInterval(final ServerLevel world) {
return ConfigHolder.getConfig().chunkSaving.autoSaveInterval.getTimeTicks();
}
@Override
public int configMaxAutoSavePerTick() {
public int configMaxAutoSavePerTick(final ServerLevel world) {
return ConfigHolder.getConfig().chunkSaving.maxAutoSaveChunksPerTick;
}
@@ -173,4 +179,47 @@ public final class FabricHooks implements PlatformHooks {
public boolean configFixMC159283() {
return ConfigHolder.getConfig().bugFixes.fixMC159283;
}
@Override
public boolean forceNoSave(final ChunkAccess chunk) {
return false;
}
@Override
public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
final int fromVersion, final int toVersion) {
return (CompoundTag)dataFixer.update(
type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
).getValue();
}
@Override
public boolean hasMainChunkLoadHook() {
return false;
}
@Override
public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) {
}
@Override
public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities) {
return entities;
}
@Override
public void unloadEntity(final Entity entity) {
entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK);
}
@Override
public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities());
}
@Override
public int modifyEntityTrackingRange(final Entity entity, final int currentRange) {
return currentRange;
}
}

View File

@@ -0,0 +1,183 @@
package ca.spottedleaf.moonrise.fabric.mixin.collisions;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import it.unimi.dsi.fastutil.objects.Object2DoubleMap;
import net.minecraft.core.BlockPos;
import net.minecraft.tags.TagKey;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(Entity.class)
abstract class EntityMixin {
@Shadow
public abstract boolean touchingUnloadedChunk();
@Shadow
public abstract AABB getBoundingBox();
@Shadow
@Deprecated
public abstract boolean isPushedByFluid();
@Shadow
private Level level;
@Shadow
@Deprecated
protected Object2DoubleMap<TagKey<Fluid>> fluidHeight;
@Shadow
public abstract Vec3 getDeltaMovement();
@Shadow
public abstract void setDeltaMovement(final Vec3 arg);
/**
* @reason Optimise the block reading in this function
* @author Spottedleaf
*/
@Overwrite
public boolean updateFluidHeightAndDoFluidPushing(final TagKey<Fluid> fluid, final double flowScale) {
if (this.touchingUnloadedChunk()) {
return false;
}
final AABB boundingBox = this.getBoundingBox().deflate(1.0E-3);
final Level world = this.level;
final int minSection = WorldUtil.getMinSection(world);
final int minBlockX = Mth.floor(boundingBox.minX);
final int minBlockY = Math.max((minSection << 4), Mth.floor(boundingBox.minY));
final int minBlockZ = Mth.floor(boundingBox.minZ);
// note: bounds are exclusive in Vanilla, so we subtract 1 - our loop expects bounds to be inclusive
final int maxBlockX = Mth.ceil(boundingBox.maxX) - 1;
final int maxBlockY = Math.min((WorldUtil.getMaxSection(world) << 4) | 15, Mth.ceil(boundingBox.maxY) - 1);
final int maxBlockZ = Mth.ceil(boundingBox.maxZ) - 1;
final boolean isPushable = this.isPushedByFluid();
final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
Vec3 pushVector = Vec3.ZERO;
double totalPushes = 0.0;
double maxHeightDiff = 0.0;
boolean inFluid = false;
final int minChunkX = minBlockX >> 4;
final int maxChunkX = maxBlockX >> 4;
final int minChunkY = minBlockY >> 4;
final int maxChunkY = maxBlockY >> 4;
final int minChunkZ = minBlockZ >> 4;
final int maxChunkZ = maxBlockZ >> 4;
final ChunkSource chunkSource = world.getChunkSource();
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
final LevelChunkSection[] sections = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, false).getSections();
// bound y
for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) {
final int sectionIdx = currChunkY - minSection;
if (sectionIdx < 0 || sectionIdx >= sections.length) {
continue;
}
final LevelChunkSection section = sections[sectionIdx];
if (section.hasOnlyAir()) {
// empty
continue;
}
final PalettedContainer<BlockState> blocks = section.states;
final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) : 0;
final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) : 15;
final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) : 0;
final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) : 15;
final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) : 0;
final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) : 15;
for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
final FluidState fluidState = blocks.get((currX) | (currZ << 4) | ((currY) << 8)).getFluidState();
if (fluidState.isEmpty() || !fluidState.is(fluid)) {
continue;
}
mutablePos.set(currX | (currChunkX << 4), currY | (currChunkY << 4), currZ | (currChunkZ << 4));
final double height = (double)((float)mutablePos.getY() + fluidState.getHeight(world, mutablePos));
final double diff = height - boundingBox.minY;
if (diff < 0.0) {
continue;
}
inFluid = true;
maxHeightDiff = Math.max(maxHeightDiff, diff);
if (!isPushable) {
continue;
}
++totalPushes;
final Vec3 flow = fluidState.getFlow(world, mutablePos);
if (diff < 0.4) {
pushVector = pushVector.add(flow.scale(diff));
} else {
pushVector = pushVector.add(flow);
}
}
}
}
}
}
}
this.fluidHeight.put(fluid, maxHeightDiff);
if (pushVector.lengthSqr() == 0.0) {
return inFluid;
}
// note: totalPushes != 0 as pushVector != 0
pushVector = pushVector.scale(1.0 / totalPushes);
final Vec3 currMovement = this.getDeltaMovement();
if (!((Entity)(Object)this instanceof Player)) {
pushVector = pushVector.normalize();
}
pushVector = pushVector.scale(flowScale);
if (Math.abs(currMovement.x) < 0.003 && Math.abs(currMovement.z) < 0.003 && pushVector.length() < 0.0045000000000000005) {
pushVector = pushVector.normalize().scale(0.0045000000000000005);
}
this.setDeltaMovement(currMovement.add(pushVector));
// note: inFluid = true here as pushVector != 0
return true;
}
}

View File

@@ -1,71 +1,76 @@
{
"schemaVersion": 1,
"id": "moonrise",
"version": "${version}",
"name": "Moonrise",
"description": "Optimisation mod for the dedicated and integrated server.",
"authors": [
"Spottedleaf"
],
"contact": {
"issues": "https://github.com/Tuinity/Moonrise/issues",
"sources": "https://github.com/Tuinity/Moonrise",
"discord": "https://discord.gg/tuinity",
"homepage": "https://www.curseforge.com/minecraft/mc-mods/moonrise"
},
"breaks": {
"notenoughcrashes": "*",
"starlight": "*",
"c2me": "*"
},
"license": "GPL-3.0-only",
"icon": "assets/moonrise/icon.png",
"environment": "*",
"entrypoints": {
"modmenu": [
"ca.spottedleaf.moonrise.fabric.MoonriseModMenuHook"
"schemaVersion": 1,
"id": "moonrise",
"version": "${version}",
"name": "Moonrise",
"description": "Optimisation mod for the dedicated and integrated server.",
"authors": [
"Spottedleaf"
],
"client": [
"ca.spottedleaf.moonrise.fabric.MoonriseFabricClient"
]
},
"mixins": [
"moonrise.mixins.json",
"moonrise-fabric.mixins.json"
],
"accessWidener": "moonrise.accesswidener",
"depends": {
"fabricloader": ">=${loader_version}",
"minecraft": ">=1.21 <=1.21.1",
"fabric-command-api-v2": "*"
},
"custom": {
"lithium:options": {
"mixin.collections.chunk_tickets": false,
"mixin.world.temperature_cache": false,
"mixin.block.flatten_states": false,
"mixin.math.fast_util": false,
"mixin.minimal_nonvanilla.collisions.empty_space": false,
"mixin.alloc.deep_passengers": false,
"mixin.math.fast_blockpos": false,
"mixin.shapes.blockstate_cache": false,
"mixin.world.block_entity_ticking": false,
"mixin.collections.entity_ticking": false,
"mixin.world.chunk_access": false,
"mixin.ai.poi": false,
"mixin.chunk.no_validation": false,
"mixin.minimal_nonvanilla.world.expiring_chunk_tickets": false,
"mixin.world.tick_scheduler": false,
"mixin.alloc.chunk_ticking": false,
"mixin.entity.replace_entitytype_predicates": false,
"mixin.util.block_tracking": false,
"mixin.shapes.specialized_shapes": false,
"mixin.shapes.optimized_matching": false,
"mixin.entity.collisions.intersection": false,
"mixin.entity.collisions.movement": false,
"mixin.entity.collisions.unpushable_cramming": false,
"mixin.chunk.entity_class_groups": false,
"mixin.alloc.entity_tracker": false
"contact": {
"issues": "https://github.com/Tuinity/Moonrise/issues",
"sources": "https://github.com/Tuinity/Moonrise",
"discord": "https://discord.gg/tuinity",
"homepage": "https://www.curseforge.com/minecraft/mc-mods/moonrise"
},
"breaks": {
"notenoughcrashes": "*",
"starlight": "*",
"c2me": "*"
},
"license": "GPL-3.0-only",
"icon": "assets/moonrise/icon.png",
"environment": "*",
"entrypoints": {
"modmenu": [
"ca.spottedleaf.moonrise.fabric.MoonriseModMenuHook"
],
"client": [
"ca.spottedleaf.moonrise.fabric.MoonriseFabricClient"
]
},
"mixins": [
"moonrise.mixins.json",
"moonrise-fabric.mixins.json"
],
"accessWidener": "moonrise.accesswidener",
"depends": {
"fabricloader": ">=${loader_version}",
"minecraft": ">1.21.1 <1.21.4",
"fabric-command-api-v2": "*"
},
"custom": {
"lithium:options": {
"mixin.ai.poi": false,
"mixin.alloc.deep_passengers": false,
"mixin.alloc.entity_tracker": false,
"mixin.block.flatten_states": false,
"mixin.chunk.entity_class_groups": false,
"mixin.chunk.no_validation": false,
"mixin.collections.chunk_tickets": false,
"mixin.collections.entity_ticking": false,
"mixin.entity.collisions.intersection": false,
"mixin.entity.collisions.movement": false,
"mixin.entity.collisions.unpushable_cramming": false,
"mixin.entity.replace_entitytype_predicates": false,
"mixin.math.fast_blockpos": false,
"mixin.math.fast_util": false,
"mixin.minimal_nonvanilla.collisions.empty_space": false,
"mixin.minimal_nonvanilla.world.expiring_chunk_tickets": false,
"mixin.shapes.blockstate_cache": false,
"mixin.shapes.optimized_matching": false,
"mixin.shapes.specialized_shapes": false,
"mixin.util.block_tracking": false,
"mixin.util.entity_movement_tracking": false,
"mixin.world.block_entity_ticking": false,
"mixin.world.chunk_access": false,
"mixin.world.explosions.block_raycast": false,
"mixin.world.temperature_cache": false,
"mixin.world.tick_scheduler": false
},
"ferritecore:disabled_options": [
"replaceNeighborLookup",
"replacePropertyMap"
]
}
}
}

View File

@@ -4,7 +4,8 @@
"mixins": [
"chunk_system.FabricDistanceManagerMixin",
"chunk_system.FabricMinecraftServerMixin",
"chunk_system.FabricServerLevelMixin"
"chunk_system.FabricServerLevelMixin",
"collisions.EntityMixin"
],
"client": [
"command.ClientSuggestionProviderMixin"

View File

@@ -3,14 +3,20 @@ org.gradle.jvmargs=-Xmx2G
org.gradle.daemon=false
# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=1.21.1
loader_version=0.16.5
neoforge_version=21.1.42
snakeyaml_version=2.2
minecraft_version=1.21.3
loader_version=0.16.7
supported_minecraft_versions=1.21.3
neoforge_version=21.3.31-beta
fabric_api_version=0.107.0+1.21.3
snakeyaml_version=2.3
concurrentutil_version=0.0.2-SNAPSHOT
cloth_version=15.0.128
lithium_version=mc1.21.1-0.13.0
yamlconfig_version=1.0.2-SNAPSHOT
cloth_version=16.0.141
modmenu_version=12.0.0-beta.1
# version ids from modrinth
fabric_lithium_version=QhCwdt4l
neo_lithium_version=wDD955sb
# Mod Properties
mod_version=0.1.0-beta.2
mod_version=0.2.0-beta.5
maven_group=ca.spottedleaf.moonrise
archives_base_name=moonrise

View File

@@ -2,5 +2,11 @@
set -eou pipefail
git submodule update --init --recursive
cd ConcurrentUtil
mvn install
cd ..
cd YamlConfig
mvn install

View File

@@ -22,6 +22,7 @@ dependencies {
neoForge "net.neoforged:neoforge:${rootProject.neoforge_version}"
shadow("ca.spottedleaf:concurrentutil:${rootProject.concurrentutil_version}")
shadow("ca.spottedleaf:yamlconfig:${rootProject.yamlconfig_version}")
shadow("org.yaml:snakeyaml:${rootProject.snakeyaml_version}")
forgeExtra("org.yaml:snakeyaml:${rootProject.snakeyaml_version}")
@@ -42,6 +43,7 @@ shadowJar {
destinationDirectory = layout.buildDirectory.dir("libs")
configurations = [project.configurations.shadow]
relocate 'ca.spottedleaf.concurrentutil', 'ca.spottedleaf.moonrise.libs.ca.spottedleaf.concurrentutil'
relocate 'ca.spottedleaf.yamlconfig', 'ca.spottedleaf.moonrise.libs.ca.spottedleaf.yamlconfig'
relocate 'org.yaml.snakeyaml', 'ca.spottedleaf.moonrise.libs.org.yaml.snakeyaml'
}
@@ -54,16 +56,14 @@ publishMods {
incompatible(
"notenoughcrashes",
"starlight-neoforge",
"canary",
"radium"
"canary"
)
}
curseforge {
incompatible(
"not-enough-crashes-forge",
"starlight-neoforge",
"canary",
"radium-reforged"
"canary"
)
}
}

View File

@@ -3,9 +3,13 @@ package ca.spottedleaf.moonrise.neoforge;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.util.ConfigHolder;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.DataFixer;
import com.mojang.serialization.Dynamic;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.GenerationChunkHolder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@@ -13,16 +17,16 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.EmptyBlockGetter;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.status.ChunkStatusTasks;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.neoforged.neoforge.common.CommonHooks;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.entity.PartEntity;
@@ -52,16 +56,6 @@ public final class NeoForgeHooks implements PlatformHooks {
};
}
@Override
public void onExplosion(final Level world, final Explosion explosion, final List<Entity> possiblyAffecting, final double diameter) {
EventHooks.onExplosionDetonate(world, explosion, possiblyAffecting, diameter);
}
@Override
public Vec3 modifyExplosionKnockback(final Level world, final Explosion explosion, final Entity entity, final Vec3 original) {
return EventHooks.getExplosionKnockback(world, explosion, entity, original);
}
@Override
public boolean hasCurrentlyLoadingChunk() {
return true;
@@ -88,10 +82,12 @@ public final class NeoForgeHooks implements PlatformHooks {
}
@Override
public void onChunkHolderTicketChange(final ServerLevel world, final NewChunkHolder holder, final int oldLevel, final int newLevel) {
public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) {
final ChunkPos pos = holder.getPos();
EventHooks.fireChunkTicketLevelUpdated(
world, CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ),
oldLevel, newLevel, holder.vanillaChunkHolder
world, CoordinateUtils.getChunkKey(pos.x, pos.z),
oldLevel, newLevel, holder
);
}
@@ -101,7 +97,7 @@ public final class NeoForgeHooks implements PlatformHooks {
}
@Override
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final CompoundTag data) {
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) {
NeoForge.EVENT_BUS.post(new ChunkDataEvent.Save(chunk, world, data));
}
@@ -193,12 +189,12 @@ public final class NeoForgeHooks implements PlatformHooks {
}
@Override
public long configAutoSaveInterval() {
public long configAutoSaveInterval(final ServerLevel world) {
return ConfigHolder.getConfig().chunkSaving.autoSaveInterval.getTimeTicks();
}
@Override
public int configMaxAutoSavePerTick() {
public int configMaxAutoSavePerTick(final ServerLevel world) {
return ConfigHolder.getConfig().chunkSaving.maxAutoSaveChunksPerTick;
}
@@ -206,4 +202,47 @@ public final class NeoForgeHooks implements PlatformHooks {
public boolean configFixMC159283() {
return ConfigHolder.getConfig().bugFixes.fixMC159283;
}
@Override
public boolean forceNoSave(final ChunkAccess chunk) {
return false;
}
@Override
public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
final int fromVersion, final int toVersion) {
return (CompoundTag)dataFixer.update(
type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
).getValue();
}
@Override
public boolean hasMainChunkLoadHook() {
return true;
}
@Override
public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) {
NeoForge.EVENT_BUS.post(new ChunkDataEvent.Load(chunk, chunkData));
}
@Override
public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities) {
return entities;
}
@Override
public void unloadEntity(final Entity entity) {
entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK);
}
@Override
public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities());
}
@Override
public int modifyEntityTrackingRange(final Entity entity, final int currentRange) {
return currentRange;
}
}

View File

@@ -1,8 +1,8 @@
package ca.spottedleaf.moonrise.neoforge.mixin.chunk_system;
import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemDistanceManager;
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.server.level.ChunkLevel;
import net.minecraft.server.level.DistanceManager;
@@ -15,13 +15,13 @@ import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@Mixin(DistanceManager.class)
abstract class NeoForgeDistanceManagerMixin implements ChunkSystemDistanceManager {
@Unique
private final ConcurrentLong2ReferenceChainedHashTable<SortedArraySet<Ticket<?>>> mtSafeForcedTickets = new ConcurrentLong2ReferenceChainedHashTable<>();
@Shadow
@Final
private Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> forcedTickets;
/**
* @reason Route to new chunk system
@@ -34,7 +34,7 @@ abstract class NeoForgeDistanceManagerMixin implements ChunkSystemDistanceManage
if (forceTicks) {
final Ticket<T> forceTicket = new Ticket<>(type, level, identifier, forceTicks);
this.mtSafeForcedTickets.compute(pos.toLong(), (final long keyInMap, final SortedArraySet<Ticket<?>> valueInMap) -> {
this.forcedTickets.compute(pos.toLong(), (final Long keyInMap, final SortedArraySet<Ticket<?>> valueInMap) -> {
final SortedArraySet<Ticket<?>> ret;
if (valueInMap != null) {
ret = valueInMap;
@@ -42,7 +42,11 @@ abstract class NeoForgeDistanceManagerMixin implements ChunkSystemDistanceManage
ret = SortedArraySet.create(4);
}
ret.add(forceTicket);
if (ret.add(forceTicket)) {
((ChunkTickServerLevel)NeoForgeDistanceManagerMixin.this.moonrise$getChunkMap().level).moonrise$addPlayerTickingRequest(
CoordinateUtils.getChunkX(keyInMap.longValue()), CoordinateUtils.getChunkZ(keyInMap.longValue())
);
}
return ret;
});
@@ -60,8 +64,12 @@ abstract class NeoForgeDistanceManagerMixin implements ChunkSystemDistanceManage
if (forceTicks) {
final Ticket<T> forceTicket = new Ticket<>(type, level, identifier, forceTicks);
this.mtSafeForcedTickets.computeIfPresent(pos.toLong(), (final long keyInMap, final SortedArraySet<Ticket<?>> valueInMap) -> {
valueInMap.remove(forceTicket);
this.forcedTickets.computeIfPresent(pos.toLong(), (final Long keyInMap, final SortedArraySet<Ticket<?>> valueInMap) -> {
if (valueInMap.remove(forceTicket)) {
((ChunkTickServerLevel)NeoForgeDistanceManagerMixin.this.moonrise$getChunkMap().level).moonrise$removePlayerTickingRequest(
CoordinateUtils.getChunkX(keyInMap.longValue()), CoordinateUtils.getChunkZ(keyInMap.longValue())
);
}
return valueInMap.isEmpty() ? null : valueInMap;
});
@@ -69,11 +77,11 @@ abstract class NeoForgeDistanceManagerMixin implements ChunkSystemDistanceManage
}
/**
* @reason Make this API thread-safe
* @reason Only use containsKey, as we fix the leak with this impl
* @author Spottedleaf
*/
@Overwrite
public boolean shouldForceTicks(final long chunkPos) {
return this.mtSafeForcedTickets.containsKey(chunkPos);
return this.forcedTickets.containsKey(chunkPos);
}
}

View File

@@ -0,0 +1,199 @@
package ca.spottedleaf.moonrise.neoforge.mixin.collisions;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.neoforge.patches.collisions.FluidPushCalculation;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceArrayMap;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.neoforged.neoforge.common.extensions.IEntityExtension;
import net.neoforged.neoforge.fluids.FluidType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.Iterator;
@Mixin(Entity.class)
abstract class EntityMixin implements IEntityExtension {
@Shadow
public abstract boolean touchingUnloadedChunk();
@Shadow
public abstract AABB getBoundingBox();
@Shadow
private Level level;
@Shadow
protected abstract void setFluidTypeHeight(final FluidType type, final double height);
@Shadow
public abstract Vec3 getDeltaMovement();
@Shadow
public abstract void setDeltaMovement(final Vec3 arg);
/**
* @reason Optimise the block reading in this function
* @author Spottedleaf
*/
@Overwrite
public void updateFluidHeightAndDoFluidPushing() {
if (this.touchingUnloadedChunk()) {
return;
}
final AABB boundingBox = this.getBoundingBox().deflate(1.0E-3);
final Level world = this.level;
final int minSection = WorldUtil.getMinSection(world);
final int minBlockX = Mth.floor(boundingBox.minX);
final int minBlockY = Math.max((minSection << 4), Mth.floor(boundingBox.minY));
final int minBlockZ = Mth.floor(boundingBox.minZ);
// note: bounds are exclusive in Vanilla, so we subtract 1
final int maxBlockX = Mth.ceil(boundingBox.maxX) - 1;
final int maxBlockY = Math.min((WorldUtil.getMaxSection(world) << 4) | 15, Mth.ceil(boundingBox.maxY) - 1);
final int maxBlockZ = Mth.ceil(boundingBox.maxZ) - 1;
final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
final int minChunkX = minBlockX >> 4;
final int maxChunkX = maxBlockX >> 4;
final int minChunkY = minBlockY >> 4;
final int maxChunkY = maxBlockY >> 4;
final int minChunkZ = minBlockZ >> 4;
final int maxChunkZ = maxBlockZ >> 4;
final ChunkSource chunkSource = world.getChunkSource();
final Reference2ReferenceArrayMap<FluidType, FluidPushCalculation> calculations = new Reference2ReferenceArrayMap<>();
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
final LevelChunkSection[] sections = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, false).getSections();
// bound y
for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) {
final int sectionIdx = currChunkY - minSection;
if (sectionIdx < 0 || sectionIdx >= sections.length) {
continue;
}
final LevelChunkSection section = sections[sectionIdx];
if (section.hasOnlyAir()) {
// empty
continue;
}
final PalettedContainer<BlockState> blocks = section.states;
final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) : 0;
final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) : 15;
final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) : 0;
final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) : 15;
final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) : 0;
final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) : 15;
for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
final FluidState fluidState = blocks.get((currX) | (currZ << 4) | ((currY) << 8)).getFluidState();
if (fluidState.isEmpty()) {
continue;
}
mutablePos.set(currX | (currChunkX << 4), currY | (currChunkY << 4), currZ | (currChunkZ << 4));
final FluidType type = fluidState.getFluidType();
// note: assume fluidState.isEmpty() == type.isAir()
final FluidPushCalculation calculation = calculations.computeIfAbsent(type, (final FluidType key) -> {
return new FluidPushCalculation();
});
final double height = (double)((float)mutablePos.getY() + fluidState.getHeight(world, mutablePos));
final double diff = height - boundingBox.minY;
if (diff < 0.0) {
continue;
}
calculation.maxHeightDiff = Math.max(calculation.maxHeightDiff, diff);
if (calculation.isPushed == Boolean.FALSE) {
continue;
} else if (calculation.isPushed == null) {
final boolean isPushed = this.isPushedByFluid(type);
calculation.isPushed = Boolean.valueOf(isPushed);
if (!isPushed) {
continue;
}
}
++calculation.totalPushes;
final Vec3 flow = fluidState.getFlow(world, mutablePos);
if (diff < 0.4) {
calculation.pushVector = calculation.pushVector.add(flow.scale(diff));
} else {
calculation.pushVector = calculation.pushVector.add(flow);
}
}
}
}
}
}
}
if (calculations.isEmpty()) {
return;
}
for (final Iterator<Reference2ReferenceMap.Entry<FluidType, FluidPushCalculation>> iterator = calculations.reference2ReferenceEntrySet().fastIterator(); iterator.hasNext();) {
final Reference2ReferenceMap.Entry<FluidType, FluidPushCalculation> entry = iterator.next();
final FluidType type = entry.getKey();
final FluidPushCalculation calculation = entry.getValue();
this.setFluidTypeHeight(type, calculation.maxHeightDiff);
Vec3 pushVector = calculation.pushVector;
if (pushVector.lengthSqr() == 0.0) {
continue;
}
// note: totalPushes != 0 as pushVector != 0
pushVector = pushVector.scale(1.0 / calculation.totalPushes);
final Vec3 currMovement = this.getDeltaMovement();
if (!((Entity)(Object)this instanceof Player)) {
pushVector = pushVector.normalize();
}
pushVector = pushVector.scale(this.getFluidMotionScale(type));
if (Math.abs(currMovement.x) < 0.003 && Math.abs(currMovement.z) < 0.003 && pushVector.length() < 0.0045000000000000005) {
pushVector = pushVector.normalize().scale(0.0045000000000000005);
}
this.setDeltaMovement(currMovement.add(pushVector));
}
}
}

View File

@@ -0,0 +1,12 @@
package ca.spottedleaf.moonrise.neoforge.patches.collisions;
import net.minecraft.world.phys.Vec3;
public final class FluidPushCalculation {
public Vec3 pushVector = Vec3.ZERO;
public double totalPushes = 0.0;
public double maxHeightDiff = 0.0;
public Boolean isPushed = null;
}

View File

@@ -13,6 +13,10 @@ displayURL = "https://github.com/Tuinity/Moonrise"
authors = "Spottedleaf"
description = "Optimisation mod for the dedicated and integrated server."
displayTest = "IGNORE_ALL_VERSION"
"ferritecore:disabled_options" = [
"replaceNeighborLookup",
"replacePropertyMap"
]
[[dependencies.moonrise]]
modId = "neoforge"
@@ -24,7 +28,7 @@ side = "BOTH"
[[dependencies.moonrise]]
modId = "minecraft"
type = "required"
versionRange = "[1.21,1.21.2)"
versionRange = "(1.21.1,1.21.4)"
ordering = "NONE"
side = "BOTH"
@@ -36,20 +40,11 @@ type = "incompatible"
modId = "starlight"
type = "incompatible"
[[dependencies.moonrise]]
modId = "lithium"
type = "incompatible"
[[dependencies.moonrise]]
# unofficial lithium port
modId = "canary"
type = "incompatible"
[[dependencies.moonrise]]
# unofficial lithium port
modId = "radium"
type = "incompatible"
[[dependencies.moonrise]]
modId = "c2me"
type = "incompatible"
@@ -59,3 +54,31 @@ config = "moonrise.mixins.json"
[[mixins]]
config = "moonrise-neoforge.mixins.json"
["lithium:options"]
"mixin.ai.poi" = false
"mixin.alloc.deep_passengers" = false
"mixin.alloc.entity_tracker" = false
"mixin.block.flatten_states" = false
"mixin.chunk.entity_class_groups" = false
"mixin.chunk.no_validation" = false
"mixin.collections.chunk_tickets" = false
"mixin.collections.entity_ticking" = false
"mixin.entity.collisions.intersection" = false
"mixin.entity.collisions.movement" = false
"mixin.entity.collisions.unpushable_cramming" = false
"mixin.entity.replace_entitytype_predicates" = false
"mixin.math.fast_blockpos" = false
"mixin.math.fast_util" = false
"mixin.minimal_nonvanilla.collisions.empty_space" = false
"mixin.minimal_nonvanilla.world.expiring_chunk_tickets" = false
"mixin.shapes.blockstate_cache" = false
"mixin.shapes.optimized_matching" = false
"mixin.shapes.specialized_shapes" = false
"mixin.util.block_tracking" = false
"mixin.util.entity_movement_tracking" = false
"mixin.world.block_entity_ticking" = false
"mixin.world.chunk_access" = false
"mixin.world.explosions.block_raycast" = false
"mixin.world.temperature_cache" = false
"mixin.world.tick_scheduler" = false

View File

@@ -4,7 +4,8 @@
"mixins": [
"chunk_system.NeoForgeDistanceManagerMixin",
"chunk_system.NeoForgeMinecraftServerMixin",
"chunk_system.NeoForgeServerLevelMixin"
"chunk_system.NeoForgeServerLevelMixin",
"collisions.EntityMixin"
],
"client": [
"command.ClientCommandSourceStackMixin"

View File

@@ -24,7 +24,7 @@ pluginManagement {
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
id("xyz.jpenilla.quiet-architectury-loom") version "1.7.295" apply false
id("xyz.jpenilla.quiet-architectury-loom") version "1.7.300" apply false
id 'com.gradleup.shadow' version '8.3.0' apply false
}
@@ -37,7 +37,7 @@ dependencyResolutionManagement {
}
versionCatalogs {
create("fabricApiLibs") {
from("net.fabricmc.fabric-api:fabric-api-catalog:0.103.0+1.21.1")
from("net.fabricmc.fabric-api:fabric-api-catalog:${fabric_api_version}")
}
}
}

View File

@@ -1,23 +1,25 @@
package ca.spottedleaf.moonrise.common;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.DataFixer;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.GenerationChunkHolder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.datafix.DataFixTypes;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import java.util.List;
import java.util.ServiceLoader;
import java.util.function.Predicate;
@@ -33,10 +35,6 @@ public interface PlatformHooks {
public Predicate<BlockState> maybeHasLightEmission();
public void onExplosion(final Level world, final Explosion explosion, final List<Entity> possiblyAffecting, final double diameter);
public Vec3 modifyExplosionKnockback(final Level world, final Explosion explosion, final Entity entity, final Vec3 original);
public boolean hasCurrentlyLoadingChunk();
public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder);
@@ -47,11 +45,11 @@ public interface PlatformHooks {
public boolean allowAsyncTicketUpdates();
public void onChunkHolderTicketChange(final ServerLevel world, final NewChunkHolder holder, final int oldLevel, final int newLevel);
public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel);
public void chunkUnloadFromWorld(final LevelChunk chunk);
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final CompoundTag data);
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data);
public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player);
@@ -82,12 +80,30 @@ public interface PlatformHooks {
public int configPlayerMaxConcurrentGens();
public long configAutoSaveInterval();
public long configAutoSaveInterval(final ServerLevel world);
public int configMaxAutoSavePerTick();
public int configMaxAutoSavePerTick(final ServerLevel world);
public boolean configFixMC159283();
// support for CB chunk mustNotSave
public boolean forceNoSave(final ChunkAccess chunk);
public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
final int fromVersion, final int toVersion);
public boolean hasMainChunkLoadHook();
public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData);
public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities);
public void unloadEntity(final Entity entity);
public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk);
public int modifyEntityTrackingRange(final Entity entity, final int currentRange);
public static final class Holder {
private Holder() {
}

View File

@@ -1,7 +0,0 @@
package ca.spottedleaf.moonrise.common.config;
public interface InitialiseHook {
public void initialise();
}

View File

@@ -1,11 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter;
import java.lang.reflect.Type;
public abstract class TypeAdapter<T, S> {
public abstract T deserialize(final TypeAdapterRegistry registry, final Object input, final Type type);
public abstract S serialize(final TypeAdapterRegistry registry, final T value, final Type type);
}

View File

@@ -1,307 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter;
import ca.spottedleaf.moonrise.common.config.InitialiseHook;
import ca.spottedleaf.moonrise.common.config.adapter.collection.CollectionTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.collection.ListTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.collection.SortedMapTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.BooleanTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.ByteTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.DoubleTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.FloatTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.IntegerTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.LongTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.ShortTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.StringTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.type.BigDecimalTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.type.BigIntegerTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.type.DurationTypeAdapter;
import ca.spottedleaf.moonrise.common.config.annotation.Adaptable;
import ca.spottedleaf.moonrise.common.config.annotation.Serializable;
import ca.spottedleaf.moonrise.common.config.type.Duration;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public final class TypeAdapterRegistry {
private final Map<Class<?>, TypeAdapter<?, ?>> adapters = new HashMap<>();
{
this.adapters.put(boolean.class, BooleanTypeAdapter.INSTANCE);
this.adapters.put(byte.class, ByteTypeAdapter.INSTANCE);
this.adapters.put(short.class, ShortTypeAdapter.INSTANCE);
this.adapters.put(int.class, IntegerTypeAdapter.INSTANCE);
this.adapters.put(long.class, LongTypeAdapter.INSTANCE);
this.adapters.put(float.class, FloatTypeAdapter.INSTANCE);
this.adapters.put(double.class, DoubleTypeAdapter.INSTANCE);
this.adapters.put(Boolean.class, BooleanTypeAdapter.INSTANCE);
this.adapters.put(Byte.class, ByteTypeAdapter.INSTANCE);
this.adapters.put(Short.class, ShortTypeAdapter.INSTANCE);
this.adapters.put(Integer.class, IntegerTypeAdapter.INSTANCE);
this.adapters.put(Long.class, LongTypeAdapter.INSTANCE);
this.adapters.put(Float.class, FloatTypeAdapter.INSTANCE);
this.adapters.put(Double.class, DoubleTypeAdapter.INSTANCE);
this.adapters.put(String.class, StringTypeAdapter.INSTANCE);
this.adapters.put(Collection.class, CollectionTypeAdapter.INSTANCE);
this.adapters.put(List.class, ListTypeAdapter.INSTANCE);
this.adapters.put(Map.class, SortedMapTypeAdapter.SORTED_CASE_INSENSITIVE);
this.adapters.put(LinkedHashMap.class, SortedMapTypeAdapter.SORTED_CASE_INSENSITIVE);
this.adapters.put(BigInteger.class, BigIntegerTypeAdapter.INSTANCE);
this.adapters.put(BigDecimal.class, BigDecimalTypeAdapter.INSTANCE);
this.adapters.put(Duration.class, DurationTypeAdapter.INSTANCE);
}
public TypeAdapter<?, ?> putAdapter(final Class<?> clazz, final TypeAdapter<?, ?> adapter) {
return this.adapters.put(clazz, adapter);
}
public TypeAdapter<?, ?> getAdapter(final Class<?> clazz) {
return this.adapters.get(clazz);
}
public Object deserialize(final Object input, final Type type) {
TypeAdapter<?, ?> adapter = null;
if (type instanceof Class<?> clazz) {
adapter = this.adapters.get(clazz);
}
if (adapter == null && (type instanceof ParameterizedType parameterizedType)) {
adapter = this.adapters.get((Class<?>)parameterizedType.getRawType());
}
if (adapter == null) {
throw new IllegalArgumentException("No adapter for " + input.getClass() + " with type " + type);
}
return ((TypeAdapter)adapter).deserialize(this, input, type);
}
public Object serialize(final Object input, final Type type) {
TypeAdapter<?, ?> adapter = null;
if (type instanceof Class<?> clazz) {
adapter = this.adapters.get(clazz);
}
if (adapter == null && (type instanceof ParameterizedType parameterizedType)) {
adapter = this.adapters.get((Class<?>)parameterizedType.getRawType());
}
if (adapter == null) {
adapter = this.adapters.get(input.getClass());
}
if (adapter == null) {
throw new IllegalArgumentException("No adapter for " + input.getClass() + " with type " + type);
}
return ((TypeAdapter)adapter).serialize(this, input, type);
}
public <T> TypeAdapter<T, Map<Object, Object>> makeAdapter(final Class<? extends T> clazz) throws Exception {
final TypeAdapter<T, Map<Object, Object>> ret = new AutoTypeAdapter<>(this, clazz);
this.putAdapter(clazz, ret);
return ret;
}
public <T> void callInitialisers(final T object) {
if (object == null) {
return;
}
final TypeAdapter<?, ?> adapter = this.getAdapter(object.getClass());
if (!(adapter instanceof AutoTypeAdapter<?> autoTypeAdapter)) {
return;
}
((AutoTypeAdapter<T>)autoTypeAdapter).callInitialisers(object);
}
private static final class AutoTypeAdapter<T> extends TypeAdapter<T, Map<Object, Object>> {
private final TypeAdapterRegistry registry;
private final Constructor<? extends T> constructor;
private final SerializableField[] fields;
public AutoTypeAdapter(final TypeAdapterRegistry registry, final Class<? extends T> clazz) throws Exception {
this.registry = registry;
this.constructor = clazz.getConstructor();
this.fields = findSerializableFields(registry, clazz);
}
private static TypeAdapter<?, ?> findOrMakeAdapter(final TypeAdapterRegistry registry, final Class<?> clazz) throws Exception {
final TypeAdapter<?, ?> ret = registry.getAdapter(clazz);
if (ret != null) {
return ret;
}
for (final Annotation annotation : clazz.getAnnotations()) {
if (annotation instanceof Adaptable adaptable) {
return registry.makeAdapter(clazz);
}
}
throw new IllegalArgumentException("No type adapter for " + clazz + " (Forgot @Adaptable?)");
}
private static String makeSerializedKey(final String input) {
final StringBuilder ret = new StringBuilder();
for (final char c : input.toCharArray()) {
if (!Character.isUpperCase(c)) {
ret.append(c);
continue;
}
ret.append('-');
ret.append(Character.toLowerCase(c));
}
return ret.toString();
}
private static record SerializableField(
Field field,
boolean required,
String comment,
TypeAdapter<?, ?> adapter,
boolean serialize,
String serializedKey
) {}
private static SerializableField[] findSerializableFields(final TypeAdapterRegistry registry, Class<?> clazz) throws Exception {
final List<SerializableField> ret = new ArrayList<>();
do {
for (final Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
for (final Annotation annotation : field.getAnnotations()) {
if (!(annotation instanceof Serializable serializable)) {
continue;
}
final TypeAdapter<?, ?> adapter;
if (serializable.adapter() != TypeAdapter.class) {
adapter = serializable.adapter().getConstructor().newInstance();
} else {
adapter = findOrMakeAdapter(registry, field.getType());
}
String serializedKey = serializable.serializedKey();
if (serializedKey.isEmpty()) {
serializedKey = makeSerializedKey(field.getName());
}
ret.add(new SerializableField(
field, serializable.required(), serializable.comment(), adapter,
serializable.serialize(), serializedKey
));
}
}
} while ((clazz = clazz.getSuperclass()) != Object.class);
ret.sort((final SerializableField c1, final SerializableField c2) -> {
return c1.serializedKey.compareTo(c2.serializedKey);
});
return ret.toArray(new SerializableField[0]);
}
@Override
public T deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (!(input instanceof Map<?,?> inputMap)) {
throw new IllegalArgumentException("Not a map type: " + input.getClass());
}
try {
final T ret = this.constructor.newInstance();
for (final SerializableField field : this.fields) {
final Object fieldValue = inputMap.get(field.serializedKey);
if (fieldValue == null) {
if (field.required) {
throw new IllegalArgumentException("Missing required field '" + field.serializedKey + "' in " + this.constructor.getDeclaringClass());
}
continue;
}
field.field.set(ret, field.adapter.deserialize(registry, fieldValue, field.field.getGenericType()));
}
return ret;
} catch (final Exception ex) {
throw new RuntimeException(ex);
}
}
@Override
public Map<Object, Object> serialize(final TypeAdapterRegistry registry, final T value, final Type type) {
final LinkedHashMap<Object, Object> ret = new LinkedHashMap<>();
for (final SerializableField field : this.fields) {
if (!field.serialize) {
continue;
}
final Object fieldValue;
try {
fieldValue = field.field.get(value);
} catch (final Exception ex) {
throw new RuntimeException(ex);
}
if (fieldValue != null) {
ret.put(
field.comment.isBlank() ? field.serializedKey : new CommentedData(field.comment, field.serializedKey),
((TypeAdapter)field.adapter).serialize(
registry, fieldValue, field.field.getGenericType()
)
);
}
}
return ret;
}
public void callInitialisers(final T value) {
for (final SerializableField field : this.fields) {
final Object fieldValue;
try {
fieldValue = field.field.get(value);
} catch (final Exception ex) {
throw new RuntimeException(ex);
}
if (fieldValue instanceof InitialiseHook initialiseHook) {
initialiseHook.initialise();
}
this.registry.callInitialisers(fieldValue);
}
}
}
public static final class CommentedData {
public final String comment;
public final Object data;
public CommentedData(final String comment, final Object data) {
this.comment = comment;
this.data = data;
}
}
}

View File

@@ -1,46 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.collection;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.StringTypeAdapter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public final class CollectionTypeAdapter extends TypeAdapter<Collection<Object>, List<Object>> {
public static final CollectionTypeAdapter INSTANCE = new CollectionTypeAdapter();
@Override
public Collection<Object> deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (!(type instanceof ParameterizedType parameterizedType)) {
throw new IllegalArgumentException("Collection field must specify generic type");
}
final Type elementType = parameterizedType.getActualTypeArguments()[0];
if (input instanceof Collection<?> collection) {
final List<Object> ret = new ArrayList<>(collection.size());
for (final Object v : collection) {
ret.add(registry.deserialize(v, elementType));
}
return ret;
}
throw new IllegalArgumentException("Not a collection type: " + input.getClass());
}
@Override
public List<Object> serialize(final TypeAdapterRegistry registry, final Collection<Object> value, final Type type) {
final List<Object> ret = new ArrayList<>(value.size());
final Type elementType = type instanceof ParameterizedType parameterizedType ? parameterizedType.getActualTypeArguments()[0] : null;
for (final Object v : value) {
ret.add(registry.serialize(v, elementType == null ? v.getClass() : elementType));
}
return ret;
}
}

View File

@@ -1,45 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.collection;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public final class ListTypeAdapter extends TypeAdapter<List<Object>, List<Object>> {
public static final ListTypeAdapter INSTANCE = new ListTypeAdapter();
@Override
public List<Object> deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (!(type instanceof ParameterizedType parameterizedType)) {
throw new IllegalArgumentException("Collection field must specify generic type");
}
final Type elementType = parameterizedType.getActualTypeArguments()[0];
if (input instanceof Collection<?> collection) {
final List<Object> ret = new ArrayList<>(collection.size());
for (final Object v : collection) {
ret.add(registry.deserialize(v, elementType));
}
return ret;
}
throw new IllegalArgumentException("Not a collection type: " + input.getClass());
}
@Override
public List<Object> serialize(final TypeAdapterRegistry registry, final List<Object> value, final Type type) {
final List<Object> ret = new ArrayList<>(value.size());
final Type elementType = type instanceof ParameterizedType parameterizedType ? parameterizedType.getActualTypeArguments()[0] : null;
for (final Object v : value) {
ret.add(registry.serialize(v, elementType));
}
return ret;
}
}

View File

@@ -1,59 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.collection;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
public final class SortedMapTypeAdapter extends TypeAdapter<Map<String, Object>, Map<String, Object>> {
public static final SortedMapTypeAdapter SORTED_CASE_INSENSITIVE = new SortedMapTypeAdapter(String.CASE_INSENSITIVE_ORDER);
public static final SortedMapTypeAdapter SORTED_CASE_SENSITIVE = new SortedMapTypeAdapter(null);
private final Comparator<String> keyComparator;
public SortedMapTypeAdapter(final Comparator<String> keyComparator) {
this.keyComparator = keyComparator;
}
@Override
public Map<String, Object> deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (!(type instanceof ParameterizedType parameterizedType)) {
throw new IllegalArgumentException("Collection field must specify generic type");
}
final Type valueType = parameterizedType.getActualTypeArguments()[1];
if (input instanceof Map<?,?> inputMap) {
final Map<String, Object> castedInput = (Map<String, Object>)inputMap;
final TreeMap<String, Object> ret = new TreeMap<>(this.keyComparator);
for (final Map.Entry<String, Object> entry : castedInput.entrySet()) {
ret.put(entry.getKey(), registry.deserialize(entry.getValue(), valueType));
}
// transform to linked so that get() is O(1)
return new LinkedHashMap<>(ret);
}
throw new IllegalArgumentException("Not a map type: " + input.getClass());
}
@Override
public Map<String, Object> serialize(final TypeAdapterRegistry registry, final Map<String, Object> value, final Type type) {
final TreeMap<String, Object> ret = new TreeMap<>(this.keyComparator);
final Type valueType = type instanceof ParameterizedType parameterizedType ? parameterizedType.getActualTypeArguments()[1] : null;
for (final Map.Entry<String, Object> entry : value.entrySet()) {
ret.put(entry.getKey(), registry.serialize(entry.getValue(), valueType));
}
// transform to linked so that get() is O(1)
return new LinkedHashMap<>(ret);
}
}

View File

@@ -1,47 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.collection;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
public final class UnsortedMapTypeAdapter extends TypeAdapter<Map<String, Object>, Map<String, Object>> {
public static final UnsortedMapTypeAdapter INSTANCE = new UnsortedMapTypeAdapter();
@Override
public Map<String, Object> deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (!(type instanceof ParameterizedType parameterizedType)) {
throw new IllegalArgumentException("Collection field must specify generic type");
}
final Type valueType = parameterizedType.getActualTypeArguments()[1];
if (input instanceof Map<?,?> inputMap) {
final Map<String, Object> castedInput = (Map<String, Object>)inputMap;
final LinkedHashMap<String, Object> ret = new LinkedHashMap<>();
for (final Map.Entry<String, Object> entry : castedInput.entrySet()) {
ret.put(entry.getKey(), registry.deserialize(entry.getValue(), valueType));
}
return ret;
}
throw new IllegalArgumentException("Not a map type: " + input.getClass());
}
@Override
public Map<String, Object> serialize(final TypeAdapterRegistry registry, final Map<String, Object> value, final Type type) {
final LinkedHashMap<String, Object> ret = new LinkedHashMap<>();
final Type valueType = type instanceof ParameterizedType parameterizedType ? parameterizedType.getActualTypeArguments()[1] : null;
for (final Map.Entry<String, Object> entry : value.entrySet()) {
ret.put(entry.getKey(), registry.serialize(entry.getValue(), valueType));
}
return ret;
}
}

View File

@@ -1,33 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
public final class BooleanTypeAdapter extends TypeAdapter<Boolean, Boolean> {
public static final BooleanTypeAdapter INSTANCE = new BooleanTypeAdapter();
@Override
public Boolean deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Boolean ret) {
return ret;
}
if (input instanceof String str) {
if (str.equalsIgnoreCase("false")) {
return Boolean.FALSE;
}
if (str.equalsIgnoreCase("true")) {
return Boolean.TRUE;
}
throw new IllegalArgumentException("Not a boolean: " + str);
}
throw new IllegalArgumentException("Not a boolean type: " + input.getClass());
}
@Override
public Boolean serialize(final TypeAdapterRegistry registry, final Boolean value, final Type type) {
return value;
}
}

View File

@@ -1,36 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
import java.math.BigInteger;
public final class ByteTypeAdapter extends TypeAdapter<Byte, Byte> {
public static final ByteTypeAdapter INSTANCE = new ByteTypeAdapter();
private static Byte cast(final Object original, final long value) {
if (value < (long)Byte.MIN_VALUE || value > (long)Byte.MAX_VALUE) {
throw new IllegalArgumentException("Byte value is out of range: " + original.toString());
}
return Byte.valueOf((byte)value);
}
@Override
public Byte deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
// note: silently discard floating point significand
return cast(input, number instanceof BigInteger bigInteger ? bigInteger.longValueExact() : number.longValue());
}
if (input instanceof String string) {
return cast(input, (long)Double.parseDouble(string));
}
throw new IllegalArgumentException("Not a byte type: " + input.getClass());
}
@Override
public Byte serialize(final TypeAdapterRegistry registry, final Byte value, final Type type) {
return value;
}
}

View File

@@ -1,27 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
public final class DoubleTypeAdapter extends TypeAdapter<Double, Double> {
public static final DoubleTypeAdapter INSTANCE = new DoubleTypeAdapter();
@Override
public Double deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
return Double.valueOf(number.doubleValue());
}
if (input instanceof String string) {
return Double.valueOf(Double.parseDouble(string));
}
throw new IllegalArgumentException("Not a byte type: " + input.getClass());
}
@Override
public Double serialize(final TypeAdapterRegistry registry, final Double value, final Type type) {
return value;
}
}

View File

@@ -1,35 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
public final class FloatTypeAdapter extends TypeAdapter<Float, Float> {
public static final FloatTypeAdapter INSTANCE = new FloatTypeAdapter();
private static Float cast(final Object original, final double value) {
if (value < -(double)Float.MAX_VALUE || value > (double)Float.MAX_VALUE) {
throw new IllegalArgumentException("Byte value is out of range: " + original.toString());
}
// note: silently ignore precision loss
return Float.valueOf((float)value);
}
@Override
public Float deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
return cast(input, number.doubleValue());
}
if (input instanceof String string) {
return cast(input, Double.parseDouble(string));
}
throw new IllegalArgumentException("Not a byte type: " + input.getClass());
}
@Override
public Float serialize(final TypeAdapterRegistry registry, final Float value, final Type type) {
return value;
}
}

View File

@@ -1,36 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
import java.math.BigInteger;
public final class IntegerTypeAdapter extends TypeAdapter<Integer, Integer> {
public static final IntegerTypeAdapter INSTANCE = new IntegerTypeAdapter();
private static Integer cast(final Object original, final long value) {
if (value < (long)Integer.MIN_VALUE || value > (long)Integer.MAX_VALUE) {
throw new IllegalArgumentException("Integer value is out of range: " + original.toString());
}
return Integer.valueOf((int)value);
}
@Override
public Integer deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
// note: silently discard floating point significand
return cast(input, number instanceof BigInteger bigInteger ? bigInteger.longValueExact() : number.longValue());
}
if (input instanceof String string) {
return cast(input, (long)Double.parseDouble(string));
}
throw new IllegalArgumentException("Not an integer type: " + input.getClass());
}
@Override
public Integer serialize(final TypeAdapterRegistry registry, final Integer value, final Type type) {
return value;
}
}

View File

@@ -1,33 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
import java.math.BigInteger;
public final class LongTypeAdapter extends TypeAdapter<Long, Long> {
public static final LongTypeAdapter INSTANCE = new LongTypeAdapter();
@Override
public Long deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
// note: silently discard floating point significand
return number instanceof BigInteger bigInteger ? bigInteger.longValueExact() : number.longValue();
}
if (input instanceof String string) {
try {
return Long.valueOf(Long.parseLong(string));
} catch (final NumberFormatException ex) {
return Long.valueOf((long)Double.parseDouble(string));
}
}
throw new IllegalArgumentException("Not a long type: " + input.getClass());
}
@Override
public Long serialize(final TypeAdapterRegistry registry, final Long value, final Type type) {
return value;
}
}

View File

@@ -1,36 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
import java.math.BigInteger;
public final class ShortTypeAdapter extends TypeAdapter<Short, Short> {
public static final ShortTypeAdapter INSTANCE = new ShortTypeAdapter();
private static Short cast(final Object original, final long value) {
if (value < (long)Short.MIN_VALUE || value > (long)Short.MAX_VALUE) {
throw new IllegalArgumentException("Short value is out of range: " + original.toString());
}
return Short.valueOf((short)value);
}
@Override
public Short deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
// note: silently discard floating point significand
return cast(input, number instanceof BigInteger bigInteger ? bigInteger.longValueExact() : number.longValue());
}
if (input instanceof String string) {
return cast(input, (long)Double.parseDouble(string));
}
throw new IllegalArgumentException("Not a short type: " + input.getClass());
}
@Override
public Short serialize(final TypeAdapterRegistry registry, final Short value, final Type type) {
return value;
}
}

View File

@@ -1,29 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
public final class StringTypeAdapter extends TypeAdapter<String, String> {
public static final StringTypeAdapter INSTANCE = new StringTypeAdapter();
@Override
public String deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Boolean bool) {
return String.valueOf(bool.booleanValue());
}
if (input instanceof Number number) {
return number.toString();
}
if (input instanceof String string) {
return string;
}
throw new IllegalArgumentException("Not a string type: " + input.getClass());
}
@Override
public String serialize(final TypeAdapterRegistry registry, final String value, final Type type) {
return value;
}
}

View File

@@ -1,30 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.type;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
public final class BigDecimalTypeAdapter extends TypeAdapter<BigDecimal, String> {
public static final BigDecimalTypeAdapter INSTANCE = new BigDecimalTypeAdapter();
@Override
public BigDecimal deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
// safest to catch all number impls is to use toString
return new BigDecimal(number.toString());
}
if (input instanceof String string) {
return new BigDecimal(string);
}
throw new IllegalArgumentException("Not an BigDecimal type: " + input.getClass());
}
@Override
public String serialize(final TypeAdapterRegistry registry, final BigDecimal value, final Type type) {
return value.toString();
}
}

View File

@@ -1,37 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.type;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
public final class BigIntegerTypeAdapter extends TypeAdapter<BigInteger, String> {
public static final BigIntegerTypeAdapter INSTANCE = new BigIntegerTypeAdapter();
@Override
public BigInteger deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
if (number instanceof BigInteger bigInteger) {
return bigInteger;
}
// note: silently discard floating point significand
if (number instanceof BigDecimal bigDecimal) {
return bigDecimal.toBigInteger();
}
return BigInteger.valueOf(number.longValue());
}
if (input instanceof String string) {
return new BigDecimal(string).toBigInteger();
}
throw new IllegalArgumentException("Not an BigInteger type: " + input.getClass());
}
@Override
public String serialize(final TypeAdapterRegistry registry, final BigInteger value, final Type type) {
return value.toString();
}
}

View File

@@ -1,24 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.type;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import ca.spottedleaf.moonrise.common.config.type.Duration;
import java.lang.reflect.Type;
public final class DurationTypeAdapter extends TypeAdapter<Duration, String> {
public static final DurationTypeAdapter INSTANCE = new DurationTypeAdapter();
@Override
public Duration deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (!(input instanceof String string)) {
throw new IllegalArgumentException("Not a string: " + input.getClass());
}
return Duration.parse(string);
}
@Override
public String serialize(final TypeAdapterRegistry registry, final Duration value, final Type type) {
return value.toString();
}
}

View File

@@ -1,15 +0,0 @@
package ca.spottedleaf.moonrise.common.config.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation used on a class to indicate that its type adapter may automatically be generated. The class must have
* a public no-args constructor.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Adaptable {
}

View File

@@ -1,45 +0,0 @@
package ca.spottedleaf.moonrise.common.config.annotation;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Function;
/**
* Annotation indicating that a field should be deserialized or serialized from the config.
* By default, this annotation is not assumed.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Serializable {
/**
* Indicates whether this field is required to be present in the config. If the field is not present,
* and {@code required = true}, then an exception will be thrown during deserialization. If {@code required = false}
* and the field is not present, then the field value will remain unmodified.
*/
public boolean required() default false;
/**
* The comment to apply before the element when serializing.
*/
public String comment() default "";
/**
* Adapter override class. The class must have a public no-args constructor.
*/
public Class<? extends TypeAdapter> adapter() default TypeAdapter.class;
/**
* Whether to serialize the value to the config.
*/
public boolean serialize() default true;
/**
* When not empty, this value overrides the auto generated serialized key in the config.
*/
public String serializedKey() default "";
}

View File

@@ -1,178 +0,0 @@
package ca.spottedleaf.moonrise.common.config.config;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.comments.CommentLine;
import org.yaml.snakeyaml.comments.CommentType;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.representer.Represent;
import org.yaml.snakeyaml.representer.Representer;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public final class YamlConfig<T> {
public final TypeAdapterRegistry typeAdapters;
private final Class<? extends T> clazz;
public volatile T config;
private final Yaml yaml;
private final LoaderOptions loaderOptions;
private final DumperOptions dumperOptions;
public YamlConfig(final Class<? extends T> clazz, final T dfl) throws Exception {
this(clazz, dfl, new TypeAdapterRegistry());
}
public YamlConfig(final Class<? extends T> clazz, final T dfl, final TypeAdapterRegistry registry) throws Exception {
this.clazz = clazz;
this.config = dfl;
this.typeAdapters = registry;
this.typeAdapters.makeAdapter(clazz);
final LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setProcessComments(true);
final DumperOptions dumperOptions = new DumperOptions();
dumperOptions.setProcessComments(true);
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
this.loaderOptions = loaderOptions;
this.dumperOptions = dumperOptions;
this.yaml = new Yaml(new YamlConstructor(loaderOptions), new YamlRepresenter(dumperOptions), dumperOptions, loaderOptions);
}
public void load(final File file) throws IOException {
try (final InputStream is = new BufferedInputStream(new FileInputStream(file))) {
this.load(is);
}
}
public void load(final InputStream is) throws IOException {
final Object serialized = this.yaml.load(new InputStreamReader(is, StandardCharsets.UTF_8));
this.config = (T)this.typeAdapters.deserialize(serialized, this.clazz);
}
public void save(final File file) throws IOException {
this.save(file, "");
}
public void save(final File file, final String header) throws IOException {
if (file.isDirectory()) {
throw new IOException("File is a directory");
}
final File parent = file.getParentFile();
if (parent != null) {
parent.mkdirs();
}
final File tmp = new File(parent, file.getName() + ".tmp");
tmp.delete();
tmp.createNewFile();
try {
try (final OutputStream os = new BufferedOutputStream(new FileOutputStream(tmp))) {
this.save(os, header);
}
try {
Files.move(tmp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (final AtomicMoveNotSupportedException ex) {
Files.move(tmp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
} finally {
tmp.delete();
}
}
public void save(final OutputStream os) throws IOException {
os.write(this.saveToString().getBytes(StandardCharsets.UTF_8));
}
public void save(final OutputStream os, final String header) throws IOException {
os.write(this.saveToString(header).getBytes(StandardCharsets.UTF_8));
}
public String saveToString() {
return this.yaml.dump(this.typeAdapters.serialize(this.config, this.clazz));
}
public String saveToString(final String header) {
if (header.isBlank()) {
return this.saveToString();
}
final StringBuilder ret = new StringBuilder();
final String lineBreak = this.dumperOptions.getLineBreak().getString();
for (final String line : header.split("\n")) {
ret.append("# ").append(line.trim()).append(lineBreak);
}
ret.append(lineBreak);
return ret.append(this.saveToString()).toString();
}
public void callInitialisers() {
this.typeAdapters.callInitialisers(this.config);
}
private static final class YamlConstructor extends Constructor {
public YamlConstructor(final LoaderOptions loadingConfig) {
super(loadingConfig);
}
}
private static final class YamlRepresenter extends Representer {
public YamlRepresenter(final DumperOptions options) {
super(options);
this.representers.put(TypeAdapterRegistry.CommentedData.class, new CommentedDataRepresenter());
}
private final class CommentedDataRepresenter implements Represent {
@Override
public Node representData(final Object data0) {
final TypeAdapterRegistry.CommentedData commentedData = (TypeAdapterRegistry.CommentedData)data0;
final Node node = YamlRepresenter.this.representData(commentedData.data);
final List<CommentLine> comments = new ArrayList<>();
for (final String line : commentedData.comment.split("\n")) {
comments.add(new CommentLine(null, null, " ".concat(line.trim()), CommentType.BLOCK));
}
node.setBlockComments(comments);
return node;
}
}
}
}

View File

@@ -1,12 +1,12 @@
package ca.spottedleaf.moonrise.common.config.moonrise;
import ca.spottedleaf.moonrise.common.config.InitialiseHook;
import ca.spottedleaf.moonrise.common.config.annotation.Adaptable;
import ca.spottedleaf.moonrise.common.config.ui.ClothConfig;
import ca.spottedleaf.moonrise.common.config.annotation.Serializable;
import ca.spottedleaf.moonrise.common.config.type.Duration;
import ca.spottedleaf.moonrise.common.util.MoonriseCommon;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
import ca.spottedleaf.yamlconfig.InitialiseHook;
import ca.spottedleaf.yamlconfig.annotation.Adaptable;
import ca.spottedleaf.yamlconfig.annotation.Serializable;
import ca.spottedleaf.yamlconfig.type.Duration;
@Adaptable
public final class MoonriseConfig {
@@ -251,4 +251,21 @@ public final class MoonriseConfig {
)
public boolean fixMC159283 = false;
}
@Serializable
public Misc misc = new Misc();
@Adaptable
public static final class Misc {
@Serializable(
serializedKey = "immediately-close-loading-screen",
comment = """
Whether the loading screen should be closed immediately when joining servers/SP worlds.
This will let you in game faster, but may result in getting in game before enough chunks are
loaded for rendering.
"""
)
public boolean immediatelyCloseLoadingScreen = false;
}
}

View File

@@ -1,38 +0,0 @@
package ca.spottedleaf.moonrise.common.config.moonrise.adapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import ca.spottedleaf.moonrise.common.config.moonrise.type.DefaultedValue;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public final class DefaultedTypeAdapter extends TypeAdapter<DefaultedValue<?>, Object> {
private static final String DEFAULT_STRING = "default";
@Override
public DefaultedValue<?> deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof String string && string.equalsIgnoreCase(DEFAULT_STRING)) {
return new DefaultedValue<>();
}
if (!(type instanceof ParameterizedType parameterizedType)) {
throw new IllegalArgumentException("DefaultedValue field must specify generic type");
}
final Type valueType = parameterizedType.getActualTypeArguments()[0];
return new DefaultedValue<>(registry.deserialize(input, valueType));
}
@Override
public Object serialize(final TypeAdapterRegistry registry, final DefaultedValue<?> value, final Type type) {
final Object raw = value.getValueRaw();
if (raw == null) {
return DEFAULT_STRING;
}
final Type valueType = type instanceof ParameterizedType parameterizedType ? parameterizedType.getActualTypeArguments()[0] : null;
return registry.serialize(raw, valueType);
}
}

View File

@@ -1,22 +0,0 @@
package ca.spottedleaf.moonrise.common.config.moonrise.type;
public final class DefaultedValue<T> {
private final T value;
public DefaultedValue() {
this(null);
}
public DefaultedValue(final T value) {
this.value = value;
}
public T getValueRaw() {
return value;
}
public T getOrDefault(final T dfl) {
return this.value != null ? this.value : dfl;
}
}

View File

@@ -1,76 +0,0 @@
package ca.spottedleaf.moonrise.common.config.type;
import java.math.BigDecimal;
public final class Duration {
private final String string;
private final long timeNS;
private Duration(final String string, final long timeNS) {
this.string = string;
this.timeNS = timeNS;
}
public static Duration parse(final String value) {
if (value.length() < 2) {
throw new IllegalArgumentException("Invalid duration: " + value);
}
final char last = value.charAt(value.length() - 1);
final long multiplier;
switch (last) {
case 's': {
multiplier = (1000L * 1000L * 1000L) * 1L;
break;
}
case 't': {
multiplier = (1000L * 1000L * 1000L) / 20L;
break;
}
case 'm': {
multiplier = (1000L * 1000L * 1000L) * 60L;
break;
}
case 'h': {
multiplier = (1000L * 1000L * 1000L) * 60L * 60L;
break;
}
case 'd': {
multiplier = (1000L * 1000L * 1000L) * 24L * 60L * 60L;
break;
}
default: {
throw new IllegalArgumentException("Duration must end with one of: [s, t, m, h, d]");
}
}
final BigDecimal parsed = new BigDecimal(value.substring(0, value.length() - 1))
.multiply(new BigDecimal(multiplier));
return new Duration(value, parsed.toBigInteger().longValueExact());
}
public long getTimeNS() {
return this.timeNS;
}
public long getTimeMS() {
return this.timeNS / (1000L * 1000L);
}
public long getTimeS() {
return this.timeNS / (1000L * 1000L * 1000L);
}
public long getTimeTicks() {
return this.timeNS / ((1000L * 1000L * 1000L) / (20L));
}
@Override
public String toString() {
return this.string;
}
}

View File

@@ -19,6 +19,13 @@ public final class IntList {
return this.count;
}
public void setMinCapacity(final int len) {
final int[] byIndex = this.byIndex;
if (byIndex.length < len) {
this.byIndex = Arrays.copyOf(byIndex, len);
}
}
public int getRaw(final int index) {
return this.byIndex[index];
}

View File

@@ -0,0 +1,77 @@
package ca.spottedleaf.moonrise.common.list;
import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap;
import java.util.Arrays;
public final class ShortList {
private final Short2ShortOpenHashMap map = new Short2ShortOpenHashMap();
{
this.map.defaultReturnValue(Short.MIN_VALUE);
}
private static final short[] EMPTY_LIST = new short[0];
private short[] byIndex = EMPTY_LIST;
private short count;
public int size() {
return (int)this.count;
}
public short getRaw(final int index) {
return this.byIndex[index];
}
public void setMinCapacity(final int len) {
final short[] byIndex = this.byIndex;
if (byIndex.length < len) {
this.byIndex = Arrays.copyOf(byIndex, len);
}
}
public boolean add(final short value) {
final int count = (int)this.count;
final short currIndex = this.map.putIfAbsent(value, (short)count);
if (currIndex != Short.MIN_VALUE) {
return false; // already in this list
}
short[] list = this.byIndex;
if (list.length == count) {
// resize required
list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative
}
list[count] = value;
this.count = (short)(count + 1);
return true;
}
public boolean remove(final short value) {
final short index = this.map.remove(value);
if (index == Short.MIN_VALUE) {
return false;
}
// move the entry at the end to this index
final short endIndex = --this.count;
final short end = this.byIndex[endIndex];
if (index != endIndex) {
// not empty after this call
this.map.put(end, index);
}
this.byIndex[(int)index] = end;
this.byIndex[(int)endIndex] = (short)0;
return true;
}
public void clear() {
this.count = (short)0;
this.map.clear();
}
}

View File

@@ -4,13 +4,17 @@ import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.common.util.MoonriseConstants;
import ca.spottedleaf.moonrise.common.util.ChunkSystem;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.ChunkPos;
import java.util.ArrayList;
public final class NearbyPlayers {
@@ -20,7 +24,27 @@ public final class NearbyPlayers {
GENERAL_REALLY_SMALL,
TICK_VIEW_DISTANCE,
VIEW_DISTANCE,
SPAWN_RANGE, // Moonrise - chunk tick iteration
// Moonrise start - chunk tick iteration
SPAWN_RANGE {
@Override
void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
((ChunkTickServerLevel)world).moonrise$addPlayerTickingRequest(chunkX, chunkZ);
}
@Override
void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
((ChunkTickServerLevel)world).moonrise$removePlayerTickingRequest(chunkX, chunkZ);
}
};
// Moonrise end - chunk tick iteration
void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
}
void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
}
}
private static final NearbyMapType[] MAP_TYPES = NearbyMapType.values();
@@ -37,6 +61,12 @@ public final class NearbyPlayers {
private final ServerLevel world;
private final Reference2ReferenceOpenHashMap<ServerPlayer, TrackedPlayer[]> players = new Reference2ReferenceOpenHashMap<>();
private final Long2ReferenceOpenHashMap<TrackedChunk> byChunk = new Long2ReferenceOpenHashMap<>();
private final Long2ReferenceOpenHashMap<ReferenceList<ServerPlayer>>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES];
{
for (int i = 0; i < this.directByChunk.length; ++i) {
this.directByChunk[i] = new Long2ReferenceOpenHashMap<>();
}
}
public NearbyPlayers(final ServerLevel world) {
this.world = world;
@@ -70,6 +100,16 @@ public final class NearbyPlayers {
}
}
public void clear() {
if (this.players.isEmpty()) {
return;
}
for (final ServerPlayer player : new ArrayList<>(this.players.keySet())) {
this.removePlayer(player);
}
}
public void tickPlayer(final ServerPlayer player) {
final TrackedPlayer[] players = this.players.get(player);
if (players == null) {
@@ -82,7 +122,7 @@ public final class NearbyPlayers {
players[NearbyMapType.GENERAL_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_SMALL_VIEW_DISTANCE);
players[NearbyMapType.GENERAL_REALLY_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_REALLY_SMALL_VIEW_DISTANCE);
players[NearbyMapType.TICK_VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getTickViewDistance(player));
players[NearbyMapType.VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getLoadViewDistance(player));
players[NearbyMapType.VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getViewDistance(player));
players[NearbyMapType.SPAWN_RANGE.ordinal()].update(chunk.x, chunk.z, ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE); // Moonrise - chunk tick iteration
}
@@ -94,38 +134,41 @@ public final class NearbyPlayers {
return this.byChunk.get(CoordinateUtils.getChunkKey(pos));
}
public ReferenceList<ServerPlayer> getPlayers(final BlockPos pos, final NearbyMapType type) {
final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));
public TrackedChunk getChunk(final int chunkX, final int chunkZ) {
return this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
}
return chunk == null ? null : chunk.players[type.ordinal()];
public ReferenceList<ServerPlayer> getPlayers(final BlockPos pos, final NearbyMapType type) {
return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos));
}
public ReferenceList<ServerPlayer> getPlayers(final ChunkPos pos, final NearbyMapType type) {
final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));
return chunk == null ? null : chunk.players[type.ordinal()];
return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos));
}
public ReferenceList<ServerPlayer> getPlayersByChunk(final int chunkX, final int chunkZ, final NearbyMapType type) {
final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
return chunk == null ? null : chunk.players[type.ordinal()];
return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
}
public ReferenceList<ServerPlayer> getPlayersByBlock(final int blockX, final int blockZ, final NearbyMapType type) {
final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4));
return chunk == null ? null : chunk.players[type.ordinal()];
return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4));
}
public static final class TrackedChunk {
private static final ServerPlayer[] EMPTY_PLAYERS_ARRAY = new ServerPlayer[0];
private final long chunkKey;
private final NearbyPlayers nearbyPlayers;
private final ReferenceList<ServerPlayer>[] players = new ReferenceList[TOTAL_MAP_TYPES];
private int nonEmptyLists;
private long updateCount;
public TrackedChunk(final long chunkKey, final NearbyPlayers nearbyPlayers) {
this.chunkKey = chunkKey;
this.nearbyPlayers = nearbyPlayers;
}
public boolean isEmpty() {
return this.nonEmptyLists == 0;
}
@@ -145,7 +188,10 @@ public final class NearbyPlayers {
final ReferenceList<ServerPlayer> list = this.players[idx];
if (list == null) {
++this.nonEmptyLists;
(this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)).add(player);
final ReferenceList<ServerPlayer> players = (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY));
this.nearbyPlayers.directByChunk[idx].put(this.chunkKey, players);
players.add(player);
return;
}
@@ -169,6 +215,7 @@ public final class NearbyPlayers {
if (list.size() == 0) {
this.players[idx] = null;
this.nearbyPlayers.directByChunk[idx].remove(this.chunkKey);
--this.nonEmptyLists;
}
}
@@ -187,9 +234,19 @@ public final class NearbyPlayers {
protected void addCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) {
final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
NearbyPlayers.this.byChunk.computeIfAbsent(chunkKey, (final long keyInMap) -> {
return new TrackedChunk();
}).addPlayer(parameter, this.type);
final TrackedChunk chunk = NearbyPlayers.this.byChunk.get(chunkKey);
final NearbyMapType type = this.type;
if (chunk != null) {
chunk.addPlayer(parameter, type);
type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ);
} else {
final TrackedChunk created = new TrackedChunk(chunkKey, NearbyPlayers.this);
NearbyPlayers.this.byChunk.put(chunkKey, created);
created.addPlayer(parameter, type);
type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ);
((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created;
}
}
@Override
@@ -201,10 +258,16 @@ public final class NearbyPlayers {
throw new IllegalStateException("Chunk should exist at " + new ChunkPos(chunkKey));
}
chunk.removePlayer(parameter, this.type);
final NearbyMapType type = this.type;
chunk.removePlayer(parameter, type);
type.removeFrom(parameter, NearbyPlayers.this.world, chunkX, chunkZ);
if (chunk.isEmpty()) {
NearbyPlayers.this.byChunk.remove(chunkKey);
final ChunkData chunkData = ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$releaseChunkData(chunkKey);
if (chunkData != null) {
chunkData.nearbyPlayers = null;
}
}
}
}

View File

@@ -2,6 +2,7 @@ package ca.spottedleaf.moonrise.common.misc;
import ca.spottedleaf.concurrentutil.util.IntPairUtil;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.ReferenceSet;
@@ -14,6 +15,10 @@ public final class PositionCountingAreaMap<T> {
return this.counters.keySet();
}
public LongSet getPositions() {
return this.positions.keySet();
}
public int getTotalPositions() {
return this.positions.size();
}

View File

@@ -6,6 +6,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk;
import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader;
import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemServerChunkCache;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
import com.mojang.logging.LogUtils;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.FullChunkStatus;
@@ -117,16 +118,18 @@ public final class ChunkSystem {
((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
);
if (!((ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) {
chunk.postProcessGeneration();
chunk.postProcessGeneration((ServerLevel)chunk.getLevel());
}
((ServerLevel)chunk.getLevel()).startTickingChunk(chunk);
((ServerLevel)chunk.getLevel()).getChunkSource().chunkMap.tickingGenerated.incrementAndGet();
((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$markChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration
}
public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().remove(
((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
);
((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$removeChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration
}
public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {
@@ -149,8 +152,8 @@ public final class ChunkSystem {
return RegionizedPlayerChunkLoader.getAPISendViewDistance(player);
}
public static int getLoadViewDistance(final ServerPlayer player) {
return RegionizedPlayerChunkLoader.getLoadViewDistance(player);
public static int getViewDistance(final ServerPlayer player) {
return RegionizedPlayerChunkLoader.getAPIViewDistance(player);
}
public static int getTickViewDistance(final ServerPlayer player) {

View File

@@ -1,10 +1,9 @@
package ca.spottedleaf.moonrise.common.util;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import ca.spottedleaf.moonrise.common.config.config.YamlConfig;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.config.moonrise.MoonriseConfig;
import ca.spottedleaf.moonrise.common.config.moonrise.adapter.DefaultedTypeAdapter;
import ca.spottedleaf.moonrise.common.config.moonrise.type.DefaultedValue;
import ca.spottedleaf.yamlconfig.adapter.TypeAdapterRegistry;
import ca.spottedleaf.yamlconfig.config.YamlConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
@@ -13,19 +12,17 @@ public final class ConfigHolder {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigHolder.class);
private static final File CONFIG_FILE = new File(System.getProperty("Moonrise.ConfigFile", "config/moonrise.yml"));
private static final File CONFIG_FILE = new File(System.getProperty(PlatformHooks.get().getBrand() + ".ConfigFile", "config/moonrise.yml"));
private static final TypeAdapterRegistry CONFIG_ADAPTERS = new TypeAdapterRegistry();
private static final YamlConfig<MoonriseConfig> CONFIG;
static {
CONFIG_ADAPTERS.putAdapter(DefaultedValue.class, new DefaultedTypeAdapter());
try {
CONFIG = new YamlConfig<>(MoonriseConfig.class, new MoonriseConfig(), CONFIG_ADAPTERS);
} catch (final Exception ex) {
throw new RuntimeException(ex);
}
}
private static final String CONFIG_HEADER = """
private static final String CONFIG_HEADER = String.format("""
This is the configuration file for Moonrise.
Each configuration option is prefixed with a comment to explain what it does. Additional changes to this file
@@ -33,9 +30,10 @@ public final class ConfigHolder {
Below are the Moonrise startup flags. Note that startup flags must be placed in the JVM arguments, not
program arguments.
-DMoonrise.ConfigFile=<file> - Override the config file location. Might be useful for multiple game versions.
-DMoonrise.WorkerThreadCount=<number> - Override the auto configured worker thread counts (worker-threads).
""";
-D%1$s.ConfigFile=<file> - Override the config file location. Might be useful for multiple game versions.
-D%1$s.WorkerThreadCount=<number> - Override the auto configured worker thread counts (worker-threads).
-D%1$s.MaxViewDistance=<number> - Overrides the maximum view distance, should only use for debugging purposes.
""", PlatformHooks.get().getBrand());
static {
reloadConfig();
@@ -50,14 +48,6 @@ public final class ConfigHolder {
}
public static boolean reloadConfig() {
final boolean ret = reloadConfig0();
CONFIG.callInitialisers();
return ret;
}
private static boolean reloadConfig0() {
synchronized (CONFIG) {
if (CONFIG_FILE.exists()) {
try {
@@ -68,6 +58,8 @@ public final class ConfigHolder {
}
}
CONFIG.callInitialisers();
// write back any changes, or create if needed
return saveConfig();
}

View File

@@ -3,8 +3,12 @@ package ca.spottedleaf.moonrise.common.util;
public final class MixinWorkarounds {
// mixins tries to find the owner of the clone() method, which doesn't exist and NPEs
// https://github.com/FabricMC/Mixin/pull/147
public static long[] clone(final long[] values) {
return values.clone();
}
public static byte[] clone(final byte[] values) {
return values.clone();
}
}

View File

@@ -19,7 +19,7 @@ public final class MoonriseCommon {
@Override
public void accept(Thread thread) {
thread.setDaemon(true);
thread.setName("Moonrise Common Worker #" + this.idGenerator.getAndIncrement());
thread.setName(PlatformHooks.get().getBrand() + " Common Worker #" + this.idGenerator.getAndIncrement());
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(final Thread thread, final Throwable throwable) {
@@ -44,7 +44,7 @@ public final class MoonriseCommon {
} else {
defaultWorkerThreads = defaultWorkerThreads / 2;
}
defaultWorkerThreads = Integer.getInteger("Moonrise.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads));
defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads));
int workerThreads = configWorkerThreads;

View File

@@ -1,8 +1,10 @@
package ca.spottedleaf.moonrise.common.util;
import ca.spottedleaf.moonrise.common.PlatformHooks;
public final class MoonriseConstants {
public static final int MAX_VIEW_DISTANCE = 32;
public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32);
private MoonriseConstants() {}

View File

@@ -1,53 +0,0 @@
package ca.spottedleaf.moonrise.common.util;
import net.minecraft.world.level.levelgen.LegacyRandomSource;
/**
* Avoid costly CAS of superclass
*/
public final class SimpleRandom extends LegacyRandomSource {
private static final long MULTIPLIER = 25214903917L;
private static final long ADDEND = 11L;
private static final int BITS = 48;
private static final long MASK = (1L << BITS) - 1;
private long value;
public SimpleRandom(final long seed) {
super(0L);
this.value = seed;
}
@Override
public void setSeed(final long seed) {
this.value = (seed ^ MULTIPLIER) & MASK;
}
private long advanceSeed() {
return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK;
}
@Override
public int next(final int bits) {
return (int)(this.advanceSeed() >>> (BITS - bits));
}
@Override
public int nextInt() {
final long seed = this.advanceSeed();
return (int)(seed >>> (BITS - Integer.SIZE));
}
@Override
public int nextInt(final int bound) {
if (bound <= 0) {
throw new IllegalArgumentException();
}
// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
final long value = this.advanceSeed() >>> (BITS - Integer.SIZE);
return (int)((value * (long)bound) >>> Integer.SIZE);
}
}

View File

@@ -0,0 +1,105 @@
package ca.spottedleaf.moonrise.common.util;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.levelgen.BitRandomSource;
import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian;
import net.minecraft.world.level.levelgen.PositionalRandomFactory;
/**
* Avoid costly CAS of superclass + division in nextInt
*/
public final class SimpleThreadUnsafeRandom implements BitRandomSource {
private static final long MULTIPLIER = 25214903917L;
private static final long ADDEND = 11L;
private static final int BITS = 48;
private static final long MASK = (1L << BITS) - 1L;
private long value;
private final MarsagliaPolarGaussian gaussianSource = new MarsagliaPolarGaussian(this);
public SimpleThreadUnsafeRandom(final long seed) {
this.setSeed(seed);
}
@Override
public void setSeed(final long seed) {
this.value = (seed ^ MULTIPLIER) & MASK;
this.gaussianSource.reset();
}
private long advanceSeed() {
return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK;
}
@Override
public int next(final int bits) {
return (int)(this.advanceSeed() >>> (BITS - bits));
}
@Override
public int nextInt() {
final long seed = this.advanceSeed();
return (int)(seed >>> (BITS - Integer.SIZE));
}
@Override
public int nextInt(final int bound) {
if (bound <= 0) {
throw new IllegalArgumentException();
}
// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
final long value = this.advanceSeed() >>> (BITS - Integer.SIZE);
return (int)((value * (long)bound) >>> Integer.SIZE);
}
@Override
public double nextGaussian() {
return this.gaussianSource.nextGaussian();
}
@Override
public RandomSource fork() {
return new SimpleThreadUnsafeRandom(this.nextLong());
}
@Override
public PositionalRandomFactory forkPositional() {
return new SimpleRandomPositionalFactory(this.nextLong());
}
public static final class SimpleRandomPositionalFactory implements PositionalRandomFactory {
private final long seed;
public SimpleRandomPositionalFactory(final long seed) {
this.seed = seed;
}
public long getSeed() {
return this.seed;
}
@Override
public RandomSource fromHashOf(final String string) {
return new SimpleThreadUnsafeRandom((long)string.hashCode() ^ this.seed);
}
@Override
public RandomSource fromSeed(final long seed) {
return new SimpleThreadUnsafeRandom(seed);
}
@Override
public RandomSource at(final int x, final int y, final int z) {
return new SimpleThreadUnsafeRandom(Mth.getSeed(x, y, z) ^ this.seed);
}
@Override
public void parityConfigString(final StringBuilder stringBuilder) {
stringBuilder.append("SimpleRandomPositionalFactory{").append(this.seed).append('}');
}
}
}

View File

@@ -0,0 +1,94 @@
package ca.spottedleaf.moonrise.common.util;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.levelgen.BitRandomSource;
import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian;
import net.minecraft.world.level.levelgen.PositionalRandomFactory;
/**
* Avoid costly CAS of superclass
*/
public final class ThreadUnsafeRandom implements BitRandomSource {
private static final long MULTIPLIER = 25214903917L;
private static final long ADDEND = 11L;
private static final int BITS = 48;
private static final long MASK = (1L << BITS) - 1L;
private long value;
private final MarsagliaPolarGaussian gaussianSource = new MarsagliaPolarGaussian(this);
public ThreadUnsafeRandom(final long seed) {
this.setSeed(seed);
}
@Override
public void setSeed(final long seed) {
this.value = (seed ^ MULTIPLIER) & MASK;
this.gaussianSource.reset();
}
private long advanceSeed() {
return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK;
}
@Override
public int next(final int bits) {
return (int)(this.advanceSeed() >>> (BITS - bits));
}
@Override
public int nextInt() {
final long seed = this.advanceSeed();
return (int)(seed >>> (BITS - Integer.SIZE));
}
@Override
public double nextGaussian() {
return this.gaussianSource.nextGaussian();
}
@Override
public RandomSource fork() {
return new ThreadUnsafeRandom(this.nextLong());
}
@Override
public PositionalRandomFactory forkPositional() {
return new ThreadUnsafeRandomPositionalFactory(this.nextLong());
}
public static final class ThreadUnsafeRandomPositionalFactory implements PositionalRandomFactory {
private final long seed;
public ThreadUnsafeRandomPositionalFactory(final long seed) {
this.seed = seed;
}
public long getSeed() {
return this.seed;
}
@Override
public RandomSource fromHashOf(final String string) {
return new ThreadUnsafeRandom((long)string.hashCode() ^ this.seed);
}
@Override
public RandomSource fromSeed(final long seed) {
return new ThreadUnsafeRandom(seed);
}
@Override
public RandomSource at(final int x, final int y, final int z) {
return new ThreadUnsafeRandom(Mth.getSeed(x, y, z) ^ this.seed);
}
@Override
public void parityConfigString(final StringBuilder stringBuilder) {
stringBuilder.append("ThreadUnsafeRandomPositionalFactory{").append(this.seed).append('}');
}
}
}

View File

@@ -8,11 +8,19 @@ public final class WorldUtil {
// min, max are inclusive
public static int getMaxSection(final LevelHeightAccessor world) {
return world.getMaxSection() - 1; // getMaxSection() is exclusive
return world.getMaxSectionY();
}
public static int getMaxSection(final Level world) {
return world.getMaxSectionY();
}
public static int getMinSection(final LevelHeightAccessor world) {
return world.getMinSection();
return world.getMinSectionY();
}
public static int getMinSection(final Level world) {
return world.getMinSectionY();
}
public static int getMaxLightSection(final LevelHeightAccessor world) {

View File

@@ -3,6 +3,7 @@ package ca.spottedleaf.moonrise.mixin.block_counting;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import net.minecraft.util.BitStorage;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -18,15 +19,15 @@ interface BitStorageMixin extends BlockCountingBitStorage {
// provide default impl in case mods implement this...
@Override
public default Int2ObjectOpenHashMap<IntArrayList> moonrise$countEntries() {
final Int2ObjectOpenHashMap<IntArrayList> ret = new Int2ObjectOpenHashMap<>();
public default Int2ObjectOpenHashMap<ShortArrayList> moonrise$countEntries() {
final Int2ObjectOpenHashMap<ShortArrayList> ret = new Int2ObjectOpenHashMap<>();
final int size = this.getSize();
for (int index = 0; index < size; ++index) {
final int paletteIdx = this.get(index);
ret.computeIfAbsent(paletteIdx, (final int key) -> {
return new IntArrayList();
}).add(index);
return new ShortArrayList(64);
}).add((short)index);
}
return ret;

View File

@@ -1,13 +1,13 @@
package ca.spottedleaf.moonrise.mixin.block_counting;
import ca.spottedleaf.moonrise.common.list.IntList;
import ca.spottedleaf.moonrise.common.list.ShortList;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection;
import com.llamalad7.mixinextras.sugar.Local;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import net.minecraft.util.BitStorage;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunkSection;
@@ -48,26 +48,32 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
public abstract boolean maybeHas(Predicate<BlockState> predicate);
@Unique
private static final IntArrayList FULL_LIST = new IntArrayList(16*16*16);
private static final ShortArrayList FULL_LIST = new ShortArrayList(16*16*16);
static {
for (int i = 0; i < (16*16*16); ++i) {
for (short i = 0; i < (16*16*16); ++i) {
FULL_LIST.add(i);
}
}
@Unique
private int specialCollidingBlocks;
private boolean isClient;
@Unique
private final IntList tickingBlocks = new IntList();
private static final short CLIENT_FORCED_SPECIAL_COLLIDING_BLOCKS = (short)9999;
@Unique
private short specialCollidingBlocks;
@Unique
private final ShortList tickingBlocks = new ShortList();
@Override
public final int moonrise$getSpecialCollidingBlocks() {
return this.specialCollidingBlocks;
public final boolean moonrise$hasSpecialCollidingBlocks() {
return this.specialCollidingBlocks != 0;
}
@Override
public final IntList moonrise$getTickingBlockList() {
public final ShortList moonrise$getTickingBlockList() {
return this.tickingBlocks;
}
@@ -86,20 +92,35 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
if (oldState == newState) {
return;
}
if (CollisionUtil.isSpecialCollidingBlock(oldState)) {
--this.specialCollidingBlocks;
}
if (CollisionUtil.isSpecialCollidingBlock(newState)) {
++this.specialCollidingBlocks;
if (this.isClient) {
if (CollisionUtil.isSpecialCollidingBlock(newState)) {
this.specialCollidingBlocks = CLIENT_FORCED_SPECIAL_COLLIDING_BLOCKS;
}
return;
}
final int position = x | (z << 4) | (y << (4+4));
if (oldState.isRandomlyTicking()) {
this.tickingBlocks.remove(position);
final boolean isSpecialOld = CollisionUtil.isSpecialCollidingBlock(oldState);
final boolean isSpecialNew = CollisionUtil.isSpecialCollidingBlock(newState);
if (isSpecialOld != isSpecialNew) {
if (isSpecialOld) {
--this.specialCollidingBlocks;
} else {
++this.specialCollidingBlocks;
}
}
if (newState.isRandomlyTicking()) {
this.tickingBlocks.add(position);
final boolean oldTicking = oldState.isRandomlyTicking();
final boolean newTicking = newState.isRandomlyTicking();
if (oldTicking != newTicking) {
final ShortList tickingBlocks = this.tickingBlocks;
final short position = (short)(x | (z << 4) | (y << (4+4)));
if (oldTicking) {
tickingBlocks.remove(position);
} else {
tickingBlocks.add(position);
}
}
}
@@ -133,11 +154,11 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
if (this.maybeHas((final BlockState state) -> !state.isAir())) {
final PalettedContainer.Data<BlockState> data = this.states.data;
final Palette<BlockState> palette = data.palette;
final Palette<BlockState> palette = data.palette();
final int paletteSize = palette.getSize();
final BitStorage storage = data.storage;
final BitStorage storage = data.storage();
final Int2ObjectOpenHashMap<IntArrayList> counts;
final Int2ObjectOpenHashMap<ShortArrayList> counts;
if (paletteSize == 1) {
counts = new Int2ObjectOpenHashMap<>(1);
counts.put(0, FULL_LIST);
@@ -145,10 +166,10 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
counts = ((BlockCountingBitStorage)storage).moonrise$countEntries();
}
for (final Iterator<Int2ObjectMap.Entry<IntArrayList>> iterator = counts.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
final Int2ObjectMap.Entry<IntArrayList> entry = iterator.next();
for (final Iterator<Int2ObjectMap.Entry<ShortArrayList>> iterator = counts.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
final Int2ObjectMap.Entry<ShortArrayList> entry = iterator.next();
final int paletteIdx = entry.getIntKey();
final IntArrayList coordinates = entry.getValue();
final ShortArrayList coordinates = entry.getValue();
final int paletteCount = coordinates.size();
final BlockState state = palette.valueFor(paletteIdx);
@@ -158,16 +179,21 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
}
if (CollisionUtil.isSpecialCollidingBlock(state)) {
this.specialCollidingBlocks += paletteCount;
this.specialCollidingBlocks += (short)paletteCount;
}
this.nonEmptyBlockCount += paletteCount;
this.nonEmptyBlockCount += (short)paletteCount;
if (state.isRandomlyTicking()) {
this.tickingBlockCount += paletteCount;
final int[] raw = coordinates.elements();
this.tickingBlockCount += (short)paletteCount;
final short[] raw = coordinates.elements();
final int rawLen = raw.length;
Objects.checkFromToIndex(0, paletteCount, raw.length);
final ShortList tickingBlocks = this.tickingBlocks;
tickingBlocks.setMinCapacity(Math.min((rawLen + tickingBlocks.size()) * 3 / 2, 16*16*16));
Objects.checkFromToIndex(0, paletteCount, rawLen);
for (int i = 0; i < paletteCount; ++i) {
this.tickingBlocks.add(raw[i]);
tickingBlocks.add(raw[i]);
}
}
@@ -176,7 +202,7 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
if (!fluid.isEmpty()) {
//this.nonEmptyBlockCount += count; // fix vanilla bug: make non-empty block count correct
if (fluid.isRandomlyTicking()) {
this.tickingFluidCount += paletteCount;
this.tickingFluidCount += (short)paletteCount;
}
}
}
@@ -184,7 +210,7 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
}
/**
* @reason Call recalcBlockCounts on the client, as the client does not invoke it when deserializing chunk sections.
* @reason Set up special colliding blocks on the client, as it is too expensive to perform a full calculation
* @author Spottedleaf
*/
@Inject(
@@ -194,6 +220,8 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
)
)
private void callRecalcBlocksClient(final CallbackInfo ci) {
this.recalcBlockCounts();
this.isClient = true;
// force has special colliding blocks to be true
this.specialCollidingBlocks = this.nonEmptyBlockCount != (short)0 && this.maybeHas(CollisionUtil::isSpecialCollidingBlock) ? CLIENT_FORCED_SPECIAL_COLLIDING_BLOCKS : (short)0;
}
}

View File

@@ -3,6 +3,7 @@ package ca.spottedleaf.moonrise.mixin.block_counting;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import net.minecraft.util.BitStorage;
import net.minecraft.util.SimpleBitStorage;
import org.spongepowered.asm.mixin.Final;
@@ -24,43 +25,66 @@ abstract class SimpleBitStorageMixin implements BitStorage, BlockCountingBitStor
@Final
private int bits;
@Shadow
@Final
private long mask;
@Shadow
@Final
private int size;
@Override
public final Int2ObjectOpenHashMap<IntArrayList> moonrise$countEntries() {
public final Int2ObjectOpenHashMap<ShortArrayList> moonrise$countEntries() {
final int valuesPerLong = this.valuesPerLong;
final int bits = this.bits;
final long mask = this.mask;
final long mask = (1L << bits) - 1L;
final int size = this.size;
// we may be backed by global palette, so limit bits for init capacity
final Int2ObjectOpenHashMap<IntArrayList> ret = new Int2ObjectOpenHashMap<>(
1 << Math.min(6, bits)
);
if (bits <= 6) {
final ShortArrayList[] byId = new ShortArrayList[1 << bits];
final Int2ObjectOpenHashMap<ShortArrayList> ret = new Int2ObjectOpenHashMap<>(1 << bits);
int index = 0;
int index = 0;
for (long value : this.data) {
int li = 0;
do {
final int paletteIdx = (int)(value & mask);
value >>= bits;
for (long value : this.data) {
int li = 0;
do {
final int paletteIdx = (int)(value & mask);
value >>= bits;
++li;
ret.computeIfAbsent(paletteIdx, (final int key) -> {
return new IntArrayList();
}).add(index);
final ShortArrayList coords = byId[paletteIdx];
if (coords != null) {
coords.add((short)index++);
continue;
} else {
final ShortArrayList newCoords = new ShortArrayList(64);
byId[paletteIdx] = newCoords;
newCoords.add((short)index++);
ret.put(paletteIdx, newCoords);
continue;
}
} while (li < valuesPerLong && index < size);
}
++li;
++index;
} while (li < valuesPerLong && index < size);
return ret;
} else {
final Int2ObjectOpenHashMap<ShortArrayList> ret = new Int2ObjectOpenHashMap<>(
1 << 6
);
int index = 0;
for (long value : this.data) {
int li = 0;
do {
final int paletteIdx = (int)(value & mask);
value >>= bits;
++li;
ret.computeIfAbsent(paletteIdx, (final int key) -> {
return new ShortArrayList(64);
}).add((short)index++);
} while (li < valuesPerLong && index < size);
}
return ret;
}
return ret;
}
}

View File

@@ -3,6 +3,7 @@ package ca.spottedleaf.moonrise.mixin.block_counting;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import net.minecraft.util.BitStorage;
import net.minecraft.util.ZeroBitStorage;
import org.spongepowered.asm.mixin.Final;
@@ -17,17 +18,17 @@ abstract class ZeroBitStorageMixin implements BitStorage, BlockCountingBitStorag
private int size;
@Override
public final Int2ObjectOpenHashMap<IntArrayList> moonrise$countEntries() {
public final Int2ObjectOpenHashMap<ShortArrayList> moonrise$countEntries() {
final int size = this.size;
final int[] raw = new int[size];
final short[] raw = new short[size];
for (int i = 0; i < size; ++i) {
raw[i] = i;
raw[i] = (short)i;
}
final IntArrayList coordinates = IntArrayList.wrap(raw, size);
final ShortArrayList coordinates = ShortArrayList.wrap(raw, size);
final Int2ObjectOpenHashMap<IntArrayList> ret = new Int2ObjectOpenHashMap<>(1);
final Int2ObjectOpenHashMap<ShortArrayList> ret = new Int2ObjectOpenHashMap<>(1);
ret.put(0, coordinates);
return ret;
}

View File

@@ -6,6 +6,7 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.Property;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -17,6 +18,9 @@ abstract class BooleanPropertyMixin extends Property<Boolean> implements Propert
super(string, class_);
}
@Unique
private static final Boolean[] BY_ID = new Boolean[]{ Boolean.FALSE, Boolean.TRUE };
@Override
public final int moonrise$getIdFor(final Boolean value) {
return value.booleanValue() ? 1 : 0;
@@ -33,23 +37,6 @@ abstract class BooleanPropertyMixin extends Property<Boolean> implements Propert
)
)
private void init(final CallbackInfo ci) {
this.moonrise$setById(new Boolean[]{ Boolean.FALSE, Boolean.TRUE });
}
/**
* This skips all ops after the identity comparison in the original code.
*
* @reason Properties are identity comparable
* @author Spottedleaf
*/
@WrapOperation(
method = "equals",
constant = @Constant(
classValue = BooleanProperty.class,
ordinal = 0
)
)
private boolean skipFurtherComparison(final Object obj, final Operation<Boolean> orig) {
return false;
this.moonrise$setById(BY_ID);
}
}

View File

@@ -18,9 +18,6 @@ import java.util.Collection;
@Mixin(EnumProperty.class)
abstract class EnumPropertyMixin<T extends Enum<T> & StringRepresentable> extends Property<T> implements PropertyAccess<T> {
@Shadow
public abstract Collection<T> getPossibleValues();
@Unique
private int[] idLookupTable;

View File

@@ -83,7 +83,6 @@ abstract class StateHolderMixin<O, S> implements PropertyAccessStateHolder {
}
// remove values arrays
this.values = null;
for (final Map.Entry<Map<Property<?>, Comparable<?>>, S> entry : map.entrySet()) {
final S value = entry.getValue();
((StateHolderMixin<O, S>)(Object)(StateHolder<O, S>)value).values = null;
@@ -126,8 +125,8 @@ abstract class StateHolderMixin<O, S> implements PropertyAccessStateHolder {
* @author Spottedleaf
*/
@Overwrite
public <T extends Comparable<T>> Optional<T> getOptionalValue(final Property<T> property) {
return property == null ? Optional.empty() : Optional.ofNullable(this.optimisedTable.get(this.tableIndex, property));
public <T extends Comparable<T>> T getNullableValue(Property<T> property) {
return property == null ? null : this.optimisedTable.get(this.tableIndex, property);
}
/**

View File

@@ -302,6 +302,10 @@ abstract class ChunkHolderMixin extends GenerationChunkHolder implements ChunkSy
)
)
private LevelChunk redirectLightUpdate(final ChunkHolder instance) {
if (this.playersSentChunkTo.size() == 0) {
// no players to sent to, so don't need to update anything
return null;
}
return this.getChunkToSend();
}

View File

@@ -11,14 +11,17 @@ import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import com.mojang.datafixers.DataFixer;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntConsumer;
import java.util.function.Supplier;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.StreamTagVisitor;
import net.minecraft.server.level.ChunkGenerationTask;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ChunkResult;
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter;
import net.minecraft.server.level.ChunkTaskDispatcher;
import net.minecraft.server.level.ChunkTrackingView;
import net.minecraft.server.level.GeneratingChunkMap;
import net.minecraft.server.level.GenerationChunkHolder;
@@ -28,7 +31,6 @@ import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.Mth;
import net.minecraft.util.StaticCache2D;
import net.minecraft.util.thread.BlockableEventLoop;
import net.minecraft.util.thread.ProcessorHandle;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
@@ -78,13 +80,10 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
private volatile Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap;
@Shadow
private ChunkTaskPriorityQueueSorter queueSorter;
private ChunkTaskDispatcher worldgenTaskDispatcher;
@Shadow
private ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> worldgenMailbox;
@Shadow
private ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mainThreadMailbox;
private ChunkTaskDispatcher lightTaskDispatcher;
@Shadow
private int serverViewDistance;
@@ -98,6 +97,12 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
@Shadow
private Queue<Runnable> unloadQueue;
@Shadow
private LongSet chunksToEagerlySave;
@Shadow
private AtomicInteger activeChunkWrites;
public ChunkMapMixin(RegionStorageInfo regionStorageInfo, Path path, DataFixer dataFixer, boolean bl) {
super(regionStorageInfo, path, dataFixer, bl);
}
@@ -128,11 +133,12 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
this.updatingChunkMap = null;
this.visibleChunkMap = null;
this.pendingUnloads = null;
this.queueSorter = null;
this.worldgenMailbox = null;
this.mainThreadMailbox = null;
this.worldgenTaskDispatcher = null;
this.lightTaskDispatcher = null;
this.pendingGenerationTasks = null;
this.unloadQueue = null;
this.chunksToEagerlySave = null;
this.activeChunkWrites = null;
// Dummy impl for mods that try to loadAsync directly
this.worker = new IOWorker(
@@ -152,7 +158,7 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
@Override
public CompletableFuture<Optional<CompoundTag>> loadAsync(final ChunkPos chunkPos) {
final CompletableFuture<Optional<CompoundTag>> future = new CompletableFuture<>();
MoonriseRegionFileIO.loadDataAsync(ChunkMapMixin.this.level, chunkPos.x, chunkPos.z, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, (tag, throwable) -> {
MoonriseRegionFileIO.loadDataAsync(ChunkMapMixin.this.level, chunkPos.x, chunkPos.z, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, (final CompoundTag tag, final Throwable throwable) -> {
if (throwable != null) {
future.completeExceptionally(throwable);
} else {
@@ -184,6 +190,15 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
};
}
/**
* @reason This map is not needed, we maintain our own ordered set of chunks to autosave.
* @author Spottedleaf
*/
@Overwrite
public void setChunkUnsaved(final ChunkPos pos) {
}
/**
* @reason Route to new chunk system hooks
* @author Spottedleaf
@@ -261,6 +276,15 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
throw new UnsupportedOperationException();
}
/**
* @reason Destroy old chunk system hooks
* @author Spottedleaf
*/
@Overwrite
public void onLevelChange(final ChunkPos chunkPos, final IntSupplier intSupplier, final int i, final IntConsumer intConsumer) {
throw new UnsupportedOperationException();
}
/**
* @reason Destroy old chunk system hooks
* @author Spottedleaf
@@ -309,6 +333,15 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.autoSave();
}
/**
* @reason Destroy old chunk system hooks
* @author Spottedleaf
*/
@Overwrite
public void saveChunksEagerly(final BooleanSupplier hasTime) {
throw new UnsupportedOperationException();
}
/**
* @reason Destroy old chunk system hooks
* @author Spottedleaf
@@ -403,7 +436,7 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
* @author Spottedleaf
*/
@Overwrite
public void onChunkReadyToSend(final LevelChunk chunk) {
public void onChunkReadyToSend(final ChunkHolder holder, final LevelChunk chunk) {
throw new UnsupportedOperationException();
}
@@ -422,7 +455,7 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
* @see NewChunkHolder#save(boolean)
*/
@Overwrite
public boolean saveChunkIfNeeded(final ChunkHolder chunkHolder) {
public boolean saveChunkIfNeeded(final ChunkHolder chunkHolder, final long time) {
throw new UnsupportedOperationException();
}
@@ -505,6 +538,21 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
throw new UnsupportedOperationException();
}
/**
* @reason Route to new chunk system
* @author Spottedleaf
*/
@Redirect(
method = "forEachSpawnCandidateChunk",
at = @At(
value = "INVOKE",
target = "Lit/unimi/dsi/fastutil/longs/Long2ObjectLinkedOpenHashMap;get(J)Ljava/lang/Object;"
)
)
private <V> V redirectChunkHolderGet(final Long2ObjectLinkedOpenHashMap<V> instance, final long key) {
return (V)this.getVisibleChunkIfPresent(key);
}
@Override
public CompletableFuture<Optional<CompoundTag>> read(final ChunkPos pos) {
final CompletableFuture<Optional<CompoundTag>> ret = new CompletableFuture<>();
@@ -524,10 +572,11 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
}
@Override
public CompletableFuture<Void> write(final ChunkPos pos, final CompoundTag tag) {
public CompletableFuture<Void> write(final ChunkPos pos, final Supplier<CompoundTag> tag) {
MoonriseRegionFileIO.scheduleSave(
this.level, pos.x, pos.z, tag,
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA);
this.level, pos.x, pos.z, tag.get(),
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA
);
return null;
}

View File

@@ -22,12 +22,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
@Mixin(ChunkStorage.class)
abstract class ChunkStorageMixin implements ChunkSystemChunkStorage, AutoCloseable {
@Shadow
private IOWorker worker;
public IOWorker worker;
@Unique
private static final Logger LOGGER = LogUtils.getLogger();
@@ -118,13 +119,13 @@ abstract class ChunkStorageMixin implements ChunkSystemChunkStorage, AutoCloseab
method = "write",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/chunk/storage/IOWorker;store(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/nbt/CompoundTag;)Ljava/util/concurrent/CompletableFuture;"
target = "Lnet/minecraft/world/level/chunk/storage/IOWorker;store(Lnet/minecraft/world/level/ChunkPos;Ljava/util/function/Supplier;)Ljava/util/concurrent/CompletableFuture;"
)
)
private CompletableFuture<Void> redirectWrite(final IOWorker instance, final ChunkPos chunkPos,
final CompoundTag compoundTag) {
final Supplier<CompoundTag> compoundTag) {
try {
this.storage.write(chunkPos, compoundTag);
this.storage.write(chunkPos, compoundTag.get());
return CompletableFuture.completedFuture(null);
} catch (final Throwable throwable) {
return CompletableFuture.failedFuture(throwable);

View File

@@ -41,8 +41,8 @@ abstract class ClientLevelMixin extends Level implements ChunkSystemLevel {
@Final
private ClientChunkCache chunkSource;
protected ClientLevelMixin(WritableLevelData writableLevelData, ResourceKey<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i);
protected ClientLevelMixin(final WritableLevelData writableLevelData, final ResourceKey<Level> resourceKey, final RegistryAccess registryAccess, final Holder<DimensionType> holder, final boolean bl, final boolean bl2, final long l, final int i) {
super(writableLevelData, resourceKey, registryAccess, holder, bl, bl2, l, i);
}
/**
@@ -55,14 +55,28 @@ abstract class ClientLevelMixin extends Level implements ChunkSystemLevel {
value = "RETURN"
)
)
private void init(ClientPacketListener clientPacketListener, ClientLevel.ClientLevelData clientLevelData,
ResourceKey<Level> resourceKey, Holder<DimensionType> holder, int i, int j, Supplier<ProfilerFiller> supplier,
LevelRenderer levelRenderer, boolean bl, long l, CallbackInfo ci) {
private void init(ClientPacketListener clientPacketListener, ClientLevel.ClientLevelData clientLevelData, ResourceKey<Level> resourceKey,
Holder<DimensionType> holder, int i, int j, LevelRenderer levelRenderer, boolean bl, long l, int k, CallbackInfo ci) {
this.entityStorage = null;
this.moonrise$setEntityLookup(new ClientEntityLookup(this, ((ClientLevel)(Object)this).new EntityCallbacks()));
}
@Override
public final boolean moonrise$areChunksLoaded(final int fromX, final int fromZ, final int toX, final int toZ) {
final ClientChunkCache chunkSource = this.chunkSource;
for (int currZ = fromZ; currZ <= toZ; ++currZ) {
for (int currX = fromX; currX <= toX; ++currX) {
if (!chunkSource.hasChunk(currX, currZ)) {
return false;
}
}
}
return true;
}
/**
* @reason Redirect to new entity manager
* @author Spottedleaf

View File

@@ -1,5 +1,6 @@
package ca.spottedleaf.moonrise.mixin.chunk_system;
import ca.spottedleaf.moonrise.common.util.MoonriseConstants;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemDistanceManager;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
@@ -8,13 +9,13 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter;
import net.minecraft.server.level.DistanceManager;
import net.minecraft.server.level.ThrottlingChunkTaskDispatcher;
import net.minecraft.server.level.Ticket;
import net.minecraft.server.level.TicketType;
import net.minecraft.server.level.TickingTracker;
import net.minecraft.util.Mth;
import net.minecraft.util.SortedArraySet;
import net.minecraft.util.thread.ProcessorHandle;
import net.minecraft.world.level.ChunkPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@@ -45,13 +46,7 @@ abstract class DistanceManagerMixin implements ChunkSystemDistanceManager {
Set<ChunkHolder> chunksToUpdateFutures;
@Shadow
ChunkTaskPriorityQueueSorter ticketThrottler;
@Shadow
ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> ticketThrottlerInput;
@Shadow
ProcessorHandle<ChunkTaskPriorityQueueSorter.Release> ticketThrottlerReleaser;
ThrottlingChunkTaskDispatcher ticketDispatcher;
@Shadow
LongSet ticketsToRelease;
@@ -59,12 +54,10 @@ abstract class DistanceManagerMixin implements ChunkSystemDistanceManager {
@Shadow
Executor mainThreadExecutor;
@Shadow
private DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter;
@Shadow
private int simulationDistance;
@Override
public ChunkMap moonrise$getChunkMap() {
throw new AbstractMethodError();
@@ -86,16 +79,14 @@ abstract class DistanceManagerMixin implements ChunkSystemDistanceManager {
this.tickingTicketsTracker = null;
this.playerTicketManager = null;
this.chunksToUpdateFutures = null;
this.ticketThrottler = null;
this.ticketThrottlerInput = null;
this.ticketThrottlerReleaser = null;
this.ticketDispatcher = null;
this.ticketsToRelease = null;
this.mainThreadExecutor = null;
this.simulationDistance = -1;
}
@Override
public ChunkHolderManager moonrise$getChunkHolderManager() {
public final ChunkHolderManager moonrise$getChunkHolderManager() {
return ((ChunkSystemServerLevel)this.moonrise$getChunkMap().level).moonrise$getChunkTaskScheduler().chunkHolderManager;
}
@@ -296,7 +287,10 @@ abstract class DistanceManagerMixin implements ChunkSystemDistanceManager {
*/
@Overwrite
public void updateSimulationDistance(final int simulationDistance) {
((ChunkSystemServerLevel)this.moonrise$getChunkMap().level).moonrise$getPlayerChunkLoader().setTickDistance(simulationDistance);
// note: vanilla does not clamp to 0, but we do simply because we need a min of 0
final int clamped = Mth.clamp(simulationDistance, 0, MoonriseConstants.MAX_VIEW_DISTANCE);
((ChunkSystemServerLevel)this.moonrise$getChunkMap().level).moonrise$getPlayerChunkLoader().setTickDistance(clamped);
}
/**
@@ -326,6 +320,15 @@ abstract class DistanceManagerMixin implements ChunkSystemDistanceManager {
throw new UnsupportedOperationException();
}
/**
* @reason Remove old chunk system hooks
* @author Spottedleaf
*/
@Overwrite
public LongSet getTickingChunks() {
throw new UnsupportedOperationException();
}
/**
* @reason This hack is not required anymore, see {@link MinecraftServerMixin}
* @author Spottedleaf

View File

@@ -1,5 +1,6 @@
package ca.spottedleaf.moonrise.mixin.chunk_system;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
import ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
import com.google.common.collect.ImmutableList;
@@ -49,6 +50,9 @@ abstract class EntityMixin implements ChunkSystemEntity {
@Unique
private FullChunkStatus chunkStatus;
@Unique
private ChunkData chunkData;
@Unique
private int sectionX = Integer.MIN_VALUE;
@@ -76,6 +80,16 @@ abstract class EntityMixin implements ChunkSystemEntity {
this.chunkStatus = status;
}
@Override
public final ChunkData moonrise$getChunkData() {
return this.chunkData;
}
@Override
public final void moonrise$setChunkData(final ChunkData chunkData) {
this.chunkData = chunkData;
}
@Override
public final int moonrise$getSectionX() {
return this.sectionX;

View File

@@ -99,8 +99,18 @@ abstract class EntityTickListMixin {
* @reason Route to new entity list
* @author Spottedleaf
*/
@Overwrite
public void forEach(final Consumer<Entity> action) {
@Inject(
method = "forEach",
at = @At("HEAD"),
cancellable = true
)
private void injectForEach(final Consumer<Entity> consumer, final CallbackInfo ci) {
this.forEach(consumer);
ci.cancel();
}
@Unique
private void forEach(final Consumer<Entity> action) {
// To ensure nothing weird happens with dimension travelling, do not iterate over new entries...
// (by dfl iterator() is configured to not iterate over new entries)
final IteratorSafeOrderedReferenceSet.Iterator<Entity> iterator = this.entities.iterator();

View File

@@ -195,15 +195,6 @@ abstract class GenerationChunkHolderMixin {
throw new UnsupportedOperationException();
}
/**
* @reason Chunk system is not built on futures anymore
* @author Spottedleaf
*/
@Overwrite
public int getGenerationRefCount() {
throw new UnsupportedOperationException();
}
/**
* @reason Route to new chunk holder
* @author Spottedleaf

View File

@@ -93,11 +93,15 @@ abstract class LevelChunkMixin extends ChunkAccess implements ChunkSystemLevelCh
}
@Override
public void setUnsaved(final boolean needsSaving) {
if (!needsSaving) {
((ChunkSystemLevelChunkTicks)this.blockTicks).moonrise$clearDirty();
((ChunkSystemLevelChunkTicks)this.fluidTicks).moonrise$clearDirty();
public boolean tryMarkSaved() {
if (!this.isUnsaved()) {
return false;
}
super.setUnsaved(needsSaving);
((ChunkSystemLevelChunkTicks)this.blockTicks).moonrise$clearDirty();
((ChunkSystemLevelChunkTicks)this.fluidTicks).moonrise$clearDirty();
super.tryMarkSaved();
return true;
}
}

View File

@@ -1,18 +1,23 @@
package ca.spottedleaf.moonrise.mixin.chunk_system;
import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup;
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup;
import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.FullChunkStatus;
import net.minecraft.util.profiling.Profiler;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.entity.EntityTypeTest;
@@ -33,9 +38,6 @@ import java.util.function.Predicate;
@Mixin(Level.class)
abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter, LevelAccessor, AutoCloseable {
@Shadow
public abstract ProfilerFiller getProfiler();
@Shadow
public abstract LevelChunk getChunk(int i, int j);
@@ -46,13 +48,16 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
@Unique
private EntityLookup entityLookup;
@Unique
private final ConcurrentLong2ReferenceChainedHashTable<ChunkData> chunkData = new ConcurrentLong2ReferenceChainedHashTable<>();
@Override
public final EntityLookup moonrise$getEntityLookup() {
return this.entityLookup;
}
@Override
public void moonrise$setEntityLookup(final EntityLookup entityLookup) {
public final void moonrise$setEntityLookup(final EntityLookup entityLookup) {
if (this.entityLookup != null && !(this.entityLookup instanceof DefaultEntityLookup)) {
throw new IllegalStateException("Entity lookup already initialised");
}
@@ -80,7 +85,7 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
@Overwrite
@Override
public List<Entity> getEntities(final Entity entity, final AABB boundingBox, final Predicate<? super Entity> predicate) {
this.getProfiler().incrementCounter("getEntities");
Profiler.get().incrementCounter("getEntities");
final List<Entity> ret = new ArrayList<>();
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(entity, boundingBox, ret, predicate);
@@ -98,7 +103,7 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
public <T extends Entity> void getEntities(final EntityTypeTest<Entity, T> entityTypeTest,
final AABB boundingBox, final Predicate<? super T> predicate,
final List<? super T> into, final int maxCount) {
this.getProfiler().incrementCounter("getEntities");
Profiler.get().incrementCounter("getEntities");
if (entityTypeTest instanceof EntityType<T> byType) {
if (maxCount != Integer.MAX_VALUE) {
@@ -171,7 +176,7 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
*/
@Override
public final <T extends Entity> List<T> getEntitiesOfClass(final Class<T> entityClass, final AABB boundingBox, final Predicate<? super T> predicate) {
this.getProfiler().incrementCounter("getEntities");
Profiler.get().incrementCounter("getEntities");
final List<T> ret = new ArrayList<>();
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(entityClass, null, boundingBox, ret, predicate);
@@ -189,7 +194,7 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
*/
@Override
public final List<Entity> moonrise$getHardCollidingEntities(final Entity entity, final AABB box, final Predicate<? super Entity> predicate) {
this.getProfiler().incrementCounter("getEntities");
Profiler.get().incrementCounter("getEntities");
final List<Entity> ret = new ArrayList<>();
((ChunkSystemLevel)this).moonrise$getEntityLookup().getHardCollidingEntities(entity, box, ret, predicate);
@@ -217,6 +222,52 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
// no-op on ClientLevel
}
@Override
public final ChunkData moonrise$getChunkData(final long chunkKey) {
return this.chunkData.get(chunkKey);
}
@Override
public final ChunkData moonrise$getChunkData(final int chunkX, final int chunkZ) {
return this.chunkData.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
}
@Override
public final ChunkData moonrise$requestChunkData(final long chunkKey) {
return this.chunkData.compute(chunkKey, (final long keyInMap, final ChunkData valueInMap) -> {
if (valueInMap == null) {
final ChunkData ret = new ChunkData();
ret.increaseRef();
return ret;
}
valueInMap.increaseRef();
return valueInMap;
});
}
@Override
public final ChunkData moonrise$releaseChunkData(final long chunkKey) {
return this.chunkData.compute(chunkKey, (final long keyInMap, final ChunkData chunkData) -> {
return chunkData.decreaseRef() == 0 ? null : chunkData;
});
}
@Override
public boolean moonrise$areChunksLoaded(final int fromX, final int fromZ, final int toX, final int toZ) {
final ChunkSource chunkSource = this.getChunkSource();
for (int currZ = fromZ; currZ <= toZ; ++currZ) {
for (int currX = fromX; currX <= toX; ++currX) {
if (!chunkSource.hasChunk(currX, currZ)) {
return false;
}
}
}
return true;
}
/**
* @reason Declare method in this class so that any invocations are virtual, and not interface.
* @author Spottedleaf
@@ -226,6 +277,13 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
return this.getChunkSource().hasChunk(x, z);
}
@Override
public boolean hasChunksAt(final int minBlockX, final int minBlockZ, final int maxBlockX, final int maxBlockZ) {
return this.moonrise$areChunksLoaded(
minBlockX >> 4, minBlockZ >> 4, maxBlockX >> 4, maxBlockZ >> 4
);
}
/**
* @reason Turn all getChunk(x, z, status) calls into virtual invokes, instead of interface invokes:
* 1. The interface invoke is expensive

View File

@@ -240,6 +240,7 @@ abstract class MinecraftServerMixin extends ReentrantBlockableEventLoop<TickTask
private void closeIOThreads(final CallbackInfo ci) {
LOGGER.info("Waiting for I/O tasks to complete...");
MoonriseRegionFileIO.flush((MinecraftServer)(Object)this);
LOGGER.info("All I/O tasks to complete");
if ((Object)this instanceof DedicatedServer) {
MoonriseCommon.haltExecutors();
}

View File

@@ -43,4 +43,17 @@ abstract class PlayerListMixin {
)
private void doNotAdjustVD(final PlayerList instance, final Packet<?> packet) {}
/**
* @reason The RegionizedPlayerChunkLoader will handle the SD packet
* @author Spottedleaf
*/
@Redirect(
method = "setSimulationDistance",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/players/PlayerList;broadcastAll(Lnet/minecraft/network/protocol/Packet;)V"
)
)
private void doNotAdjustSD(final PlayerList instance, final Packet<?> packet) {}
}

View File

@@ -36,6 +36,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -44,7 +45,14 @@ import java.util.stream.Stream;
@Mixin(PoiManager.class)
// Declare the generic type as Object so that our Overrides match the method signature of the superclass
// Specifically, getOrCreate must return Object so that existing invokes do not route to the superclass
public abstract class PoiManagerMixin extends SectionStorage<Object> implements ChunkSystemPoiManager {
public abstract class PoiManagerMixin extends SectionStorage<Object, Object> implements ChunkSystemPoiManager {
public PoiManagerMixin(final SimpleRegionStorage simpleRegionStorage, final Codec<Object> codec, final Function<Object, Object> function,
final BiFunction<Object, Runnable, Object> biFunction, final Function<Runnable, Object> function2,
final RegistryAccess registryAccess, final ChunkIOErrorReporter chunkIOErrorReporter,
final LevelHeightAccessor levelHeightAccessor) {
super(simpleRegionStorage, codec, function, biFunction, function2, registryAccess, chunkIOErrorReporter, levelHeightAccessor);
}
@Shadow
abstract boolean isVillageCenter(long l);
@@ -52,10 +60,6 @@ public abstract class PoiManagerMixin extends SectionStorage<Object> implements
@Shadow
public abstract void checkConsistencyWithBlocks(SectionPos sectionPos, LevelChunkSection levelChunkSection);
public PoiManagerMixin(SimpleRegionStorage simpleRegionStorage, Function<Runnable, Codec<Object>> function, Function<Runnable, Object> function2, RegistryAccess registryAccess, ChunkIOErrorReporter chunkIOErrorReporter, LevelHeightAccessor levelHeightAccessor) {
super(simpleRegionStorage, function, function2, registryAccess, chunkIOErrorReporter, levelHeightAccessor);
}
@Unique
private ServerLevel world;
@@ -151,8 +155,8 @@ public abstract class PoiManagerMixin extends SectionStorage<Object> implements
TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main");
final ChunkHolderManager manager = ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager;
final PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true);
final PoiChunk ret = ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager
.getPoiChunkIfLoaded(chunkX, chunkZ, true);
return ret == null ? Optional.empty() : (Optional)ret.getSectionForVanilla(chunkY);
}
@@ -206,9 +210,13 @@ public abstract class PoiManagerMixin extends SectionStorage<Object> implements
public final void moonrise$onUnload(final long coordinate) { // Paper - rewrite chunk system
final int chunkX = CoordinateUtils.getChunkX(coordinate);
final int chunkZ = CoordinateUtils.getChunkZ(coordinate);
final int minY = WorldUtil.getMinSection(this.world);
final int maxY = WorldUtil.getMaxSection(this.world);
TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Unloading poi chunk off-main");
for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
final long sectionPos = SectionPos.asLong(chunkX, section, chunkZ);
for (int sectionY = minY; sectionY <= maxY; ++sectionY) {
final long sectionPos = SectionPos.asLong(chunkX, sectionY, chunkZ);
this.updateDistanceTracking(sectionPos);
}
}
@@ -217,8 +225,12 @@ public abstract class PoiManagerMixin extends SectionStorage<Object> implements
public final void moonrise$loadInPoiChunk(final PoiChunk poiChunk) {
final int chunkX = poiChunk.chunkX;
final int chunkZ = poiChunk.chunkZ;
final int minY = WorldUtil.getMinSection(this.world);
final int maxY = WorldUtil.getMaxSection(this.world);
TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Loading poi chunk off-main");
for (int sectionY = this.levelHeightAccessor.getMinSection(); sectionY < this.levelHeightAccessor.getMaxSection(); ++sectionY) {
for (int sectionY = minY; sectionY <= maxY; ++sectionY) {
final PoiSection section = poiChunk.getSection(sectionY);
if (section != null && !((ChunkSystemPoiSection)section).moonrise$isEmpty()) {
this.onSectionLoad(SectionPos.asLong(chunkX, sectionY, chunkZ));
@@ -254,20 +266,4 @@ public abstract class PoiManagerMixin extends SectionStorage<Object> implements
private <T> Stream<T> skipLoadedSet(final Stream<T> instance, final Predicate<? super T> predicate) {
return instance;
}
@Override
public final void moonrise$close() throws IOException {}
@Override
public final CompoundTag moonrise$read(final int chunkX, final int chunkZ) throws IOException {
return MoonriseRegionFileIO.loadData(
this.world, chunkX, chunkZ, MoonriseRegionFileIO.RegionFileType.POI_DATA,
MoonriseRegionFileIO.getIOBlockingPriorityForCurrentThread()
);
}
@Override
public final void moonrise$write(final int chunkX, final int chunkZ, final CompoundTag data) throws IOException {
MoonriseRegionFileIO.scheduleSave(this.world, chunkX, chunkZ, data, MoonriseRegionFileIO.RegionFileType.POI_DATA);
}
}

View File

@@ -30,7 +30,7 @@ abstract class PoiSectionMixin implements ChunkSystemPoiSection {
@Unique
private final Optional<PoiSection> noAllocOptional = Optional.of((PoiSection)(Object)this);;
private final Optional<PoiSection> noAllocOptional = Optional.of((PoiSection)(Object)this);
@Override
public final boolean moonrise$isEmpty() {

View File

@@ -2,8 +2,6 @@ package ca.spottedleaf.moonrise.mixin.chunk_system;
import ca.spottedleaf.moonrise.patches.chunk_system.level.storage.ChunkSystemSectionStorage;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.RegistryOps;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
import net.minecraft.world.level.chunk.storage.SectionStorage;
@@ -23,15 +21,11 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Mixin(SectionStorage.class)
abstract class SectionStorageMixin implements ChunkSystemSectionStorage, AutoCloseable {
abstract class SectionStorageMixin<R, P> implements ChunkSystemSectionStorage, AutoCloseable {
@Shadow
private SimpleRegionStorage simpleRegionStorage;
@Shadow
@Final
private static Logger LOGGER;
@Unique
private RegionFileStorage storage;
@@ -41,6 +35,9 @@ abstract class SectionStorageMixin implements ChunkSystemSectionStorage, AutoClo
return this.storage;
}
@Override
public void moonrise$close() throws IOException {}
/**
* @reason Retrieve storage from IOWorker, and then nuke it
* @author Spottedleaf
@@ -61,12 +58,8 @@ abstract class SectionStorageMixin implements ChunkSystemSectionStorage, AutoClo
* @author Spottedleaf
*/
@Overwrite
public final CompletableFuture<Optional<CompoundTag>> tryRead(final ChunkPos pos) {
try {
return CompletableFuture.completedFuture(Optional.ofNullable(this.moonrise$read(pos.x, pos.z)));
} catch (final Throwable thr) {
return CompletableFuture.failedFuture(thr);
}
public final CompletableFuture<Optional<SectionStorage.PackedChunk<P>>> tryRead(final ChunkPos pos) {
throw new IllegalStateException("Only chunk system can write state, offending class:" + this.getClass().getName());
}
/**
@@ -74,29 +67,17 @@ abstract class SectionStorageMixin implements ChunkSystemSectionStorage, AutoClo
* @author Spottedleaf
*/
@Overwrite
public void readColumn(final ChunkPos pos, final RegistryOps<Tag> ops, final CompoundTag data) {
public void unpackChunk(final ChunkPos chunkPos, final SectionStorage.PackedChunk<P> packedChunk) {
throw new IllegalStateException("Only chunk system can load in state, offending class:" + this.getClass().getName());
}
/**
* @reason Route to new chunk system hook
* @reason Destroy old chunk system hook
* @author Spottedleaf
*/
@Redirect(
method = "writeColumn(Lnet/minecraft/world/level/ChunkPos;)V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/chunk/storage/SimpleRegionStorage;write(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/nbt/CompoundTag;)Ljava/util/concurrent/CompletableFuture;"
)
)
private CompletableFuture<Void> redirectWrite(final SimpleRegionStorage instance, final ChunkPos pos,
final CompoundTag tag) {
try {
this.moonrise$write(pos.x, pos.z, tag);
} catch (final IOException ex) {
LOGGER.error("Error writing poi chunk data to disk for chunk " + pos, ex);
}
return null;
@Overwrite
private void writeChunk(final ChunkPos chunkPos) {
throw new IllegalStateException("Only chunk system can write state, offending class:" + this.getClass().getName());
}
/**
@@ -113,4 +94,4 @@ abstract class SectionStorageMixin implements ChunkSystemSectionStorage, AutoClo
private void redirectClose(final SimpleRegionStorage instance) throws IOException {
this.moonrise$close();
}
}
}

View File

@@ -3,13 +3,13 @@ package ca.spottedleaf.moonrise.mixin.chunk_system;
import net.minecraft.core.SectionPos;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.storage.ChunkSerializer;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(ChunkSerializer.class)
abstract class ChunkSerializerMixin {
@Mixin(SerializableChunkData.class)
abstract class SerializableChunkDataMixin {
/**
* @reason Chunk system handles this during full transition
@@ -17,12 +17,12 @@ abstract class ChunkSerializerMixin {
* @see ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkFullTask
*/
@Redirect(
method = "read",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/ai/village/poi/PoiManager;checkConsistencyWithBlocks(Lnet/minecraft/core/SectionPos;Lnet/minecraft/world/level/chunk/LevelChunkSection;)V"
)
method = "read",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/ai/village/poi/PoiManager;checkConsistencyWithBlocks(Lnet/minecraft/core/SectionPos;Lnet/minecraft/world/level/chunk/LevelChunkSection;)V"
)
)
private static void skipConsistencyCheck(PoiManager instance, SectionPos sectionPos, LevelChunkSection levelChunkSection) {}
private void skipConsistencyCheck(final PoiManager instance, final SectionPos sectionPos, final LevelChunkSection levelChunkSection) {}
}

View File

@@ -3,6 +3,7 @@ package ca.spottedleaf.moonrise.mixin.chunk_system;
import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable;
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.common.util.TickThread;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
@@ -23,6 +24,7 @@ import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LightChunk;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.storage.DimensionDataStorage;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@@ -33,6 +35,8 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@@ -48,6 +52,10 @@ abstract class ServerChunkCacheMixin extends ChunkSource implements ChunkSystemS
@Final
public ServerLevel level;
@Shadow
@Final
private DimensionDataStorage dataStorage;
@Unique
private final ConcurrentLong2ReferenceChainedHashTable<LevelChunk> fullChunks = new ConcurrentLong2ReferenceChainedHashTable<>();
@@ -258,6 +266,7 @@ abstract class ServerChunkCacheMixin extends ChunkSource implements ChunkSystemS
@Override
@Overwrite
public void close() throws IOException {
this.dataStorage.close();
((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.close(true, true);
}
@@ -282,7 +291,8 @@ abstract class ServerChunkCacheMixin extends ChunkSource implements ChunkSystemS
*/
@Overwrite
public void getFullChunk(final long pos, final Consumer<LevelChunk> consumer) {
final LevelChunk fullChunk = this.getChunkNow(CoordinateUtils.getChunkX(pos), CoordinateUtils.getChunkZ(pos));
// note: bypass currentlyLoaded from getChunkNow
final LevelChunk fullChunk = this.fullChunks.get(pos);
if (fullChunk != null) {
consumer.accept(fullChunk);
}
@@ -305,12 +315,28 @@ abstract class ServerChunkCacheMixin extends ChunkSource implements ChunkSystemS
return false;
}
/**
* @reason We need to use {@link ChunkHolder#getChunkToSend()} as the new chunk system will not bring every chunk
* sent to players up to block ticking.
* @author Spottedleaf
*/
@Redirect(
method = "broadcastChangedChunks",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/level/ChunkHolder;getTickingChunk()Lnet/minecraft/world/level/chunk/LevelChunk;")
)
private LevelChunk redirectTickingChunk(final ChunkHolder instance) {
return instance.getChunkToSend();
}
/**
* @reason Perform mid-tick chunk task processing during chunk tick
* @author Spottedleaf
*/
@Inject(
method = "tickChunks",
method = "tickChunks(Lnet/minecraft/util/profiling/ProfilerFiller;JLjava/util/List;)V",
at = @At(
value = "INVOKE",
shift = At.Shift.AFTER,
@@ -331,27 +357,11 @@ abstract class ServerChunkCacheMixin extends ChunkSource implements ChunkSystemS
* @author Spottedleaf
*/
@Redirect(
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/level/ServerLevel;isNaturalSpawningAllowed(Lnet/minecraft/world/level/ChunkPos;)Z"
)
)
private boolean shortNaturalSpawning(final ServerLevel instance, final ChunkPos chunkPos) {
return true;
}
/**
* @reason In the chunk system, ticking chunks always have loaded entities. Of course, they are also always
* marked to be as ticking as well.
* @author Spottedleaf
*/
@Redirect(
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/level/ServerLevel;shouldTickBlocksAt(J)Z"
)
method = "tickChunks(Lnet/minecraft/util/profiling/ProfilerFiller;JLjava/util/List;)V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/level/ServerLevel;shouldTickBlocksAt(J)Z"
)
)
private boolean shortShouldTickBlocks(final ServerLevel instance, final long pos) {
return true;

View File

@@ -10,6 +10,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.EntityData
import ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.PoiDataController;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder;
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup;
import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
@@ -26,6 +27,7 @@ import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.DistanceManager;
import net.minecraft.server.level.ServerChunkCache;
@@ -62,6 +64,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
@@ -83,8 +86,8 @@ abstract class ServerLevelMixin extends Level implements ChunkSystemServerLevel,
@Final
private ServerChunkCache chunkSource;
protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i);
protected ServerLevelMixin(final WritableLevelData writableLevelData, final ResourceKey<Level> resourceKey, final RegistryAccess registryAccess, final Holder<DimensionType> holder, final boolean bl, final boolean bl2, final long l, final int i) {
super(writableLevelData, resourceKey, registryAccess, holder, bl, bl2, l, i);
}
@Unique
@@ -345,6 +348,21 @@ abstract class ServerLevelMixin extends Level implements ChunkSystemServerLevel,
return this.entityTickingChunks;
}
@Override
public final boolean moonrise$areChunksLoaded(final int fromX, final int fromZ, final int toX, final int toZ) {
final ServerChunkCache chunkSource = this.chunkSource;
for (int currZ = fromZ; currZ <= toZ; ++currZ) {
for (int currX = fromX; currX <= toX; ++currX) {
if (!chunkSource.hasChunk(currX, currZ)) {
return false;
}
}
}
return true;
}
/**
* @reason Declare method in this class so that any invocations are virtual, and not interface.
* @author Spottedleaf

View File

@@ -33,7 +33,7 @@ abstract class ChunkMapMixin {
public ServerLevel level;
@Shadow
protected abstract boolean playerIsCloseEnoughForSpawning(ServerPlayer serverPlayer, ChunkPos chunkPos);
public abstract boolean playerIsCloseEnoughForSpawning(ServerPlayer serverPlayer, ChunkPos chunkPos);
/**
* @reason Hook for updating the spawn tracker in distance manager. We add our own hook instead of using the
@@ -85,11 +85,20 @@ abstract class ChunkMapMixin {
}
/**
* @reason Use nearby players to avoid iterating over all online players
* @reason Avoid checking first if there are nearby players, as we make internal perform this implicitly.
* @author Spottedleaf
*/
@Overwrite
public boolean anyPlayerCloseEnoughForSpawning(final ChunkPos pos) {
return this.anyPlayerCloseEnoughForSpawningInternal(pos);
}
/**
* @reason Use nearby players to avoid iterating over all online players
* @author Spottedleaf
*/
@Overwrite
public boolean anyPlayerCloseEnoughForSpawningInternal(final ChunkPos pos) {
final ReferenceList<ServerPlayer> players = ((ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers().getPlayers(
pos, NearbyPlayers.NearbyMapType.SPAWN_RANGE
);
@@ -116,15 +125,15 @@ abstract class ChunkMapMixin {
*/
@Overwrite
public List<ServerPlayer> getPlayersCloseForSpawning(final ChunkPos pos) {
final List<ServerPlayer> ret = new ArrayList<>();
final ReferenceList<ServerPlayer> players = ((ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers().getPlayers(
pos, NearbyPlayers.NearbyMapType.SPAWN_RANGE
);
if (players == null) {
return ret;
return new ArrayList<>();
}
List<ServerPlayer> ret = null;
final ServerPlayer[] raw = players.getRawDataUnchecked();
final int len = players.size();
@@ -132,10 +141,15 @@ abstract class ChunkMapMixin {
for (int i = 0; i < len; ++i) {
final ServerPlayer player = raw[i];
if (this.playerIsCloseEnoughForSpawning(player, pos)) {
ret.add(player);
if (ret == null) {
ret = new ArrayList<>(len - i);
ret.add(player);
} else {
ret.add(player);
}
}
}
return ret;
return ret == null ? new ArrayList<>() : ret;
}
}

View File

@@ -4,6 +4,7 @@ import ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager;
import it.unimi.dsi.fastutil.longs.LongIterator;
import net.minecraft.core.SectionPos;
import net.minecraft.server.level.DistanceManager;
import net.minecraft.server.level.ServerPlayer;
@@ -106,4 +107,13 @@ abstract class DistanceManagerMixin implements ChunkTickDistanceManager {
public boolean hasPlayersNearby(final long pos) {
return this.spawnChunkTracker.hasObjectsNear(CoordinateUtils.getChunkX(pos), CoordinateUtils.getChunkZ(pos));
}
/**
* @reason Use spawnChunkTracker instead
* @author Spottedleaf
*/
@Overwrite
public LongIterator getSpawnCandidateChunks() {
return this.spawnChunkTracker.getPositions().iterator();
}
}

View File

@@ -1,24 +1,30 @@
package ca.spottedleaf.moonrise.mixin.chunk_tick_iteration;
import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
import ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
import net.minecraft.Util;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkSource;
import org.objectweb.asm.Opcodes;
import net.minecraft.world.level.chunk.LevelChunk;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
@Mixin(ServerChunkCache.class)
abstract class ServerChunkCacheMixin extends ChunkSource {
@@ -27,94 +33,84 @@ abstract class ServerChunkCacheMixin extends ChunkSource {
@Final
public ServerLevel level;
@Shadow
@Final
public ChunkMap chunkMap;
@Unique
private ServerChunkCache.ChunkAndHolder[] iterationCopy;
private final SimpleThreadUnsafeRandom shuffleRandom = new SimpleThreadUnsafeRandom(0L);
/**
* @reason Avoid creating the list, which is sized at the chunkholder count. The actual number of ticking
* chunks is always lower. The mixin below will initialise the list to non-null.
* @author Spottedleaf
*/
@Redirect(
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Lcom/google/common/collect/Lists;newArrayListWithCapacity(I)Ljava/util/ArrayList;"
)
)
private <T> ArrayList<T> avoidListCreation(final int initialArraySize) {
return null;
}
/**
* @reason Initialise the list to contain only the ticking chunks.
* @author Spottedleaf
*/
@ModifyVariable(
method = "tickChunks",
at = @At(
value = "STORE",
opcode = Opcodes.ASTORE,
ordinal = 0
)
)
private List<ServerChunkCache.ChunkAndHolder> initTickChunks(final List<ServerChunkCache.ChunkAndHolder> shouldBeNull) {
final ReferenceList<ServerChunkCache.ChunkAndHolder> tickingChunks =
((ChunkSystemServerLevel)this.level).moonrise$getTickingChunks();
final ServerChunkCache.ChunkAndHolder[] raw = tickingChunks.getRawDataUnchecked();
final int size = tickingChunks.size();
if (this.iterationCopy == null || this.iterationCopy.length < size) {
this.iterationCopy = new ServerChunkCache.ChunkAndHolder[raw.length];
@Unique
private boolean isChunkNearPlayer(final ChunkMap chunkMap, final ChunkPos chunkPos, final LevelChunk levelChunk) {
final ChunkData chunkData = ((ChunkSystemChunkHolder)((ChunkSystemLevelChunk)levelChunk).moonrise$getChunkAndHolder().holder())
.moonrise$getRealChunkHolder().holderData;
final NearbyPlayers.TrackedChunk nearbyPlayers = chunkData.nearbyPlayers;
if (nearbyPlayers == null) {
return false;
}
System.arraycopy(raw, 0, this.iterationCopy, 0, size);
return ObjectArrayList.wrap(
this.iterationCopy, size
);
}
final ReferenceList<ServerPlayer> players = nearbyPlayers.getPlayers(NearbyPlayers.NearbyMapType.SPAWN_RANGE);
if (players == null) {
return false;
}
final ServerPlayer[] raw = players.getRawDataUnchecked();
final int len = players.size();
Objects.checkFromIndexSize(0, len, raw.length);
for (int i = 0; i < len; ++i) {
if (chunkMap.playerIsCloseEnoughForSpawning(raw[i], chunkPos)) {
return true;
}
}
/**
* @reason Do not initialise ticking chunk list, as we did that above.
* @author Spottedleaf
*/
@Redirect(
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Ljava/util/Iterator;hasNext()Z",
ordinal = 0
)
)
private <E> boolean skipTickAdd(final Iterator<E> instance) {
return false;
}
/**
* @reason Clear the iteration array, and at the same time broadcast chunk changes.
* @reason Use the player ticking chunks list, which already contains chunks that are:
* 1. block ticking
* 2. within spawn range (8 chunks on any axis)
* @author Spottedleaf
*/
@Overwrite
private void collectTickingChunks(final List<LevelChunk> list) {
final ReferenceList<ServerChunkCache.ChunkAndHolder> tickingChunks =
((ChunkTickServerLevel)this.level).moonrise$getPlayerTickingChunks();
final ServerChunkCache.ChunkAndHolder[] raw = tickingChunks.getRawDataUnchecked();
final int size = tickingChunks.size();
final ChunkMap chunkMap = this.chunkMap;
for (int i = 0; i < size; ++i) {
final ServerChunkCache.ChunkAndHolder chunkAndHolder = raw[i];
final LevelChunk levelChunk = chunkAndHolder.chunk();
if (!this.isChunkNearPlayer(chunkMap, levelChunk.getPos(), levelChunk)) {
continue;
}
list.add(levelChunk);
}
}
/**
* @reason Use random implementation which does not use CAS and has a faster nextInt(int)
* function
* @author Spottedleaf
*/
@Redirect(
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Ljava/util/List;forEach(Ljava/util/function/Consumer;)V",
ordinal = 0
)
method = "tickChunks()V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/Util;shuffle(Ljava/util/List;Lnet/minecraft/util/RandomSource;)V"
)
)
private void broadcastChanges(final List<ServerChunkCache.ChunkAndHolder> instance,
final Consumer<ServerChunkCache.ChunkAndHolder> consumer) {
final ObjectArrayList<ServerChunkCache.ChunkAndHolder> chunks = (ObjectArrayList<ServerChunkCache.ChunkAndHolder>)instance;
final ServerChunkCache.ChunkAndHolder[] raw = chunks.elements();
final int size = chunks.size();
Objects.checkFromToIndex(0, size, raw.length);
for (int i = 0; i < size; ++i) {
final ServerChunkCache.ChunkAndHolder holder = raw[i];
raw[i] = null;
holder.holder().broadcastChanges(holder.chunk());
}
private <T> void useBetterRandom(final List<T> list, final RandomSource randomSource) {
this.shuffleRandom.setSeed(randomSource.nextLong());
Util.shuffle(list, this.shuffleRandom);
}
}

View File

@@ -0,0 +1,100 @@
package ca.spottedleaf.moonrise.mixin.chunk_tick_iteration;
import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.common.util.TickThread;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.LevelChunk;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(ServerLevel.class)
abstract class ServerLevelMixin implements ChunkTickServerLevel {
@Unique
private static final ServerChunkCache.ChunkAndHolder[] EMPTY_PLAYER_CHUNK_HOLDERS = new ServerChunkCache.ChunkAndHolder[0];
@Unique
private final ReferenceList<ServerChunkCache.ChunkAndHolder> playerTickingChunks = new ReferenceList<>(EMPTY_PLAYER_CHUNK_HOLDERS);
@Unique
private final Long2IntOpenHashMap playerTickingRequests = new Long2IntOpenHashMap();
@Override
public final ReferenceList<ServerChunkCache.ChunkAndHolder> moonrise$getPlayerTickingChunks() {
return this.playerTickingChunks;
}
@Override
public final void moonrise$markChunkForPlayerTicking(final LevelChunk chunk) {
final ChunkPos pos = chunk.getPos();
if (!this.playerTickingRequests.containsKey(CoordinateUtils.getChunkKey(pos))) {
return;
}
this.playerTickingChunks.add(((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder());
}
@Override
public final void moonrise$removeChunkForPlayerTicking(final LevelChunk chunk) {
this.playerTickingChunks.remove(((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder());
}
@Override
public final void moonrise$addPlayerTickingRequest(final int chunkX, final int chunkZ) {
TickThread.ensureTickThread((ServerLevel)(Object)this, chunkX, chunkZ, "Cannot add ticking request async");
final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
if (this.playerTickingRequests.addTo(chunkKey, 1) != 0) {
// already added
return;
}
final NewChunkHolder chunkHolder = ((ChunkSystemServerLevel)(ServerLevel)(Object)this).moonrise$getChunkTaskScheduler()
.chunkHolderManager.getChunkHolder(chunkKey);
if (chunkHolder == null || !chunkHolder.isTickingReady()) {
return;
}
this.playerTickingChunks.add(
((ChunkSystemLevelChunk)(LevelChunk)chunkHolder.getCurrentChunk()).moonrise$getChunkAndHolder()
);
}
@Override
public final void moonrise$removePlayerTickingRequest(final int chunkX, final int chunkZ) {
TickThread.ensureTickThread((ServerLevel)(Object)this, chunkX, chunkZ, "Cannot remove ticking request async");
final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
final int val = this.playerTickingRequests.addTo(chunkKey, -1);
if (val <= 0) {
throw new IllegalStateException("Negative counter");
}
if (val != 1) {
// still has at least one request
return;
}
final NewChunkHolder chunkHolder = ((ChunkSystemServerLevel)(ServerLevel)(Object)this).moonrise$getChunkTaskScheduler()
.chunkHolderManager.getChunkHolder(chunkKey);
if (chunkHolder == null || !chunkHolder.isTickingReady()) {
return;
}
this.playerTickingChunks.remove(
((ChunkSystemLevelChunk)(LevelChunk)chunkHolder.getCurrentChunk()).moonrise$getChunkAndHolder()
);
}
}

View File

@@ -6,37 +6,32 @@ import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.entity.vehicle.AbstractMinecart;
import net.minecraft.world.level.Level;
import org.spongepowered.asm.mixin.Final;
import net.minecraft.world.phys.AABB;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.List;
import java.util.function.Predicate;
@Mixin(ArmorStand.class)
abstract class ArmorStandMixin extends LivingEntity {
@Shadow
@Final
private static Predicate<Entity> RIDABLE_MINECARTS;
protected ArmorStandMixin(EntityType<? extends LivingEntity> entityType, Level level) {
super(entityType, level);
}
/**
* @reason Optimise this method by making it use the class lookup
* @reason Optimise by making it use the class lookup
* @author Spottedleaf
*/
@Overwrite
public void pushEntities() {
final List<AbstractMinecart> nearby = this.level().getEntitiesOfClass(AbstractMinecart.class, this.getBoundingBox(), RIDABLE_MINECARTS);
for (int i = 0, len = nearby.size(); i < len; ++i) {
final AbstractMinecart minecart = nearby.get(i);
if (this.distanceToSqr(minecart) <= 0.2) {
minecart.push(this);
}
}
@Redirect(
method = "pushEntities",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/Level;getEntities(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;Ljava/util/function/Predicate;)Ljava/util/List;"
)
)
private List<Entity> redirectGetEntities(final Level instance, final Entity entity, final AABB list, final Predicate<? super Entity> arg) {
return (List)this.level().getEntitiesOfClass(AbstractMinecart.class, this.getBoundingBox(), arg);
}
}

View File

@@ -4,10 +4,14 @@ import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(BlockPos.class)
abstract class BlockPosMixin extends Vec3i {
@Shadow
public abstract BlockPos immutable();
public BlockPosMixin(int i, int j, int k) {
super(i, j, k);
}
@@ -29,7 +33,7 @@ abstract class BlockPosMixin extends Vec3i {
@Overwrite
@Override
public BlockPos above(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX(), this.getY() + distance, this.getZ());
return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() + distance, this.getZ());
}
/**
@@ -49,7 +53,7 @@ abstract class BlockPosMixin extends Vec3i {
@Overwrite
@Override
public BlockPos below(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX(), this.getY() - distance, this.getZ());
return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() - distance, this.getZ());
}
/**
@@ -69,7 +73,7 @@ abstract class BlockPosMixin extends Vec3i {
@Overwrite
@Override
public BlockPos north(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX(), this.getY(), this.getZ() - distance);
return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() - distance);
}
/**
@@ -89,7 +93,7 @@ abstract class BlockPosMixin extends Vec3i {
@Overwrite
@Override
public BlockPos south(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX(), this.getY(), this.getZ() + distance);
return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() + distance);
}
/**
@@ -109,7 +113,7 @@ abstract class BlockPosMixin extends Vec3i {
@Overwrite
@Override
public BlockPos west(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX() - distance, this.getY(), this.getZ());
return distance == 0 ? this.immutable() : new BlockPos(this.getX() - distance, this.getY(), this.getZ());
}
/**
@@ -129,6 +133,6 @@ abstract class BlockPosMixin extends Vec3i {
@Overwrite
@Override
public BlockPos east(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX() + distance, this.getY(), this.getZ());
return distance == 0 ? this.immutable() : new BlockPos(this.getX() + distance, this.getY(), this.getZ());
}
}

View File

@@ -5,16 +5,12 @@ import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
import com.mojang.serialization.MapCodec;
import it.unimi.dsi.fastutil.HashCommon;
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.spongepowered.asm.mixin.Mixin;
@@ -32,7 +28,13 @@ abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implem
protected BlockBehaviour.BlockStateBase.Cache cache;
@Shadow
public abstract VoxelShape getCollisionShape(BlockGetter blockGetter, BlockPos blockPos, CollisionContext collisionContext);
public abstract boolean isAir();
@Shadow
public VoxelShape[] occlusionShapesByFace;
@Shadow
public VoxelShape occlusionShape;
protected BlockStateBaseMixin(Block object, Reference2ObjectArrayMap<Property<?>, Comparable<?>> reference2ObjectArrayMap, MapCodec<BlockState> mapCodec) {
super(object, reference2ObjectArrayMap, mapCodec);
@@ -74,7 +76,7 @@ abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implem
}
if (neighbours) {
for (final Direction direction : DIRECTIONS_CACHED) {
initCaches(Shapes.getFaceShape(shape, direction), false);
initCaches(((CollisionVoxelShape)shape).moonrise$getFaceShapeClamped(direction), false);
initCaches(shape.getFaceShape(direction), false);
}
}
@@ -93,10 +95,9 @@ abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implem
private void initCollisionState(final CallbackInfo ci) {
if (this.cache != null) {
final VoxelShape collisionShape = this.cache.collisionShape;
try {
this.constantCollisionShape = this.getCollisionShape(null, null, null);
} catch (final Throwable throwable) {
// :(
if (this.isAir()) {
this.constantCollisionShape = Shapes.empty();
} else {
this.constantCollisionShape = null;
}
this.occludesFullBlock = ((CollisionVoxelShape)collisionShape).moonrise$occludesFullBlock();
@@ -107,17 +108,21 @@ abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implem
if (this.constantCollisionShape != null) {
initCaches(this.constantCollisionShape, true);
}
if (this.cache.occlusionShapes != null) {
for (final VoxelShape shape : this.cache.occlusionShapes) {
initCaches(shape, false);
}
}
} else {
this.occludesFullBlock = false;
this.emptyCollisionShape = false;
this.emptyConstantCollisionShape = false;
this.constantCollisionShape = null;
}
if (this.occlusionShape != null) {
initCaches(this.occlusionShape, true);
}
if (this.occlusionShapesByFace != null) {
for (final VoxelShape shape : this.occlusionShapesByFace) {
initCaches(shape, true);
}
}
}
@Override

View File

@@ -1,6 +1,6 @@
package ca.spottedleaf.moonrise.mixin.collisions;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState;
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
@@ -10,11 +10,11 @@ import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
@@ -48,7 +48,7 @@ abstract class EntityMixin {
public abstract Vec3 getEyePosition();
@Shadow
public abstract boolean onGround();
private boolean onGround;
@Unique
private static float[] calculateStepHeights(final AABB box, final List<VoxelShape> voxels, final List<AABB> aabbs, final float stepHeight,
@@ -144,9 +144,9 @@ abstract class EntityMixin {
final boolean collidedDownwards = collidedY && movement.y < 0.0;
final double stepHeight = (double)this.maxUpStep();
final double stepHeight;
if (stepHeight <= 0.0 || (!collidedDownwards && !this.onGround()) || (!collidedX && !collidedZ)) {
if ((!collidedDownwards && !this.onGround) || (!collidedX && !collidedZ) || (stepHeight = (double)this.maxUpStep()) <= 0.0) {
return collided;
}
@@ -184,64 +184,99 @@ abstract class EntityMixin {
return false;
}
final float reducedWith = this.dimensions.width() * 0.8F;
final AABB box = AABB.ofSize(this.getEyePosition(), reducedWith, 1.0E-6D, reducedWith);
final double reducedWith = (double)(this.dimensions.width() * 0.8F);
final AABB boundingBox = AABB.ofSize(this.getEyePosition(), reducedWith, 1.0E-6D, reducedWith);
final Level world = this.level;
if (CollisionUtil.isEmpty(box)) {
if (CollisionUtil.isEmpty(boundingBox)) {
return false;
}
final BlockPos.MutableBlockPos tempPos = new BlockPos.MutableBlockPos();
final int minBlockX = Mth.floor(boundingBox.minX);
final int minBlockY = Mth.floor(boundingBox.minY);
final int minBlockZ = Mth.floor(boundingBox.minZ);
final int minX = Mth.floor(box.minX);
final int minY = Mth.floor(box.minY);
final int minZ = Mth.floor(box.minZ);
final int maxX = Mth.floor(box.maxX);
final int maxY = Mth.floor(box.maxY);
final int maxZ = Mth.floor(box.maxZ);
final int maxBlockX = Mth.floor(boundingBox.maxX);
final int maxBlockY = Mth.floor(boundingBox.maxY);
final int maxBlockZ = Mth.floor(boundingBox.maxZ);
final ChunkSource chunkProvider = this.level.getChunkSource();
final int minChunkX = minBlockX >> 4;
final int minChunkY = minBlockY >> 4;
final int minChunkZ = minBlockZ >> 4;
long lastChunkKey = ChunkPos.INVALID_CHUNK_POS;
LevelChunk lastChunk = null;
for (int fz = minZ; fz <= maxZ; ++fz) {
tempPos.setZ(fz);
for (int fx = minX; fx <= maxX; ++fx) {
final int newChunkX = fx >> 4;
final int newChunkZ = fz >> 4;
final LevelChunk chunk = lastChunkKey == (lastChunkKey = CoordinateUtils.getChunkKey(newChunkX, newChunkZ)) ?
lastChunk : (lastChunk = (LevelChunk)chunkProvider.getChunk(newChunkX, newChunkZ, ChunkStatus.FULL, true));
tempPos.setX(fx);
for (int fy = minY; fy <= maxY; ++fy) {
tempPos.setY(fy);
final int maxChunkX = maxBlockX >> 4;
final int maxChunkY = maxBlockY >> 4;
final int maxChunkZ = maxBlockZ >> 4;
final BlockState state = chunk.getBlockState(tempPos);
final int minSection = WorldUtil.getMinSection(world);
final ChunkSource chunkSource = world.getChunkSource();
final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
if (((CollisionBlockState)state).moonrise$emptyCollisionShape() || !state.isSuffocating(this.level, tempPos)) {
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
final LevelChunkSection[] sections = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, true).getSections();
for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) {
final int sectionIdx = currChunkY - minSection;
if (sectionIdx < 0 || sectionIdx >= sections.length) {
continue;
}
final LevelChunkSection section = sections[sectionIdx];
if (section.hasOnlyAir()) {
// empty
continue;
}
// Yes, it does not use the Entity context stuff.
final VoxelShape collisionShape = state.getCollisionShape(this.level, tempPos);
final PalettedContainer<BlockState> blocks = section.states;
if (collisionShape.isEmpty()) {
continue;
}
final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) : 0;
final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) : 15;
final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) : 0;
final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) : 15;
final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) : 0;
final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) : 15;
final AABB toCollide = box.move(-(double)fx, -(double)fy, -(double)fz);
for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
final int blockY = currY | (currChunkY << 4);
mutablePos.setY(blockY);
for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
final int blockZ = currZ | (currChunkZ << 4);
mutablePos.setZ(blockZ);
for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
final int blockX = currX | (currChunkX << 4);
mutablePos.setX(blockX);
final AABB singleAABB = ((CollisionVoxelShape)collisionShape).moonrise$getSingleAABBRepresentation();
if (singleAABB != null) {
if (CollisionUtil.voxelShapeIntersect(singleAABB, toCollide)) {
return true;
final BlockState blockState = blocks.get((currX) | (currZ << 4) | ((currY) << 8));
if (((CollisionBlockState)blockState).moonrise$emptyCollisionShape()
|| !blockState.isSuffocating(world, mutablePos)) {
continue;
}
// Yes, it does not use the Entity context stuff.
final VoxelShape collisionShape = blockState.getCollisionShape(world, mutablePos);
if (collisionShape.isEmpty()) {
continue;
}
final AABB toCollide = boundingBox.move(-(double)blockX, -(double)blockY, -(double)blockZ);
final AABB singleAABB = ((CollisionVoxelShape)collisionShape).moonrise$getSingleAABBRepresentation();
if (singleAABB != null) {
if (CollisionUtil.voxelShapeIntersect(singleAABB, toCollide)) {
return true;
}
continue;
}
if (CollisionUtil.voxelShapeIntersectNoEmpty(collisionShape, toCollide)) {
return true;
}
continue;
}
}
continue;
}
if (CollisionUtil.voxelShapeIntersectNoEmpty(collisionShape, toCollide)) {
return true;
}
continue;
}
}
}

View File

@@ -1,11 +1,10 @@
package ca.spottedleaf.moonrise.mixin.collisions;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState;
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
import ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
@@ -16,6 +15,7 @@ import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
@@ -27,20 +27,18 @@ import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Mixin(Level.class)
abstract class LevelMixin implements CollisionLevel, LevelAccessor, AutoCloseable {
abstract class LevelMixin implements LevelAccessor, AutoCloseable {
@Shadow
public abstract LevelChunk getChunk(int x, int z);
@@ -48,38 +46,6 @@ abstract class LevelMixin implements CollisionLevel, LevelAccessor, AutoCloseabl
@Shadow
public abstract WorldBorder getWorldBorder();
@Unique
private int minSection;
@Unique
private int maxSection;
@Override
public final int moonrise$getMinSection() {
return this.minSection;
}
@Override
public final int moonrise$getMaxSection() {
return this.maxSection;
}
/**
* @reason Init min/max section
* @author Spottedleaf
*/
@Inject(
method = "<init>",
at = @At(
value = "RETURN"
)
)
private void init(final CallbackInfo ci) {
this.minSection = WorldUtil.getMinSection(this);
this.maxSection = WorldUtil.getMaxSection(this);
}
/**
* Route to faster lookup.
* See {@link EntityGetterMixin#isUnobstructed(Entity, VoxelShape)} for expected behavior
@@ -117,7 +83,7 @@ abstract class LevelMixin implements CollisionLevel, LevelAccessor, AutoCloseabl
final Vec3 to = clipContext.getTo();
final Vec3 from = clipContext.getFrom();
return BlockHitResult.miss(to, Direction.getNearest(from.x - to.x, from.y - to.y, from.z - to.z), BlockPos.containing(to.x, to.y, to.z));
return BlockHitResult.miss(to, Direction.getApproximateNearest(from.x - to.x, from.y - to.y, from.z - to.z), BlockPos.containing(to.x, to.y, to.z));
}
@Unique
@@ -173,7 +139,7 @@ abstract class LevelMixin implements CollisionLevel, LevelAccessor, AutoCloseabl
int lastChunkY = Integer.MIN_VALUE;
int lastChunkZ = Integer.MIN_VALUE;
final int minSection = ((CollisionLevel)level).moonrise$getMinSection();
final int minSection = WorldUtil.getMinSection(level);
for (;;) {
currPos.set(currX, currY, currZ);
@@ -308,7 +274,7 @@ abstract class LevelMixin implements CollisionLevel, LevelAccessor, AutoCloseabl
*/
@Override
public Optional<Vec3> findFreePosition(final Entity entity, final VoxelShape boundsShape, final Vec3 fromPosition,
final double rangeX, final double rangeY, final double rangeZ) {
final double rangeX, final double rangeY, final double rangeZ) {
if (boundsShape.isEmpty()) {
return Optional.empty();
}
@@ -368,102 +334,138 @@ abstract class LevelMixin implements CollisionLevel, LevelAccessor, AutoCloseabl
*/
@Override
public Optional<BlockPos> findSupportingBlock(final Entity entity, final AABB aabb) {
final int minSection = WorldUtil.getMinSection((Level)(Object)this);
final int minBlockX = Mth.floor(aabb.minX - CollisionUtil.COLLISION_EPSILON) - 1;
final int maxBlockX = Mth.floor(aabb.maxX + CollisionUtil.COLLISION_EPSILON) + 1;
final int minBlockY = Mth.floor(aabb.minY - CollisionUtil.COLLISION_EPSILON) - 1;
final int maxBlockY = Mth.floor(aabb.maxY + CollisionUtil.COLLISION_EPSILON) + 1;
final int minBlockY = Math.max((minSection << 4) - 1, Mth.floor(aabb.minY - CollisionUtil.COLLISION_EPSILON) - 1);
final int maxBlockY = Math.min((WorldUtil.getMaxSection((Level)(Object)this) << 4) + 16, Mth.floor(aabb.maxY + CollisionUtil.COLLISION_EPSILON) + 1);
final int minBlockZ = Mth.floor(aabb.minZ - CollisionUtil.COLLISION_EPSILON) - 1;
final int maxBlockZ = Mth.floor(aabb.maxZ + CollisionUtil.COLLISION_EPSILON) + 1;
CollisionUtil.LazyEntityCollisionContext collisionContext = null;
final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
final CollisionContext collisionShape = new CollisionUtil.LazyEntityCollisionContext(entity);
BlockPos selected = null;
double selectedDistance = Double.MAX_VALUE;
final Vec3 entityPos = entity.position();
LevelChunk lastChunk = null;
int lastChunkX = Integer.MIN_VALUE;
int lastChunkZ = Integer.MIN_VALUE;
// special cases:
if (minBlockY > maxBlockY) {
// no point in checking
return Optional.empty();
}
final int minChunkX = minBlockX >> 4;
final int maxChunkX = maxBlockX >> 4;
final int minChunkY = minBlockY >> 4;
final int maxChunkY = maxBlockY >> 4;
final int minChunkZ = minBlockZ >> 4;
final int maxChunkZ = maxBlockZ >> 4;
final ChunkSource chunkSource = this.getChunkSource();
for (int currZ = minBlockZ; currZ <= maxBlockZ; ++currZ) {
pos.setZ(currZ);
for (int currX = minBlockX; currX <= maxBlockX; ++currX) {
pos.setX(currX);
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
final ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, false);
final int newChunkX = currX >> 4;
final int newChunkZ = currZ >> 4;
if (((newChunkX ^ lastChunkX) | (newChunkZ ^ lastChunkZ)) != 0) {
lastChunkX = newChunkX;
lastChunkZ = newChunkZ;
lastChunk = (LevelChunk)chunkSource.getChunk(newChunkX, newChunkZ, ChunkStatus.FULL, false);
}
if (lastChunk == null) {
if (chunk == null) {
continue;
}
for (int currY = minBlockY; currY <= maxBlockY; ++currY) {
int edgeCount = ((currX == minBlockX || currX == maxBlockX) ? 1 : 0) +
((currY == minBlockY || currY == maxBlockY) ? 1 : 0) +
((currZ == minBlockZ || currZ == maxBlockZ) ? 1 : 0);
if (edgeCount == 3) {
final LevelChunkSection[] sections = chunk.getSections();
// bound y
for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) {
final int sectionIdx = currChunkY - minSection;
if (sectionIdx < 0 || sectionIdx >= sections.length) {
continue;
}
final LevelChunkSection section = sections[sectionIdx];
if (section.hasOnlyAir()) {
// empty
continue;
}
pos.setY(currY);
final boolean hasSpecial = ((BlockCountingChunkSection)section).moonrise$hasSpecialCollidingBlocks();
final int sectionAdjust = !hasSpecial ? 1 : 0;
final double distance = pos.distToCenterSqr(entityPos);
if (distance > selectedDistance || (distance == selectedDistance && selected.compareTo(pos) >= 0)) {
continue;
}
final PalettedContainer<BlockState> blocks = section.states;
final BlockState state = ((GetBlockChunk)lastChunk).moonrise$getBlock(currX, currY, currZ);
if (((CollisionBlockState)state).moonrise$emptyContextCollisionShape()) {
continue;
}
final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) + sectionAdjust : 0;
final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) - sectionAdjust : 15;
final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) + sectionAdjust : 0;
final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) - sectionAdjust : 15;
final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) + sectionAdjust : 0;
final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) - sectionAdjust : 15;
VoxelShape blockCollision = ((CollisionBlockState)state).moonrise$getConstantContextCollisionShape();
for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
final int blockY = currY | (currChunkY << 4);
mutablePos.setY(blockY);
for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
final int blockZ = currZ | (currChunkZ << 4);
mutablePos.setZ(blockZ);
for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
final int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8);
final int blockX = currX | (currChunkX << 4);
mutablePos.setX(blockX);
if ((edgeCount != 1 || state.hasLargeCollisionShape()) && (edgeCount != 2 || state.getBlock() == Blocks.MOVING_PISTON)) {
if (collisionContext == null) {
collisionContext = new CollisionUtil.LazyEntityCollisionContext(entity);
}
final int edgeCount = hasSpecial ? ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0) : 0;
if (edgeCount == 3) {
continue;
}
if (blockCollision == null) {
blockCollision = state.getCollisionShape((Level)(Object)this, pos, collisionContext);
}
final double distance = mutablePos.distToCenterSqr(entityPos);
if (distance > selectedDistance || (distance == selectedDistance && selected.compareTo(mutablePos) >= 0)) {
continue;
}
if (blockCollision.isEmpty()) {
continue;
}
final BlockState blockData = blocks.get(localBlockIndex);
// avoid VoxelShape#move by shifting the entity collision shape instead
final AABB shiftedAABB = aabb.move(-(double)currX, -(double)currY, -(double)currZ);
if (((CollisionBlockState)blockData).moonrise$emptyContextCollisionShape()) {
continue;
}
final AABB singleAABB = ((CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation();
if (singleAABB != null) {
if (!CollisionUtil.voxelShapeIntersect(singleAABB, shiftedAABB)) {
continue;
VoxelShape blockCollision = ((CollisionBlockState)blockData).moonrise$getConstantContextCollisionShape();
if (edgeCount == 0 || ((edgeCount != 1 || blockData.hasLargeCollisionShape()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON))) {
if (blockCollision == null) {
blockCollision = blockData.getCollisionShape((Level)(Object)this, mutablePos, collisionShape);
if (blockCollision.isEmpty()) {
continue;
}
}
// avoid VoxelShape#move by shifting the entity collision shape instead
final AABB shiftedAABB = aabb.move(-(double)blockX, -(double)blockY, -(double)blockZ);
final AABB singleAABB = ((CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation();
if (singleAABB != null) {
if (!CollisionUtil.voxelShapeIntersect(singleAABB, shiftedAABB)) {
continue;
}
selected = mutablePos.immutable();
selectedDistance = distance;
continue;
}
if (!CollisionUtil.voxelShapeIntersectNoEmpty(blockCollision, shiftedAABB)) {
continue;
}
selected = mutablePos.immutable();
selectedDistance = distance;
continue;
}
}
selected = pos.immutable();
selectedDistance = distance;
continue;
}
if (!CollisionUtil.voxelShapeIntersectNoEmpty(blockCollision, shiftedAABB)) {
continue;
}
selected = pos.immutable();
selectedDistance = distance;
continue;
}
}
}

View File

@@ -4,9 +4,7 @@ import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import net.minecraft.client.renderer.block.LiquidBlockRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.ArrayVoxelShape;
import net.minecraft.world.phys.shapes.BooleanOp;
@@ -23,12 +21,7 @@ abstract class LiquidBlockRendererMixin {
* @author Spottedleaf
*/
@Overwrite
private static boolean isFaceOccludedByState(final BlockGetter world, final Direction direction, final float height,
final BlockPos pos, final BlockState state) {
if (!state.canOcclude()) {
return false;
}
private static boolean isFaceOccludedByState(final Direction direction, final float height, final BlockState state) {
// check for created shape is empty
if (height < (float)CollisionUtil.COLLISION_EPSILON) {
return false;
@@ -50,25 +43,26 @@ abstract class LiquidBlockRendererMixin {
} else {
// the extrusion includes the height
heightShape = new ArrayVoxelShape(
Shapes.block().shape,
CollisionUtil.ZERO_ONE,
DoubleArrayList.wrap(new double[] { 0.0, heightDouble }),
CollisionUtil.ZERO_ONE
Shapes.block().shape,
CollisionUtil.ZERO_ONE,
DoubleArrayList.wrap(new double[] { 0.0, heightDouble }),
CollisionUtil.ZERO_ONE
);
}
final VoxelShape stateShape = ((CollisionVoxelShape)state.getOcclusionShape(world, pos)).moonrise$getFaceShapeClamped(direction.getOpposite());
final VoxelShape occlusionShape = ((CollisionVoxelShape)state.getFaceOcclusionShape(direction.getOpposite()))
.moonrise$getFaceShapeClamped(direction.getOpposite());
if (stateShape.isEmpty()) {
if (occlusionShape.isEmpty()) {
// cannot occlude
return false;
}
// fast check for box
if (heightShape == stateShape) {
if (heightShape == occlusionShape) {
return true;
}
return !Shapes.joinIsNotEmpty(heightShape, stateShape, BooleanOp.ONLY_FIRST);
return !Shapes.joinIsNotEmpty(heightShape, occlusionShape, BooleanOp.ONLY_FIRST);
}
}

View File

@@ -1,55 +0,0 @@
package ca.spottedleaf.moonrise.mixin.collisions;
import net.minecraft.world.entity.Attackable;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySelector;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.List;
@Mixin(LivingEntity.class)
abstract class LivingEntityMixin extends Entity implements Attackable {
@Shadow
protected abstract void doPush(Entity entity);
public LivingEntityMixin(EntityType<?> entityType, Level level) {
super(entityType, level);
}
/**
* @reason Optimise this method
* @author Spottedleaf
*/
@Overwrite
public void pushEntities() {
if (this.level().isClientSide()) {
final List<Player> players = this.level().getEntitiesOfClass(Player.class, this.getBoundingBox(), EntitySelector.pushableBy(this));
for (int i = 0, len = players.size(); i < len; ++i) {
this.doPush(players.get(i));
}
} else {
final List<Entity> nearby = this.level().getEntities(this, this.getBoundingBox(), EntitySelector.pushableBy(this));
// only iterate ONCE
int nonPassengers = 0;
for (int i = 0, len = nearby.size(); i < len; ++i) {
final Entity entity = nearby.get(i);
nonPassengers += (entity.isPassenger() ? 1 : 0);
this.doPush(entity);
}
int maxCramming;
if (nonPassengers != 0 && (maxCramming = this.level().getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING)) > 0
&& nonPassengers > (maxCramming - 1) && this.random.nextInt(4) == 0) {
this.hurt(this.damageSources().cramming(), 6.0F);
}
}
}
}

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