Skip to content

Commit fd10a94

Browse files
Introduce new wrappers for data components
1 parent 7c94f70 commit fd10a94

File tree

3 files changed

+246
-8
lines changed

3 files changed

+246
-8
lines changed

src/main/java/dev/latvian/mods/kubejs/component/DataComponentWrapper.java

Lines changed: 242 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,33 @@
55
import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType;
66
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
77
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
8+
import com.mojang.datafixers.util.Pair;
89
import com.mojang.serialization.Codec;
10+
import com.mojang.serialization.DataResult;
911
import com.mojang.serialization.DynamicOps;
12+
import dev.latvian.mods.kubejs.error.KubeRuntimeException;
1013
import dev.latvian.mods.kubejs.plugin.KubeJSPlugin;
1114
import dev.latvian.mods.kubejs.plugin.KubeJSPlugins;
15+
import dev.latvian.mods.kubejs.script.SourceLine;
1216
import dev.latvian.mods.kubejs.util.Cast;
1317
import dev.latvian.mods.kubejs.util.ID;
1418
import dev.latvian.mods.kubejs.util.Lazy;
19+
import dev.latvian.mods.kubejs.util.RegistryAccessContainer;
20+
import dev.latvian.mods.rhino.BaseFunction;
21+
import dev.latvian.mods.rhino.Context;
22+
import dev.latvian.mods.rhino.EvaluatorException;
1523
import dev.latvian.mods.rhino.NativeJavaMap;
24+
import dev.latvian.mods.rhino.Undefined;
1625
import dev.latvian.mods.rhino.type.TypeInfo;
26+
import net.minecraft.Util;
1727
import net.minecraft.core.component.DataComponentMap;
1828
import net.minecraft.core.component.DataComponentPatch;
1929
import net.minecraft.core.component.DataComponentPredicate;
2030
import net.minecraft.core.component.DataComponentType;
2131
import net.minecraft.core.component.DataComponents;
32+
import net.minecraft.core.component.PatchedDataComponentMap;
2233
import net.minecraft.core.registries.BuiltInRegistries;
34+
import net.minecraft.core.registries.Registries;
2335
import net.minecraft.nbt.NbtOps;
2436
import net.minecraft.nbt.Tag;
2537
import net.minecraft.nbt.TagParser;
@@ -32,12 +44,23 @@
3244
import java.lang.reflect.ParameterizedType;
3345
import java.util.HashSet;
3446
import java.util.Map;
47+
import java.util.Objects;
3548
import java.util.Set;
49+
import java.util.function.Consumer;
50+
import java.util.function.Function;
51+
import java.util.stream.Collectors;
52+
import java.util.stream.Stream;
53+
54+
import static com.mojang.serialization.DataResult.error;
55+
import static com.mojang.serialization.DataResult.success;
3656

