Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.cyclops.integratedscripting.command.CommandTestScript;
import org.cyclops.integratedscripting.component.DataComponentDiskIdConfig;
import org.cyclops.integratedscripting.core.client.model.ScriptingVariableModelProviders;
import org.cyclops.integratedscripting.core.evaluate.ScriptEvaluationStatuses;
import org.cyclops.integratedscripting.core.evaluate.ScriptVariableFacadeHandler;
import org.cyclops.integratedscripting.core.packageddependencies.PackagedDependenciesLoader;
import org.cyclops.integratedscripting.core.language.LanguageHandlerRegistry;
Expand Down Expand Up @@ -128,6 +129,7 @@ protected void onServerStopping(ServerStoppingEvent event) {
}
this.scriptingData = null;
EvaluationExceptionResolutionHelpers.reset();
ScriptEvaluationStatuses.reset();
}

@SubscribeEvent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import net.minecraft.network.chat.Style;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.util.StringUtil;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
Expand Down Expand Up @@ -556,7 +557,19 @@ protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) {
Component bindingLabel = getBindingLabel();
if (bindingLabel != null) {
int right = getErrorX() - 2;
guiGraphics.drawString(font, bindingLabel, right - font.width(bindingLabel), getErrorY() + 2, 4210752, false);
int left = right - font.width(bindingLabel);
guiGraphics.drawString(font, bindingLabel, left, getErrorY() + 2, 4210752, false);

// Show the last evaluation of the bound card
if (getMenu().getCardMember() != null && pendingDialog == null
&& mouseX >= this.leftPos + left && mouseX < this.leftPos + right
&& mouseY >= this.topPos + getErrorY() + 1 && mouseY < this.topPos + getErrorY() + 2 + font.lineHeight) {
List<FormattedCharSequence> lines = Lists.newArrayList();
for (MutableComponent line : getMenu().getCardEvaluation()) {
lines.addAll(font.split(line, 200));
}
this.setTooltipForNextRenderPass(lines);
}
}

displayErrors.drawForeground(guiGraphics.pose(), getMenu().getReadErrors(), getErrorX(), getErrorY(), mouseX, mouseY, this, this.leftPos, this.topPos);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.cyclops.integratedscripting.core.evaluate;

import com.google.common.collect.Lists;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import org.apache.commons.lang3.tuple.Triple;
import org.cyclops.integrateddynamics.api.evaluate.variable.IValue;
import org.cyclops.integratedscripting.IntegratedScripting;
import org.cyclops.integratedscripting.core.network.ScriptingData;

import javax.annotation.Nullable;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
* The last evaluation results of script members, so they can be shown while editing.
* @author rubensworks
*/
public class ScriptEvaluationStatuses {

private static final int MAX_VALUE_LENGTH = 100;
private static final Map<Triple<Integer, Path, String>, Status> STATUSES = new ConcurrentHashMap<>();

public static void recordValue(int disk, Path path, String member, IValue value) {
Status status = getOrCreate(disk, path, member);
status.value = value;
status.readError = null;
}

public static void recordReadError(int disk, Path path, String member, MutableComponent error) {
Status status = getOrCreate(disk, path, member);
status.value = null;
status.readError = error;
// Read errors are also created by the exception factory, so don't show them twice
status.callError = null;
}

public static void recordCallError(int disk, Path path, String member, MutableComponent error) {
getOrCreate(disk, path, member).callError = error;
}

private static Status getOrCreate(int disk, Path path, String member) {
String script = getScript(disk, path);
Status status = STATUSES.get(Triple.of(disk, path, member));
if (status == null || !Objects.equals(status.script, script)) {
status = new Status(script);
STATUSES.put(Triple.of(disk, path, member), status);
}
return status;
}

@Nullable
private static String getScript(int disk, Path path) {
// Scripting data is not available outside of a running server, such as in unit tests
ScriptingData scriptingData = IntegratedScripting._instance == null ? null : IntegratedScripting._instance.scriptingData;
return scriptingData == null ? null : scriptingData.getScripts(disk).get(path);
}

/**
* @return Lines describing the last evaluation of the member since its script last changed.
*/
public static List<MutableComponent> describe(int disk, Path path, String member) {
Status status = STATUSES.get(Triple.of(disk, path, member));
List<MutableComponent> lines = Lists.newArrayList();
if (status == null || !Objects.equals(status.script, getScript(disk, path)) || (status.value == null && status.readError == null)) {
lines.add(Component.translatable("gui.integratedscripting.evaluation.none").withStyle(ChatFormatting.GRAY));
return lines;
}
if (status.readError != null) {
lines.add(Component.translatable("gui.integratedscripting.evaluation.error", status.readError.copy()).withStyle(ChatFormatting.RED));
} else {
String value = status.value.getType().toCompactString(status.value).getString();
if (value.length() > MAX_VALUE_LENGTH) {
value = value.substring(0, MAX_VALUE_LENGTH) + "…";
}
lines.add(Component.translatable("gui.integratedscripting.evaluation.value", Component.literal(value).withStyle(ChatFormatting.WHITE)).withStyle(ChatFormatting.GRAY));
}
if (status.callError != null) {
lines.add(Component.translatable("gui.integratedscripting.evaluation.call_error", status.callError.copy()).withStyle(ChatFormatting.RED));
}
return lines;
}

public static void reset() {
STATUSES.clear();
}

private static class Status {
@Nullable
private final String script;
@Nullable
private IValue value;
@Nullable
private MutableComponent readError;
@Nullable
private MutableComponent callError;

private Status(@Nullable String script) {
this.script = script;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ public IValueType<IValue> getType() {

@Override
public IValue getValue() throws EvaluationException {
try {
IValue value = this.evaluate();
ScriptEvaluationStatuses.recordValue(this.disk, this.path, this.member, value);
return value;
} catch (EvaluationException e) {
ScriptEvaluationStatuses.recordReadError(this.disk, this.path, this.member, e.getErrorMessage());
throw e;
}
}

protected IValue evaluate() throws EvaluationException {
if (this.lastEvaluationException != null) {
throw this.lastEvaluationException;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypeOperator;
import org.cyclops.integratedscripting.GeneralConfig;
import org.cyclops.integratedscripting.api.evaluate.translation.IEvaluationExceptionFactory;
import org.cyclops.integratedscripting.core.evaluate.ScriptEvaluationStatuses;
import org.cyclops.integratedscripting.core.packageddependencies.UnsafeHelper;
import org.cyclops.integratedscripting.evaluate.translation.ValueTranslators;
import org.graalvm.polyglot.*;
Expand Down Expand Up @@ -144,9 +145,12 @@ public static IEvaluationExceptionFactory getDummyEvaluationExceptionFactory() {
public static IEvaluationExceptionFactory getEvaluationExceptionFactory(int disk, Path path, String member) {
EvaluationExceptionResolutionHelpers.expungeStaleEvaluationExceptions();

return message -> EvaluationExceptionResolutionHelpers.resolveOnScriptChange(
new EvaluationException(Component.translatable("script.integratedscripting.error.script_exec", member, path.toString(), disk, message)),
disk, path);
return message -> {
ScriptEvaluationStatuses.recordCallError(disk, path, member, message);
return EvaluationExceptionResolutionHelpers.resolveOnScriptChange(
new EvaluationException(Component.translatable("script.integratedscripting.error.script_exec", member, path.toString(), disk, message)),
disk, path);
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import net.minecraft.gametest.framework.GameTestAssertException;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.contents.TranslatableContents;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.neoforged.neoforge.gametest.GameTestHolder;
Expand All @@ -21,12 +23,14 @@
import org.cyclops.integratedscripting.IntegratedScripting;
import org.cyclops.integratedscripting.Reference;
import org.cyclops.integratedscripting.api.network.IScriptingData;
import org.cyclops.integratedscripting.core.evaluate.ScriptEvaluationStatuses;
import org.cyclops.integratedscripting.core.network.ScriptingNetworkHelpers;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import static org.cyclops.integrateddynamics.gametest.GameTestHelpersIntegratedDynamics.*;
import static org.cyclops.integratedscripting.gametest.GameTestHelpersIntegratedScripting.createBasicNetwork;
Expand Down Expand Up @@ -192,6 +196,58 @@ public void testScriptsDisplayScriptInfiniteLoop(GameTestHelper helper) {
});
}

@GameTest(template = TEMPLATE_EMPTY, timeoutTicks = TIMEOUT)
public void testScriptsEvaluationStatus(GameTestHelper helper) {
GameTestHelpersIntegratedScripting.NetworkPositions positions = createBasicNetwork(helper, POS);
Path path = Path.of("script0.js");
ScriptingNetworkHelpers.getScriptingData().setScript(positions.diskId(), path, "const abc = 3", IScriptingData.ChangeLocation.MEMORY);
helper.assertTrue(getEvaluationStatusKey(positions.diskId(), path, "abc").equals("gui.integratedscripting.evaluation.none"), "Script must not be evaluated yet");

ItemStack variableScript = createVariableForScript(helper.getLevel(), positions.diskId(), path, "abc");
placeVariableInDisplayPanel(helper.getLevel(), positions.displayPanel(), variableScript);

helper.startSequence()
.thenWaitUntil(() -> {
helper.assertTrue(getEvaluationStatusKey(positions.diskId(), path, "abc").equals("gui.integratedscripting.evaluation.value"), "Value must be recorded");
helper.assertTrue(ScriptEvaluationStatuses.describe(positions.diskId(), path, "abc").get(0).getString().contains("3"), "Value must be 3");
})
.thenExecute(() -> ScriptingNetworkHelpers.getScriptingData().setScript(positions.diskId(), path, "const abc = doesNotExist();", IScriptingData.ChangeLocation.MEMORY))
.thenWaitUntil(() -> helper.assertTrue(getEvaluationStatusKey(positions.diskId(), path, "abc").equals("gui.integratedscripting.evaluation.error"), "Error must be recorded"))
.thenSucceed();
}

@GameTest(template = TEMPLATE_EMPTY, timeoutTicks = TIMEOUT)
public void testScriptsEvaluationStatusCallError(GameTestHelper helper) {
GameTestHelpersIntegratedScripting.NetworkPositions positions = createBasicNetwork(helper, POS);
Path path = Path.of("script0.js");
ScriptingNetworkHelpers.getScriptingData().setScript(positions.diskId(), path, "function abc(a, b) { throw new Error('boom ' + (a + b)); }", IScriptingData.ChangeLocation.MEMORY);

// Apply the function in a display panel
ItemStack variableScript = createVariableForScript(helper.getLevel(), positions.diskId(), path, "abc");
ItemStack variableConst1 = createVariableForValue(helper.getLevel(), ValueTypes.INTEGER, ValueTypeInteger.ValueInteger.of(3));
ItemStack variableConst2 = createVariableForValue(helper.getLevel(), ValueTypes.INTEGER, ValueTypeInteger.ValueInteger.of(8));
positions.variableStore().getInventory().setItem(0, variableScript);
positions.variableStore().getInventory().setItem(1, variableConst1);
positions.variableStore().getInventory().setItem(2, variableConst2);
ItemStack variableApplied = createVariableForOperator(helper.getLevel(), Operators.OPERATOR_APPLY_2, new int[]{
getVariableFacade(helper.getLevel(), variableScript).getId(),
getVariableFacade(helper.getLevel(), variableConst1).getId(),
getVariableFacade(helper.getLevel(), variableConst2).getId()
});
placeVariableInDisplayPanel(helper.getLevel(), positions.displayPanel(), variableApplied);

helper.succeedWhen(() -> {
List<MutableComponent> lines = ScriptEvaluationStatuses.describe(positions.diskId(), path, "abc");
helper.assertTrue(lines.size() == 2, "The value and call error must be described");
helper.assertTrue(((TranslatableContents) lines.get(1).getContents()).getKey().equals("gui.integratedscripting.evaluation.call_error"), "Call error must be recorded");
helper.assertTrue(lines.get(1).getString().contains("boom 11"), "Call error must contain the thrown message");
});
}

private static String getEvaluationStatusKey(int disk, Path path, String member) {
return ((TranslatableContents) ScriptEvaluationStatuses.describe(disk, path, member).get(0).getContents()).getKey();
}

@GameTest(template = TEMPLATE_EMPTY, timeoutTicks = TIMEOUT)
public void testScriptsDisplayScriptErrorResolvedAfterExternalFix(GameTestHelper helper) {
GameTestHelpersIntegratedScripting.NetworkPositions positions = createBasicNetwork(helper, POS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.cyclops.integratedscripting.api.language.ScriptSyntaxError;
import org.cyclops.integratedscripting.api.network.IScriptingData;
import org.cyclops.integratedscripting.api.network.IScriptingNetwork;
import org.cyclops.integratedscripting.core.evaluate.ScriptEvaluationStatuses;
import org.cyclops.integratedscripting.core.evaluate.ScriptVariableFacadeHandler;
import org.cyclops.integratedscripting.core.item.ScriptVariableFacade;
import org.cyclops.integratedscripting.core.language.LanguageHandlers;
Expand Down Expand Up @@ -80,6 +81,7 @@ public class ContainerTerminalScripting extends InventoryContainer implements ID
private final int activeDiskId;
private final int activeScriptPathId;
private final int selectionId;
private final int cardEvaluationId;
@Nullable
private BiConsumer<Integer, Path> remoteScriptChangeListener;
private final Map<Pair<Integer, Path>, ScriptSyntaxError> syntaxErrors = Maps.newHashMap();
Expand Down Expand Up @@ -119,6 +121,7 @@ public ContainerTerminalScripting(int id, Inventory playerInventory,
setActiveDisk(this.availableDisks.isEmpty() ? -1 : this.availableDisks.getInt(0));
this.activeScriptPathId = getNextValueId();
this.selectionId = getNextValueId();
this.cardEvaluationId = getNextValueId();
}


Expand Down Expand Up @@ -235,9 +238,21 @@ public void broadcastChanges() {
});

this.checkActiveScriptSyntax();

// Send the last evaluation of the bound card
IScriptVariableFacade cardFacade = getCardFacade();
ValueNotifierHelpers.setValue(this, cardEvaluationId, cardFacade == null ? Collections.emptyList()
: ScriptEvaluationStatuses.describe(cardFacade.getDisk(), cardFacade.getPath(), cardFacade.getMember()));
}
}

/**
* @return Lines describing the last evaluation of the bound card.
*/
public List<MutableComponent> getCardEvaluation() {
return ValueNotifierHelpers.getValueTextComponentList(this, cardEvaluationId);
}

/**
* Check the active script for syntax errors once it hasn't changed for a while, and send the result to the client.
*/
Expand Down Expand Up @@ -427,13 +442,19 @@ public boolean isReservedWord(String word) {
*/
@Nullable
public String getCardMember() {
IScriptVariableFacade facade = getCardFacade();
return facade != null ? facade.getMember() : null;
}

@Nullable
protected IScriptVariableFacade getCardFacade() {
ItemStack itemStack = getContainerInventory().getItem(0);
if (itemStack.isEmpty()) {
return null;
}
IVariableFacadeHandlerRegistry registry = IntegratedDynamics._instance.getRegistryManager().getRegistry(IVariableFacadeHandlerRegistry.class);
IVariableFacade variableFacade = registry.handle(ValueDeseralizationContext.of(getLevel()), itemStack);
return variableFacade instanceof IScriptVariableFacade scriptVariableFacade ? scriptVariableFacade.getMember() : null;
return variableFacade instanceof IScriptVariableFacade scriptVariableFacade ? scriptVariableFacade : null;
}

@Nullable
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/assets/integratedscripting/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
"gui.integratedscripting.error.reserved_member": "\"%s\" is a keyword, so it can not be bound. Select a function name or variable name instead.",
"gui.integratedscripting.binding.next": "Binds to: %s",
"gui.integratedscripting.binding.card": "Bound to: %s",
"gui.integratedscripting.evaluation.none": "Not evaluated by the network since the script last changed",
"gui.integratedscripting.evaluation.value": "Last value: %s",
"gui.integratedscripting.evaluation.error": "Last error: %s",
"gui.integratedscripting.evaluation.call_error": "Last error when called: %s",

"key.categories.integratedscripting": "Integrated Scripting",
"key.integratedscripting.undo": "Undo",
Expand Down
Loading