Skip to content

Commit b2fb33e

Browse files
committed
add ShipDataStorage api and fix fabric dep version range
1 parent b141d2e commit b2fb33e

10 files changed

Lines changed: 252 additions & 9 deletions

File tree

common/src/main/java/com/github/litermc/vtil/Constants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
public final class Constants {
77
public static final String MOD_ID = "vtil";
88
public static final String MOD_NAME = "vtil";
9-
public static final String MOD_VERSION = "0.5.10";
9+
public static final String MOD_VERSION = "0.6.0";
1010
public static final Logger LOG = LoggerFactory.getLogger(MOD_NAME);
1111

1212
private Constants() {}

common/src/main/java/com/github/litermc/vtil/VtilListeners.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
import com.github.litermc.vtil.accessor.ShipObjectServerAccessor;
44
import com.github.litermc.vtil.api.assemble.MoveApi;
55
import com.github.litermc.vtil.api.attachment.IServerTickListener;
6+
import com.github.litermc.vtil.api.storage.ShipDataStorage;
67
import com.github.litermc.vtil.util.LevelUtil;
78
import com.github.litermc.vtil.util.ShipQuerier;
89
import com.github.litermc.vtil.util.TaskUtil;
910

1011
import net.minecraft.server.MinecraftServer;
1112
import net.minecraft.server.level.ServerLevel;
13+
import net.minecraft.world.level.Level;
1214

1315
import org.valkyrienskies.core.api.ships.LoadedServerShip;
1416
import org.valkyrienskies.mod.common.VSGameUtilsKt;
@@ -26,10 +28,16 @@ public static void onModInit() {
2628
}
2729

2830
public static void onServerLevelLoad(final ServerLevel level) {
31+
if (level.dimension() == Level.OVERWORLD) {
32+
ShipDataStorage.onOverworldLoad(level);
33+
}
2934
LevelUtil.onServerLevelLoad(level);
3035
}
3136

3237
public static void onServerLevelUnload(final ServerLevel level) {
38+
if (level.dimension() == Level.OVERWORLD) {
39+
ShipDataStorage.onOverworldLoad(null);
40+
}
3341
LevelUtil.onServerLevelUnload(level);
3442
TaskUtil.onServerLevelUnload(level);
3543
}

common/src/main/java/com/github/litermc/vtil/api/assemble/ShipAllocator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.github.litermc.vtil.accessor.ShipObjectServerAccessor;
66
import com.github.litermc.vtil.accessor.ShipObjectServerWorldAccessor;
77
import com.github.litermc.vtil.api.attachment.IPermanentAttachment;
8+
import com.github.litermc.vtil.api.storage.ShipDataStorage;
89
import com.github.litermc.vtil.config.Config;
910
import com.github.litermc.vtil.platform.PlatformHelper;
1011
import com.github.litermc.vtil.util.LevelUtil;
@@ -139,6 +140,7 @@ public boolean contains(final long shipId) {
139140
*/
140141
public boolean putShip(final ServerShip ship) {
141142
final long shipId = ship.getId();
143+
ShipDataStorage.onShipRemoved(shipId);
142144
final ServerLevel level = LevelUtil.getLevel(ship.getChunkClaimDimension());
143145
ship.setSlug(REUSABLE_SHIP_SLUG_PREFIX + shipId);
144146
ship.setStatic(true);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.github.litermc.vtil.api.storage;
2+
3+
import net.minecraft.nbt.CompoundTag;
4+
5+
/**
6+
* Data classes must extends this interface to be use with {@link ShipDataStorage}.
7+
* Any data classes must also define a public no‑argument constructor.
8+
*/
9+
public interface IShipAdditionalData {
10+
/**
11+
* load will be invoked when a new instance just created and have saved serialized data.
12+
* @param data the serialized data, should never be modified.
13+
*/
14+
void load(CompoundTag data);
15+
16+
/**
17+
* save will be invoked when data needs to be serialized and saved.
18+
* @param data the serialized data storage. Should be modified in the method only.
19+
*/
20+
void save(CompoundTag data);
21+
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package com.github.litermc.vtil.api.storage;
2+
3+
import com.github.litermc.vtil.Constants;
4+
5+
import net.minecraft.nbt.CompoundTag;
6+
import net.minecraft.server.level.ServerLevel;
7+
import net.minecraft.world.level.saveddata.SavedData;
8+
9+
import org.valkyrienskies.core.api.ships.PhysShip;
10+
import org.valkyrienskies.core.api.ships.ServerShip;
11+
import org.valkyrienskies.core.api.ships.Ship;
12+
13+
import java.lang.reflect.Constructor;
14+
import java.lang.reflect.InvocationTargetException;
15+
import java.lang.reflect.Modifier;
16+
import java.util.Map;
17+
import java.util.concurrent.ConcurrentHashMap;
18+
import java.util.function.Supplier;
19+
20+
public final class ShipDataStorage extends SavedData {
21+
private static final String DATA_NAME = Constants.MOD_ID + "_ShipAdditionalDatas";
22+
private static volatile ShipDataStorage INSTANCE = null;
23+
24+
private final Map<Long, Holder> holders = new ConcurrentHashMap<>();
25+
26+
private ShipDataStorage() {}
27+
28+
private ShipDataStorage(final CompoundTag shipsData) {
29+
for (final String shipIdHex : shipsData.getAllKeys()) {
30+
final long shipId = Long.parseUnsignedLong(shipIdHex, 16);
31+
this.holders.put(shipId, this.new Holder(shipId, shipsData.getCompound(shipIdHex)));
32+
}
33+
}
34+
35+
public static Holder get(final Ship ship) {
36+
if (ship instanceof ServerShip || ship instanceof PhysShip) {
37+
return get(ship.getId());
38+
}
39+
throw new IllegalArgumentException("Unexpected ship " + ship.getClass().getName() + " must be either ServerShip or PhysShip");
40+
}
41+
42+
public static Holder get(final long shipId) {
43+
final ShipDataStorage instance = INSTANCE;
44+
if (instance == null) {
45+
throw new IllegalStateException("ShipDataStorage is not initialized. Is overworld loaded?");
46+
}
47+
return instance.holders.computeIfAbsent(shipId, (shipId0) -> instance.new Holder(shipId0, new CompoundTag()));
48+
}
49+
50+
/**
51+
* module-private
52+
*/
53+
public static void onShipRemoved(final long shipId) {
54+
final ShipDataStorage instance = INSTANCE;
55+
if (instance == null) {
56+
return;
57+
}
58+
instance.holders.remove(shipId);
59+
}
60+
61+
/**
62+
* module-private
63+
*/
64+
public static void onOverworldLoad(final ServerLevel overworld) {
65+
if (overworld == null) {
66+
INSTANCE = null;
67+
return;
68+
}
69+
INSTANCE = overworld.getDataStorage().computeIfAbsent(
70+
(data) -> new ShipDataStorage(data),
71+
() -> new ShipDataStorage(),
72+
DATA_NAME
73+
);
74+
}
75+
76+
@Override
77+
public boolean isDirty() {
78+
return true;
79+
}
80+
81+
@Override
82+
public CompoundTag save(final CompoundTag data) {
83+
this.holders.forEach((shipId, holder) -> {
84+
final CompoundTag tag = new CompoundTag();
85+
holder.save(tag);
86+
if (!tag.isEmpty()) {
87+
data.put(Long.toHexString(shipId), tag);
88+
}
89+
});
90+
return data;
91+
}
92+
93+
public final class Holder {
94+
private final long shipId;
95+
private final CompoundTag oldData;
96+
private final Map<Class<? extends IShipAdditionalData>, IShipAdditionalData> storages = new ConcurrentHashMap<>();
97+
98+
private Holder(final long shipId, final CompoundTag oldData) {
99+
this.shipId = shipId;
100+
this.oldData = oldData;
101+
}
102+
103+
public <T extends IShipAdditionalData> T get(final Class<T> clazz) {
104+
final T storage = (T) this.storages.get(clazz);
105+
if (storage != null) {
106+
return storage;
107+
}
108+
if (!this.oldData.contains(clazz.getName())) {
109+
return null;
110+
}
111+
validateDataClass(clazz);
112+
return (T) this.storages.computeIfAbsent(clazz, (clazz0) -> {
113+
final T newStorage = construct((Class<T>) clazz0);
114+
newStorage.load(this.oldData.getCompound(clazz0.getName()));
115+
return newStorage;
116+
});
117+
}
118+
119+
public <T extends IShipAdditionalData> T getOrCreate(final Class<T> clazz) {
120+
final T storage = (T) this.storages.get(clazz);
121+
if (storage != null) {
122+
return storage;
123+
}
124+
validateDataClass(clazz);
125+
return (T) this.storages.computeIfAbsent(clazz, (clazz0) -> {
126+
final T newStorage = construct((Class<T>) clazz0);
127+
final String className = clazz0.getName();
128+
if (this.oldData.contains(className)) {
129+
newStorage.load(this.oldData.getCompound(className));
130+
}
131+
return newStorage;
132+
});
133+
}
134+
135+
public <T extends IShipAdditionalData> T getOrCreate(final Class<T> clazz, final Supplier<T> supplier) {
136+
final T storage = (T) this.storages.get(clazz);
137+
if (storage != null) {
138+
return storage;
139+
}
140+
validateDataClass(clazz);
141+
return (T) this.storages.computeIfAbsent(clazz, (clazz0) -> {
142+
final T newStorage;
143+
final String className = clazz0.getName();
144+
if (this.oldData.contains(className)) {
145+
newStorage = construct((Class<T>) clazz0);
146+
newStorage.load(this.oldData.getCompound(className));
147+
} else {
148+
newStorage = supplier.get();
149+
}
150+
return newStorage;
151+
});
152+
}
153+
154+
public <T extends IShipAdditionalData> void put(final T storage) {
155+
validateDataClass(storage.getClass());
156+
this.storages.put(storage.getClass(), storage);
157+
}
158+
159+
public <T extends IShipAdditionalData> T remove(final Class<T> clazz) {
160+
return (T) this.storages.remove(clazz);
161+
}
162+
163+
public void save(final CompoundTag data) {
164+
for (final String className : this.oldData.getAllKeys()) {
165+
data.put(className, this.oldData.get(className));
166+
}
167+
this.storages.forEach((clazz, d) -> {
168+
final CompoundTag tag = new CompoundTag();
169+
d.save(tag);
170+
data.put(clazz.getName(), tag);
171+
});
172+
}
173+
}
174+
175+
public static <T extends IShipAdditionalData> T construct(final Class<T> clazz) {
176+
try {
177+
return clazz.getConstructor().newInstance();
178+
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
179+
throw new AssertionError(e);
180+
}
181+
}
182+
183+
private static void validateDataClass(final Class<? extends IShipAdditionalData> clazz) throws IllegalArgumentException {
184+
if (clazz.getEnclosingClass() != null) {
185+
throw new IllegalArgumentException("Storage class " + clazz.getName() + " must be a top-level class");
186+
}
187+
if (!Modifier.isFinal(clazz.getModifiers())) {
188+
throw new IllegalArgumentException("Storage class " + clazz.getName() + " must be a final class");
189+
}
190+
try {
191+
clazz.getConstructor();
192+
} catch (NoSuchMethodException e) {
193+
throw new IllegalArgumentException("Storage class " + clazz.getName() + " missing public no‑argument constructor!", e);
194+
}
195+
}
196+
}

common/src/main/java/com/github/litermc/vtil/api/teleport/TeleportUtil.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.joml.Quaterniondc;
1111
import org.joml.Vector3dc;
1212
import org.valkyrienskies.core.api.ships.LoadedServerShip;
13+
import org.valkyrienskies.core.api.ships.ServerShip;
1314
import org.valkyrienskies.core.api.ships.ServerShipTransformProvider;
1415
import org.valkyrienskies.core.api.ships.properties.ShipTransform;
1516
import org.valkyrienskies.core.internal.ShipTeleportData;
@@ -26,7 +27,7 @@ public class TeleportUtil {
2627
* @param ship Teleporting ship
2728
* @param data Teleport data
2829
*/
29-
public static void teleportShip(final LoadedServerShip ship, final TeleportData data) {
30+
public static void teleportShip(final ServerShip ship, final TeleportData data) {
3031
final ServerLevel level = data.level();
3132
final String dimension = VSGameUtilsKt.getDimensionId(level);
3233
final VsiServerShipWorld world = VSGameUtilsKt.getShipObjectWorld(level);
@@ -37,8 +38,17 @@ public static void teleportShip(final LoadedServerShip ship, final TeleportData
3738
final Quaterniondc rotation = data.rotation();
3839
final Vector3dc velocity = data.velocity();
3940
final Vector3dc omega = data.omega();
41+
final double scale = ship.getTransform().getShipToWorldScaling().y();
4042

41-
final ShipTeleportData teleportData = ValkyrienSkiesMod.getVsCore().newShipTeleportData(newPos, rotation, velocity, omega, dimension, null, ship.getTransform().getPositionInShip());
43+
final ShipTeleportData teleportData = ValkyrienSkiesMod.getVsCore().newShipTeleportData(
44+
newPos,
45+
rotation,
46+
velocity,
47+
omega,
48+
dimension,
49+
scale,
50+
ship.getTransform().getPositionInShip()
51+
);
4252
world.teleportShip(ship, teleportData);
4353
if (velocity.lengthSquared() != 0 || omega.lengthSquared() != 0) {
4454
final ServerShipTransformProvider oldProvider = ship.getTransformProvider();

common/src/main/java/com/github/litermc/vtil/mixin/valkyrienskies/shadow/MixinShipObjectServerWorld.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.github.litermc.vtil.accessor.ShipObjectServerWorldAccessor;
44
import com.github.litermc.vtil.api.assemble.ShipAllocator;
5+
import com.github.litermc.vtil.api.storage.ShipDataStorage;
56
import com.github.litermc.vtil.config.Config;
67

78
import com.google.common.collect.ImmutableMap;
@@ -20,7 +21,9 @@
2021
import org.spongepowered.asm.mixin.Mixin;
2122
import org.spongepowered.asm.mixin.Shadow;
2223
import org.spongepowered.asm.mixin.injection.At;
24+
import org.spongepowered.asm.mixin.injection.Inject;
2325
import org.spongepowered.asm.mixin.injection.Slice;
26+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
2427

2528
import java.util.Collection;
2629
import java.util.Collections;
@@ -46,6 +49,11 @@ public abstract class MixinShipObjectServerWorld implements ShipObjectServerWorl
4649
return this.c();
4750
}
4851

52+
@Inject(method = "deleteShip(Lorg/valkyrienskies/core/api/ships/ServerShip;)V", at = @At("HEAD"), remap = false)
53+
public void deleteShip$head(final ServerShip ship, final CallbackInfo ci) {
54+
ShipDataStorage.onShipRemoved(ship.getId());
55+
}
56+
4957
@WrapOperation(
5058
method = M_postTick + "()V",
5159
at = @At(

fabric/src/main/resources/fabric.mod.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"fabricloader": ">=${fabric_loader_version}",
3333
"java": ">=${java_version}",
3434
"minecraft": "${minecraft_version}",
35-
"valkyrienskies": "[2.4,2.5)"
35+
"valkyrienskies": "2.4.*"
3636
},
3737
"recommends": {
3838
},

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ org.gradle.jvmargs=-Xmx6G
66
org.gradle.daemon=true
77

88
# Project
9-
version=0.5.10
9+
version=0.6.0
1010
group=com.github.litermc.vtil
1111
java_version=17
1212

mappings/vs.tiny

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ c org/valkyrienskies/core/impl/shadow/DL org/valkyrienskies/core/impl/util/Aerod
2828

2929
c org/valkyrienskies/core/impl/shadow/DN org/valkyrienskies/core/impl/todo/VsBlockTypes
3030

31-
c org/valkyrienskies/core/impl/shadow/dt org/valkyrienskies/core/impl/todo/RegexValidator
32-
3331
c org/valkyrienskies/core/impl/shadow/DW org/valkyrienskies/core/impl/game/connectivity/ConnectivityHandler
3432

3533
c org/valkyrienskies/core/impl/shadow/Ej org/valkyrienskies/core/impl/game/ships/DummyShipWorldServer
@@ -114,6 +112,8 @@ c org/valkyrienskies/core/impl/shadow/EY org/valkyrienskies/core/impl/networking
114112

115113
c org/valkyrienskies/core/impl/shadow/Ez org/valkyrienskies/core/impl/todo/LoadManager
116114

115+
c org/valkyrienskies/core/impl/shadow/FH org/valkyrienskies/core/impl/pipelines/VSPhysicsPipelineStage
116+
117117
c org/valkyrienskies/core/impl/shadow/Fi org/valkyrienskies/core/impl/networking/VSNetworking
118118
f Lorg/valkyrienskies/core/impl/shadow/EW; a udp
119119
f Lorg/valkyrienskies/core/impl/shadow/EW; b tcp
@@ -123,8 +123,6 @@ c org/valkyrienskies/core/impl/shadow/Fi org/valkyrienskies/core/impl/networking
123123
f Z e serverUsesUDP
124124
f Lorg/valkyrienskies/core/api/event/RegisteredListener; f prevStateHandler
125125

126-
c org/valkyrienskies/core/impl/shadow/FH org/valkyrienskies/core/impl/pipelines/VSPhysicsPipelineStage
127-
128126
c org/valkyrienskies/core/impl/shadow/FM org/valkyrienskies/core/impl/todo/VsPipelineImpl
129127

130128
c org/valkyrienskies/core/impl/shadow/FO org/valkyrienskies/core/impl/todo/DummyPhysLevel

0 commit comments

Comments
 (0)