Skip to content

Commit 83eae99

Browse files
authored
Merge branch 'ryanhcode:main' into main
2 parents 63f3e5f + 60b31d2 commit 83eae99

84 files changed

Lines changed: 1953 additions & 812 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ view [Sable Companion](https://github.com/ryanhcode/sable-companion).
2424

2525
View the [Sable Developer Wiki](https://github.com/ryanhcode/sable/wiki) for documentation and guides.
2626

27+
Join sable zone for development discussion: https://discord.gg/pnkzu2dtVA
28+
2729
# Building Rust Natives
2830

2931
1. Install Docker from https://www.docker.com/get-started/ or from your relevant package manager

buildSrc/src/main/groovy/multiloader-common.gradle

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,22 @@ repositories {
8888
maven {
8989
url = "https://maven.ryanhcode.dev/releases"
9090
}
91+
92+
exclusiveContent { // Sodium
93+
forRepository {
94+
maven {
95+
name "CaffeineMC"
96+
url "https://maven.caffeinemc.net/releases" // or /snapshots
97+
}
98+
}
99+
filter {
100+
includeGroup("net.caffeinemc")
101+
}
102+
}
91103
}
92104

93105
dependencies {
94-
compileOnly "maven.modrinth:lithium:mc1.21.1-0.15.3-neoforge"
95-
compileOnly "maven.modrinth:sodium:$sodium_version"
106+
compileOnly "net.caffeinemc:sodium-neoforge-mod:$sodium_version"
96107
compileOnly "maven.modrinth:iris:$iris_version"
97108
compileOnly "maven.modrinth:distanthorizons:$distant_horizons_version"
98109
compileOnly("cc.tweaked:cc-tweaked-$minecraft_version-forge:$cc_tweaked_version")
@@ -169,6 +180,7 @@ processResources {
169180
'credits' : credits,
170181
'java_version' : java_version,
171182
'veil_version' : veil_version,
183+
'sodium_version' : sodium_version,
172184
'sable_companion_version' : sable_companion_version
173185
]
174186

common/src/main/java/dev/ryanhcode/sable/Sable.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import dev.ryanhcode.sable.api.physics.force.ForceGroups;
66
import dev.ryanhcode.sable.api.sublevel.ServerSubLevelContainer;
77
import dev.ryanhcode.sable.api.sublevel.SubLevelContainer;
8+
import dev.ryanhcode.sable.api.sublevel.SubLevelTicketLoadingSystem;
89
import dev.ryanhcode.sable.companion.SableCompanion;
910
import dev.ryanhcode.sable.index.SableTags;
1011
import dev.ryanhcode.sable.network.tcp.SableTCPPackets;
@@ -83,6 +84,7 @@ public static void defaultSubLevelContainerInitializer(final Level level, final
8384
serverContainer.addObserver(physicsSystem);
8485
serverContainer.addObserver(trackingSystem);
8586
serverContainer.addObserver(new SubLevelTrackingPointObserver(serverLevel));
87+
serverContainer.addObserver(new SubLevelTicketLoadingSystem(serverContainer));
8688

8789
PhysicsBlockPropertiesDefinitionLoader.INSTANCE.applyAll();
8890
}

common/src/main/java/dev/ryanhcode/sable/SableClientConfig.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import dev.ryanhcode.sable.sublevel.ClientSubLevel;
1010
import dev.ryanhcode.sable.sublevel.SubLevel;
1111
import dev.ryanhcode.sable.sublevel.render.SubLevelRenderer;
12+
import foundry.veil.Veil;
1213
import foundry.veil.api.client.render.VeilRenderSystem;
1314
import net.minecraft.client.Minecraft;
1415
import net.minecraft.client.multiplayer.ClientLevel;
@@ -21,16 +22,19 @@ public final class SableClientConfig {
2122

2223
public static final ModConfigSpec SPEC;
2324

25+
public static final ModConfigSpec.BooleanValue ATTEMPT_UDP_NETWORKING;
2426
public static final ModConfigSpec.BooleanValue SUB_LEVEL_DYNAMIC_SHADING;
2527
public static final ModConfigSpec.BooleanValue SUB_LEVEL_WATER_OCCLUSION;
2628
public static final ModConfigSpec.BooleanValue SUB_LEVEL_SKYLIGHT_SHADOWS;
29+
public static final ModConfigSpec.BooleanValue DEBUG_DRAW_LOADED_CHUNKS;
2730
public static final ModConfigSpec.DoubleValue INTERPOLATION_DELAY;
2831
public static final ModConfigSpec.EnumValue<SubLevelRenderer.SelectedRenderer> SELECTED_RENDERER;
2932
public static final ModConfigSpec.DoubleValue ZOOM_SENSITIVITY;
3033

3134
static {
3235
final ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
3336

37+
3438
SUB_LEVEL_DYNAMIC_SHADING = builder
3539
.comment("Whether sub-levels should apply block shading dynamically")
3640
.define("sub_level_dynamic_shading", true);
@@ -40,6 +44,9 @@ public final class SableClientConfig {
4044
SUB_LEVEL_SKYLIGHT_SHADOWS = builder
4145
.comment("Whether sub-levels should cast a shadow on the world")
4246
.define("sub_level_skylight_shadows", false);
47+
DEBUG_DRAW_LOADED_CHUNKS = builder
48+
.comment("Whether to draw loaded chunks on the client in the chunk debug renderer")
49+
.define("debug_draw_loaded_chunks", Veil.platform().isDevelopmentEnvironment());
4350
INTERPOLATION_DELAY = builder
4451
.comment("The distance back in game-ticks that the snapshot interpolation should operate")
4552
.defineInRange("sub_level_snapshot_interpolation_delay_ticks", 1.5, 0.0, 100.0);
@@ -51,7 +58,9 @@ public final class SableClientConfig {
5158
ZOOM_SENSITIVITY = builder
5259
.comment("The zoom sensitivity for sub-level camera types")
5360
.defineInRange("sub_level_zoom_sensitivity", 0.2, 0.0, 100.0);
54-
61+
ATTEMPT_UDP_NETWORKING = builder
62+
.comment("If Sable should attempt to establish a UDP connection with the server, to receive sub-level movement data over a UDP channel")
63+
.define("attempt_udp_networking", true);
5564

5665
SPEC = builder.build();
5766
}

common/src/main/java/dev/ryanhcode/sable/SableConfig.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public final class SableConfig {
1717
public static final ModConfigSpec.IntValue SUB_LEVEL_PUNCH_COOLDOWN_TICKS;
1818
public static final ModConfigSpec.BooleanValue DISABLE_UDP_PIPELINE;
1919
public static final ModConfigSpec.BooleanValue ATTEMPT_UDP_NETWORKING;
20+
public static final ModConfigSpec.BooleanValue VERBOSE_SERIALIZATION_LOGGING;
21+
public static final ModConfigSpec.BooleanValue SUB_LEVEL_SAVING_LOG_MESSAGE;
2022

2123
static {
2224
final ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
@@ -54,8 +56,14 @@ public final class SableConfig {
5456
.comment("If the entire Sable UDP Networking pipeline should be disabled. This can improve compatibility with certain mods like Replay mod and certain networking setups, but will have worse performance and latency for networking sub-levels.")
5557
.define("disable_udp_pipeline", false);
5658
ATTEMPT_UDP_NETWORKING = builder
57-
.comment("If Sable should attempt to authenticate with clients and send them sub-level data over UDP")
59+
.comment("If Sable should attempt to authenticate with clients and send them sub-level movement data over UDP")
5860
.define("attempt_udp_networking", true);
61+
SUB_LEVEL_SAVING_LOG_MESSAGE = builder
62+
.comment("If Sable should log when saving sub-levels for a dimension.")
63+
.define("sub_level_saving_log_message", true);
64+
VERBOSE_SERIALIZATION_LOGGING = builder
65+
.comment("If Sable should use verbose logging for its serialization system and the holding chunk-map. Not recommended- for debugging purposes only.")
66+
.define("verbose_serialization_logging", false);
5967

6068
SPEC = builder.build();
6169
}

common/src/main/java/dev/ryanhcode/sable/api/SubLevelAssemblyHelper.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import dev.ryanhcode.sable.companion.math.BoundingBox3ic;
1111
import dev.ryanhcode.sable.companion.math.JOMLConversion;
1212
import dev.ryanhcode.sable.companion.math.Pose3d;
13+
import dev.ryanhcode.sable.index.SableTags;
1314
import dev.ryanhcode.sable.platform.SableAssemblyPlatform;
1415
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
1516
import dev.ryanhcode.sable.sublevel.SubLevel;
@@ -348,11 +349,19 @@ public static void moveBlocks(final ServerLevel level, final AssemblyTransform t
348349
tag.putInt("z", newPos.getZ());
349350
}
350351

351-
if (blockEntity instanceof final RandomizableContainer container) {
352-
container.setLootTable(null);
353-
}
354-
if (blockEntity instanceof final Clearable clearable) {
355-
clearable.clearContent();
352+
if (state.is(SableTags.SILENT_ASSEMBLY_REMOVAL)) {
353+
level.removeBlockEntity(block);
354+
} else {
355+
// This is the "correct" way to remove a block from the world, but many mods do not implement
356+
// Clearable correctly. The above tag exists to allow this issue to be "fixed" on a case-by-case
357+
// basis without updating a mod's code
358+
//
359+
// A real solution is to implement Clearable on all block entities that can be cleared in the
360+
// same way as Vanilla MC. See SetBlockCommand
361+
if (blockEntity instanceof final RandomizableContainer container) {
362+
container.setLootTable(null);
363+
}
364+
Clearable.tryClear(blockEntity);
356365
}
357366

358367
final LevelChunk chunk = resultingAccelerator.getChunk(SectionPos.blockToSectionCoord(newPos.getX()), SectionPos.blockToSectionCoord(newPos.getZ()));

common/src/main/java/dev/ryanhcode/sable/api/sublevel/ServerSubLevelContainer.java

Lines changed: 159 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
package dev.ryanhcode.sable.api.sublevel;
22

3-
43
import dev.ryanhcode.sable.Sable;
4+
import dev.ryanhcode.sable.SableConfig;
5+
import dev.ryanhcode.sable.api.SubLevelHelper;
6+
import dev.ryanhcode.sable.api.sublevel.ticket.SubLevelLoadingTicket;
7+
import dev.ryanhcode.sable.api.sublevel.ticket.SubLevelLoadingTicketType;
8+
import dev.ryanhcode.sable.api.sublevel.ticket.SubLevelTicketInfo;
59
import dev.ryanhcode.sable.companion.math.Pose3d;
610
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
711
import dev.ryanhcode.sable.sublevel.SubLevel;
812
import dev.ryanhcode.sable.sublevel.storage.SubLevelOccupancySavedData;
913
import dev.ryanhcode.sable.sublevel.storage.SubLevelRemovalReason;
14+
import dev.ryanhcode.sable.sublevel.storage.SubLevelTicketsSavedData;
15+
import dev.ryanhcode.sable.sublevel.storage.holding.GlobalSavedSubLevelPointer;
1016
import dev.ryanhcode.sable.sublevel.storage.holding.SubLevelHoldingChunkMap;
1117
import dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem;
1218
import dev.ryanhcode.sable.sublevel.system.SubLevelTrackingSystem;
19+
import it.unimi.dsi.fastutil.objects.*;
1320
import net.minecraft.core.BlockPos;
1421
import net.minecraft.core.Holder;
1522
import net.minecraft.resources.ResourceKey;
@@ -21,9 +28,7 @@
2128
import org.jetbrains.annotations.Nullable;
2229
import org.joml.Vector3d;
2330

24-
import java.util.List;
25-
import java.util.Optional;
26-
import java.util.UUID;
31+
import java.util.*;
2732

2833
/**
2934
* Holds all sub-levels and plots in a {@link ServerLevel}
@@ -45,6 +50,16 @@ public class ServerSubLevelContainer extends SubLevelContainer {
4550
*/
4651
private SubLevelHoldingChunkMap holdingChunkMap;
4752

53+
/**
54+
* All active sub-level loading tickets
55+
*/
56+
protected final Object2ObjectMap<ServerSubLevel, ObjectSet<SubLevelLoadingTicket<?>>> activeTickets = new Object2ObjectOpenHashMap<>();
57+
58+
/**
59+
* All sub-level loading tickets
60+
*/
61+
protected final Object2ObjectMap<UUID, SubLevelTicketInfo> allTickets = new Object2ObjectOpenHashMap<>();
62+
4863
/**
4964
* Creates a new sub-level container with the given side length and plot size.
5065
*
@@ -63,6 +78,8 @@ public ServerSubLevelContainer(final Level level, final int logSideLength, final
6378
*/
6479
public void initialize() {
6580
this.holdingChunkMap = new SubLevelHoldingChunkMap(this.getLevel(), this);
81+
82+
this.loadForceLoadedSubLevels();
6683
}
6784

6885
/**
@@ -169,10 +186,148 @@ public ServerLevel getLevel() {
169186
return (ServerLevel) super.getLevel();
170187
}
171188

189+
/**
190+
* Adds a sub-level force-loading ticket
191+
*
192+
* @param subLevel the loaded sub-level to add the ticket to
193+
* @param ticketType the type of ticket to add to the sub-level
194+
* @param key the key of the ticket. This will be used to identify the ticket to remove it.
195+
* Two tickets with the same key on the same sub-level cannot exist
196+
*
197+
* @return true if the ticket was added (and did not previously exist)
198+
*/
199+
public <T> boolean addForceLoadTicket(final ServerSubLevel subLevel, final SubLevelLoadingTicketType<T> ticketType, final T key) {
200+
final UUID uuid = subLevel.getUniqueId();
201+
final SubLevelLoadingTicket<T> ticket = new SubLevelLoadingTicket<>(ticketType, uuid, key);
202+
203+
final ObjectSet<SubLevelLoadingTicket<?>> loadedSet = this.activeTickets.computeIfAbsent(subLevel, (ignored) -> new ObjectArraySet<>());
204+
final SubLevelTicketInfo allSet = this.allTickets.computeIfAbsent(uuid, (ignored) -> new SubLevelTicketInfo());
205+
loadedSet.add(ticket);
206+
207+
if (allSet.tickets().add(ticket)) {
208+
SubLevelTicketsSavedData.getOrLoad(this.getLevel()).setDirty();
209+
return true;
210+
}
211+
212+
return false;
213+
}
214+
215+
/**
216+
* Removes a sub-level force-loading ticket
217+
*
218+
* @param subLevel the loaded sub-level to remove the ticket from
219+
* @param ticketType the type of ticket to add to the sub-level
220+
* @param key the key of the ticket. This will be used to identify the ticket to remove it.
221+
* Two tickets with the same key on the same sub-level cannot exist
222+
*
223+
* @return true if the ticket existed and was removed
224+
*/
225+
public <T> boolean removeForceLoadTicket(final ServerSubLevel subLevel, final SubLevelLoadingTicketType<T> ticketType, final T key) {
226+
final UUID uuid = subLevel.getUniqueId();
227+
final SubLevelLoadingTicket<T> ticket = new SubLevelLoadingTicket<>(ticketType, uuid, key);
228+
229+
final ObjectSet<SubLevelLoadingTicket<?>> loadedSet = this.activeTickets.get(subLevel);
230+
final SubLevelTicketInfo allSet = this.allTickets.get(subLevel.getUniqueId());
231+
232+
if (loadedSet != null) {
233+
loadedSet.remove(ticket);
234+
235+
if (loadedSet.isEmpty()) {
236+
this.activeTickets.remove(subLevel);
237+
}
238+
}
239+
240+
if (allSet != null) {
241+
final boolean existed = allSet.tickets().remove(ticket);
242+
243+
if (allSet.tickets().isEmpty()) {
244+
this.allTickets.remove(subLevel.getUniqueId());
245+
}
246+
247+
if (existed) {
248+
SubLevelTicketsSavedData.getOrLoad(this.getLevel()).setDirty();
249+
return true;
250+
}
251+
}
252+
253+
return false;
254+
}
255+
256+
/**
257+
* Collect all force-loaded sub-levels (and sub-levels force-loaded through dependencies)
258+
*/
259+
public Collection<ServerSubLevel> collectForceLoadedSubLevels() {
260+
if (this.activeTickets.isEmpty()) {
261+
return List.of();
262+
}
263+
final ObjectOpenHashSet<ServerSubLevel> subLevels = new ObjectOpenHashSet<>();
264+
265+
for (final ServerSubLevel subLevel : this.activeTickets.keySet()) {
266+
if (subLevels.contains(subLevel)) {
267+
continue;
268+
}
269+
270+
subLevels.addAll(SubLevelHelper.getLoadingDependencyChain(subLevel));
271+
}
272+
273+
return subLevels;
274+
}
275+
276+
/**
277+
* Loads sub-level tickets
278+
*/
279+
@ApiStatus.Internal
280+
public void loadTickets(final Object2ObjectMap<UUID, SubLevelTicketInfo> tickets) {
281+
this.allTickets.putAll(tickets);
282+
}
283+
284+
/**
285+
* @return an immutable view of all sub-level loading tickets
286+
*/
287+
@ApiStatus.Internal
288+
public Map<UUID, SubLevelTicketInfo> getAllTickets() {
289+
return Collections.unmodifiableMap(this.allTickets);
290+
}
291+
292+
/**
293+
* Loads all force-loaded sub-levels
294+
*/
295+
private void loadForceLoadedSubLevels() {
296+
for (final Map.Entry<UUID, SubLevelTicketInfo> entry : this.allTickets.entrySet()) {
297+
final UUID uuid = entry.getKey();
298+
final GlobalSavedSubLevelPointer pointer = entry.getValue().getPointer();
299+
300+
if (pointer != null) {
301+
this.holdingChunkMap.snatchAndLoad(pointer, uuid);
302+
} else {
303+
Sable.LOGGER.error("Cannot load force-loaded sub-level with ID {} because the ticket info was not saved with a pointer", uuid);
304+
}
305+
}
306+
}
307+
172308
/**
173309
* Frees all native resources
174310
*/
311+
@ApiStatus.Internal
175312
public void close() {
313+
final List<ServerSubLevel> subLevels = new ObjectArrayList<>(this.getAllSubLevels());
314+
315+
if (!subLevels.isEmpty()) {
316+
final Map<UUID, SubLevelTicketInfo> tickets = this.getAllTickets();
317+
318+
for (final ServerSubLevel subLevel : subLevels) {
319+
if (SableConfig.VERBOSE_SERIALIZATION_LOGGING.get() && !tickets.containsKey(subLevel.getUniqueId())) {
320+
Sable.LOGGER.error("Sub-level {} was present after world closing, but is not force-loaded.", subLevel);
321+
}
322+
323+
this.removeSubLevel(subLevel, SubLevelRemovalReason.UNLOADED);
324+
}
325+
}
326+
327+
if (this.physics != null) {
328+
this.physics.getPipeline().dispose();
329+
}
330+
176331
try {
177332
this.holdingChunkMap.close();
178333
} catch (final Exception e) {

0 commit comments

Comments
 (0)