3757
public interface DataComponentWrapper {
38-
DynamicCommandExceptionType ERROR_UNKNOWN_COMPONENT = new DynamicCommandExceptionType((object) -> Component.translatableEscape("arguments.item.component.unknown", object));
58+
DynamicCommandExceptionType ERROR_UNKNOWN_COMPONENT = new DynamicCommandExceptionType(object -> Component.translatableEscape("arguments.item.component.unknown", object));
3959
Dynamic2CommandExceptionType ERROR_MALFORMED_COMPONENT = new Dynamic2CommandExceptionType((object, object2) -> Component.translatableEscape("arguments.item.component.malformed", object, object2));
4060
SimpleCommandExceptionType ERROR_EXPECTED_COMPONENT = new SimpleCommandExceptionType(Component.translatable("arguments.item.component.expected"));
61+
62+
TypeInfo COMPONENT_TYPE = TypeInfo.of(DataComponentType.class);
63+
4164
Lazy<Map<DataComponentType<?>, TypeInfo>> TYPE_INFOS = Lazy.identityMap(map -> {
4265
try {
4366
for (var field : DataComponents.class.getDeclaredFields()) {
@@ -46,7 +69,7 @@ public interface DataComponentWrapper {
4669
&& Modifier.isStatic(field.getModifiers())
4770
&& field.getGenericType() instanceof ParameterizedType t
4871
) {
49-
var key = (DataComponentType) field.get(null);
72+
@SuppressWarnings("rawtypes") var key = (DataComponentType) field.get(null);
5073
var typeInfo = TypeInfo.of(t.getActualTypeArguments()[0]);
5174
map.put(key, typeInfo);
5275
}
@@ -122,7 +145,7 @@ static DataComponentMap readMap(@Nullable DynamicOps<Tag> registryOps, StringRea
122145
builder = DataComponentMap.builder();
123146
}
124147

125-
builder.set(dataComponentType, Cast.to(dataResult.getOrThrow((string) -> {
148+
builder.set(dataComponentType, Cast.to(dataResult.getOrThrow(string -> {
126149
reader.setCursor(i);
127150
return ERROR_MALFORMED_COMPONENT.createWithContext(reader, dataComponentType.toString(), string);
128151
})));
@@ -203,7 +226,7 @@ static DataComponentPatch readPatch(@Nullable DynamicOps<Tag> registryOps, Strin
203226
builder = DataComponentPatch.builder();
204227
}
205228

206-
builder.set(dataComponentType, Cast.to(dataResult.getOrThrow((string) -> {
229+
builder.set(dataComponentType, Cast.to(dataResult.getOrThrow(string -> {
207230
reader.setCursor(i);
208231
return ERROR_MALFORMED_COMPONENT.createWithContext(reader, dataComponentType.toString(), string);
209232
})));
@@ -261,6 +284,7 @@ static boolean filter(Object from, TypeInfo target) {
261284
return from == null || from instanceof DataComponentMap || from instanceof DataComponentPatch || from instanceof Map || from instanceof NativeJavaMap || from instanceof String s && (s.isEmpty() || s.charAt(0) == '[');
262285
}
263286

287+
@Deprecated(forRemoval = true)
264288
static DataComponentMap mapOf(@Nullable DynamicOps<Tag> ops, Object o) {
265289
try {
266290
return readMap(ops, new StringReader(o.toString()));
@@ -269,6 +293,7 @@ static DataComponentMap mapOf(@Nullable DynamicOps<Tag> ops, Object o) {
269293
}
270294
}
271295

296+
@Deprecated(forRemoval = true)
272297
static DataComponentMap mapOrEmptyOf(@Nullable DynamicOps<Tag> ops, Object o) {
273298
try {
274299
return readMap(ops, new StringReader(o.toString()));
@@ -277,6 +302,7 @@ static DataComponentMap mapOrEmptyOf(@Nullable DynamicOps<Tag> ops, Object o) {
277302
}
278303
}
279304

305+
@Deprecated(forRemoval = true)
280306
static DataComponentPatch patchOf(@Nullable DynamicOps<Tag> ops, Object o) {
281307
try {
282308
return readPatch(ops, new StringReader(o.toString()));
@@ -285,6 +311,7 @@ static DataComponentPatch patchOf(@Nullable DynamicOps<Tag> ops, Object o) {
285311
}
286312
}
287313

314+
@Deprecated(forRemoval = true)
288315
static DataComponentPatch patchOrEmptyOf(@Nullable DynamicOps<Tag> ops, Object o) {
289316
try {
290317
return readPatch(ops, new StringReader(o.toString()));
@@ -293,6 +320,207 @@ static DataComponentPatch patchOrEmptyOf(@Nullable DynamicOps<Tag> ops, Object o
293320
}
294321
}
295322

323+
static DataComponentMap mapOf(Context cx, Object from) {
324+
return tryMapOf(cx, from)
325+
.getOrThrow(error -> new KubeRuntimeException("Failed to warp DataComponentMap: %s".formatted(error))
326+
.source(SourceLine.of(cx)));
327+
}
328+
329+
static DataComponentPatch patchOf(Context cx, Object from) {
330+
return tryPatchOf(cx, from)
331+
.getOrThrow(error -> new KubeRuntimeException("Failed to warp DataComponentMap: %s".formatted(error))
332+
.source(SourceLine.of(cx)));
333+
}
334+
335+
static DataComponentMap mapOrEmptyOf(Context cx, Object from) {
336+
return tryMapOf(cx, from)
337+
.resultOrPartial()
338+
.orElse(DataComponentMap.EMPTY);
339+
}
340+
341+
static DataComponentPatch patchOrEmptyOf(Context cx, Object from) {
342+
return tryPatchOf(cx, from)
343+
.resultOrPartial()
344+
.orElse(DataComponentPatch.EMPTY);
345+
}
346+
347+
static DataResult<DataComponentMap> tryMapOf(Context cx, @Nullable Object o) {
348+
return switch (o) {
349+
case DataComponentMap map -> success(map);
350+
case DataComponentPatch patch -> success(PatchedDataComponentMap.fromPatch(DataComponentMap.EMPTY, patch));
351+
case BaseFunction fn -> fnToBuilder(cx, MapBuilder.class, fn,
352+
builder -> Util.make(DataComponentMap.builder(), builder).build());
353+
case Map<?, ?> map -> {
354+
var reg = RegistryAccessContainer.of(cx);
355+
var builder = DataComponentMap.builder();
356+
357+
var failed = false;
358+
Stream.Builder<Pair<DataComponentType<?>, String>> errors = Stream.builder();
359+
360+
Map<DataComponentType<?>, ?> wrapped = Objects.requireNonNull(cx.optionalMapOf(map, COMPONENT_TYPE, TypeInfo.NONE));
361+
362+
for (var entry : wrapped.entrySet()) {
363+
var type = entry.getKey();
364+
var valueType = getTypeInfo(type);
365+
366+
var value = entry.getValue();
367+
368+
if (cx.canConvert(value, valueType)) {
369+
try {
370+
Object converted = cx.jsToJava(value, valueType);
371+
if (converted != null) {
372+
//noinspection rawtypes, unchecked
373+
builder.set((DataComponentType) type, converted);
374+
continue;
375+
}
376+
} catch (EvaluatorException e) {
377+
failed = true;
378+
errors.add(Pair.of(type, "Failed to parse data component from input '%s': %s".formatted(value, e)));
379+
continue;
380+
}
381+
}
382+
383+
var codec = type.codec();
384+
385+
if (codec == null) {
386+
failed = true;
387+
errors.add(Pair.of(type, "Component has non-serializable type"));
388+
continue;
389+
}
390+
391+
switch (codec.parse(reg.java(), value)) {
392+
case DataResult.Success<?> success ->
393+
//noinspection rawtypes, unchecked
394+
builder.set((DataComponentType) type, success.value());
395+
case DataResult.Error<?> error -> {
396+
failed = true;
397+
errors.add(Pair.of(type, error.message()));
398+
}
399+
}
400+
}
401+
402+
if (failed) {
403+
var msg = errors.build().map(pair -> {
404+
var type = pair.getFirst();
405+
var error = pair.getSecond();
406+
407+
var id = reg.access().registryOrThrow(Registries.DATA_COMPONENT_TYPE).getKeyOrNull(type);
408+
409+
return "'%s': %s".formatted(id, error);
410+
}).collect(Collectors.joining("; "));
411+
yield error(() -> "Failed to parse DataComponentMap: " + msg, builder.build());
412+
} else {
413+
yield success(builder.build());
414+
}
415+
}
416+
case null -> success(DataComponentMap.EMPTY);
417+
case String s -> {
418+
try {
419+
var reg = RegistryAccessContainer.of(cx);
420+
yield success(readMap(reg.nbt(), new StringReader(s)));
421+
} catch (CommandSyntaxException ex) {
422+
yield error(() -> "Error parsing DataComponentMap from %s: %s".formatted(s, ex.getMessage()));
423+
}
424+
}
425+
default -> error(() -> "Don't know how to convert %s to DataComponentMap!".formatted(o));
426+
};
427+
}
428+
429+
static DataResult<DataComponentPatch> tryPatchOf(Context cx, @Nullable Object o) {
430+
return switch (o) {
431+
case DataComponentPatch patch -> success(patch);
432+
case BaseFunction fn -> fnToBuilder(cx, PatchBuilder.class, fn,
433+
builder -> Util.make(DataComponentPatch.builder(), builder).build());
434+
case Map<?, ?> map -> {
435+
var reg = RegistryAccessContainer.of(cx);
436+
var builder = DataComponentPatch.builder();
437+
438+
var failed = false;
439+
Stream.Builder<Pair<DataComponentType<?>, String>> errors = Stream.builder();
440+
441+
Map<DataComponentType<?>, ?> wrapped = Objects.requireNonNull(cx.optionalMapOf(map, COMPONENT_TYPE, TypeInfo.NONE));
442+
443+
for (var entry : wrapped.entrySet()) {
444+
var type = entry.getKey();
445+
var valueType = getTypeInfo(type);
446+
447+
var value = entry.getValue();
448+
449+
if (value == null || value instanceof Undefined) {
450+
builder.remove(type);
451+
continue;
452+
}
453+
454+
if (cx.canConvert(value, valueType)) {
455+
try {
456+
Object converted = cx.jsToJava(value, valueType);
457+
if (converted != null) {
458+
//noinspection rawtypes, unchecked
459+
builder.set((DataComponentType) type, converted);
460+
continue;
461+
}
462+
} catch (EvaluatorException e) {
463+
failed = true;
464+
errors.add(Pair.of(type, "Failed to parse data component from input '%s': %s".formatted(value, e)));
465+
continue;
466+
}
467+
}
468+
469+
var codec = type.codec();
470+
471+
if (codec == null) {
472+
failed = true;
473+
errors.add(Pair.of(type, "Component has non-serializable type"));
474+
continue;
475+
}
476+
477+
switch (codec.parse(reg.java(), value)) {
478+
case DataResult.Success<?> success ->
479+
//noinspection rawtypes, unchecked
480+
builder.set((DataComponentType) type, success.value());
481+
case DataResult.Error<?> error -> {
482+
failed = true;
483+
errors.add(Pair.of(type, error.message()));
484+
}
485+
}
486+
}
487+
488+
if (failed) {
489+
var msg = errors.build().map(pair -> {
490+
var type = pair.getFirst();
491+
var error = pair.getSecond();
492+
493+
var id = reg.access().registryOrThrow(Registries.DATA_COMPONENT_TYPE).getKeyOrNull(type);
494+
495+
return "'%s': %s".formatted(id, error);
496+
}).collect(Collectors.joining("; "));
497+
yield error(() -> "Failed to parse DataComponentPatch: " + msg, builder.build());
498+
} else {
499+
yield success(builder.build());
500+
}
501+
}
502+
case null -> success(DataComponentPatch.EMPTY);
503+
case String s -> {
504+
try {
505+
var reg = RegistryAccessContainer.of(cx);
506+
yield success(readPatch(reg.nbt(), new StringReader(s)));
507+
} catch (CommandSyntaxException ex) {
508+
yield error(() -> "Error parsing DataComponentPatch from %s: %s".formatted(s, ex.getMessage()));
509+
}
510+
}
511+
default -> error(() -> "Don't know how to convert %s to DataComponentPatch!".formatted(o));
512+
};
513+
}
514+
515+
private static <B, T> DataResult<T> fnToBuilder(Context cx, Class<B> builderType, BaseFunction fn, Function<B, T> build) {
516+
try {
517+
B builder = Cast.to(cx.createInterfaceAdapter(TypeInfo.of(builderType), fn));
518+
return success(build.apply(builder));
519+
} catch (Exception e) {
520+
return error(() -> "Failed to create %s from builder: %s".formatted(builderType.toString(), e));
521+
}
522+
}
523+
296524
static StringBuilder mapToString(StringBuilder builder, @Nullable DynamicOps<Tag> ops, DataComponentMap map) {
297525
builder.append('[');
298526

@@ -381,4 +609,14 @@ static DataComponentPatch visualPatch(DataComponentPatch patch) {
381609

382610
return builder.build();
383611
}
612+
613+
interface MapBuilder extends Consumer<DataComponentMap.Builder> {
614+
@Override
615+
void accept(DataComponentMap.Builder builder);
616+
}
617+
618+
interface PatchBuilder extends Consumer<DataComponentPatch.Builder> {
619+
@Override
620+
void accept(DataComponentPatch.Builder builder);
621+
}
384622
}

src/main/java/dev/latvian/mods/kubejs/plugin/builtin/BuiltinKubeJSPlugin.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@
171171
import dev.latvian.mods.kubejs.util.NameProvider;
172172
import dev.latvian.mods.kubejs.util.NotificationToastData;
173173
import dev.latvian.mods.kubejs.util.RegExpKJS;
174-
import dev.latvian.mods.kubejs.util.RegistryAccessContainer;
175174
import dev.latvian.mods.kubejs.util.RotationAxis;
176175
import dev.latvian.mods.kubejs.util.ScheduledEvents;
177176
import dev.latvian.mods.kubejs.util.SlotFilter;
@@ -510,8 +509,8 @@ public void registerTypeWrappers(TypeWrapperRegistry registry) {
510509
registry.register(ListTag.class, (from, target) -> NBTWrapper.isTagCollection(from), NBTWrapper::wrapListTag);
511510
registry.register(Tag.class, NBTWrapper::wrap);
512511
registry.register(DataComponentType.class, DataComponentWrapper::wrapType);
513-
registry.register(DataComponentMap.class, DataComponentWrapper::filter, (cx, from, target) -> DataComponentWrapper.mapOf(RegistryAccessContainer.of(cx).nbt(), from));
514-
registry.register(DataComponentPatch.class, DataComponentWrapper::filter, (cx, from, target) -> DataComponentWrapper.patchOf(RegistryAccessContainer.of(cx).nbt(), from));
512+
registry.register(DataComponentMap.class, DataComponentWrapper::filter, DataComponentWrapper::mapOf);
513+
registry.register(DataComponentPatch.class, DataComponentWrapper::filter, DataComponentWrapper::patchOf);
515514

516515
registry.register(BlockPos.class, MiscWrappers::wrapBlockPos);
517516
registry.register(Vec3.class, MiscWrappers::wrapVec3);

src/main/java/dev/latvian/mods/kubejs/web/KJSHTTPRequest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.latvian.mods.kubejs.web;
22

3+
import com.mojang.brigadier.StringReader;
34
import com.mojang.brigadier.exceptions.CommandSyntaxException;
45
import com.mojang.serialization.DynamicOps;
56
import dev.latvian.apps.tinyserver.http.HTTPRequest;
@@ -46,7 +47,7 @@ public ResourceLocation id() {
4647

4748
public DataComponentPatch components(DynamicOps<Tag> ops) throws CommandSyntaxException {
4849
var str = query("components").asString();
49-
return str.isEmpty() ? DataComponentPatch.EMPTY : DataComponentWrapper.patchOrEmptyOf(ops, "[" + str + "]");
50+
return str.isEmpty() ? DataComponentPatch.EMPTY : DataComponentWrapper.readPatch(ops, new StringReader("[" + str + "]"));
5051
}
5152

5253
@Override

0 commit comments

Comments
 (0)