Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
12 changes: 5 additions & 7 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,13 +210,11 @@ Notes; finished-work narrative does not.
overlapping loops can mean something different from `table.move`; prove overlap/direction, bounds,
defaults and counter semantics. Establish game availability, crossover and hot consumers before
selecting an API or automatic lowering. Scripts and raw samples are in the investigation note.
- **Scope an opt-in Lua body hot reload.** Stock-Lua model and actual generated-code probes replace
a body on an existing object without resetting storage/allocators. This is not implemented in the
existing JHCR command. First verify engine `load` and delivery, then design stable semantic IDs,
explicit lexical storage bindings, all slot aliases, ABI/version rejection, inlining dependencies,
saved callbacks and active-frame policy. Begin with named function bodies in single-player dev
mode; no schema/init/closure migration and no release overhead. Multiplayer needs synchronized
payload/application ordering. See the investigation note for a staged plan and capability probe.
- **Use the Lua body hot reload on a real map.** Hot run of a Lua project replaces bodies in the
running map on save, measured on the 3.0.1.24371 client in a test map over five payloads
([docs/LUA_HOT_RELOAD.md](docs/LUA_HOT_RELOAD.md)). Not yet measured: how often a real project's
edits are rejected, and its payload sizes and reload times. Later stages are in the design note: new
functions, a library reload hook, and multiplayer only through a synchronised channel.
- **Audit the remaining Lua emission for waste.** The Lua backend began as "make Lua mode usable", and
recent fixes (`git log --grep "Lua"`) keep finding helper calls, allocations and dead bindings that
were simply the easiest thing to emit. Method: read the emitted script of a real map next to what
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,11 @@ private void removeUnreachableFunctionsBeforeCompiletime() {
}

public LuaCompilationUnit transformProgToLua() {
if (runArgs.isLuaHotReload() && (runArgs.isInline() || runArgs.isOptimize())) {
// Inlining copies a body into each caller and -opt renames every declaration per build, so a payload
// would have to replace the callers or could not name anything in the running script.
throw new IllegalArgumentException("-luaHotReload is for development builds: remove -inline and -opt.");
}

ImAttrType.setWurstClassType(null);
int stage;
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import de.peeeq.wurstscript.luaAst.LuaCompilationUnit;
import de.peeeq.wurstscript.parser.WPos;
import de.peeeq.wurstscript.translation.lua.translation.LuaAssertions;
import de.peeeq.wurstscript.translation.lua.translation.LuaHotReload;
import de.peeeq.wurstscript.utils.LineOffsets;
import de.peeeq.wurstscript.utils.Utils;
import net.moonlightflower.wc3libs.bin.app.W3I;
Expand Down Expand Up @@ -125,6 +126,9 @@ public static class CompilationResult {
public File w3i;
}

/** What the last {@code -luaHotReload} compilation emitted ({@link LuaHotReloadSession}), null otherwise. */
protected LuaHotReload.@Nullable Manifest luaHotReloadManifest;

public MapRequest(WurstLanguageServer langServer, Optional<File> map, List<String> compileArgs, WFile workspaceRoot,
Optional<String> wc3Path, Optional<String> gameExePath) {
this.langServer = langServer;
Expand Down Expand Up @@ -264,7 +268,16 @@ protected File compileMap(ModelManager modelManager, File projectFolder, WurstGu

String compiledMapScript = sb.toString();
LuaAssertions.assertNoLeakedNativeCalls(compiledMapScript);
luaHotReloadManifest = runArgs.isLuaHotReload() ? compiler.getLuaTranslator().hotReloadManifest() : null;
if (luaHotReloadManifest != null) {
long stored = luaHotReloadManifest.functions().values().stream().filter(f -> !f.captures().isEmpty()).count();
println("Lua hot reload: " + luaHotReloadManifest.functions().size() + " reloadable functions, "
+ stored + " of them stored as values (not reloadable)");
}
File buildDir = getBuildDir();
if (luaHotReloadManifest != null) {
LuaHotReloadSession.writeManifest(buildDir.toPath(), luaHotReloadManifest);
}
File outFile = new File(buildDir, BUILD_COMPILED_LUA_NAME);
Files.write(compiledMapScript.getBytes(Charsets.UTF_8), outFile);
return outFile;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import de.peeeq.wurstscript.WLogger;
import de.peeeq.wurstscript.attributes.CompileError;
import de.peeeq.wurstscript.gui.WurstGui;
import de.peeeq.wurstscript.translation.lua.translation.LuaHotReload;
import de.peeeq.wurstscript.utils.Utils;
import net.moonlightflower.wc3libs.port.GameVersion;
import net.moonlightflower.wc3libs.port.Orient;
Expand Down Expand Up @@ -126,6 +127,11 @@ private String compileMap(ModelManager modelManager, WurstGui gui, WurstProjectC
Optional<File> testMap = map.map($ -> new File(buildDir, "WurstRunMap.w3x"));
CompilationResult result = compileScript(modelManager, gui, testMap, projectConfig, buildDir, false);

if (runArgs.isHotReload() && runArgs.isLua()) {
sendLuaHotReloadPayload(gui, buildDir);
return "ok";
}

if (runArgs.isHotReload()) {
// call jhcr update
gui.sendProgress("Calling JHCR update");
Expand All @@ -138,7 +144,24 @@ private String compileMap(ModelManager modelManager, WurstGui gui, WurstProjectC


if (testMap.isPresent()) {
startGame(gui, result);
if (luaHotReloadManifest == null) {
startGame(gui, result);
} else {
// The new map polls from the first file. The session and the files of a map which is still running
// change only once the new map is launched, so a launch which fails leaves that map's session intact.
Path customMapData = getCustomMapDataPath();
LuaHotReloadSession.stage(buildDir.toPath(), luaHotReloadManifest);
LuaHotReloadSession.setAsideDeliveries(customMapData);
Comment thread
Frotty marked this conversation as resolved.
try {
startGame(gui, result);
} catch (Exception e) {
LuaHotReloadSession.restoreDeliveries(customMapData);
LuaHotReloadSession.unstage(buildDir.toPath());
throw e;
}
LuaHotReloadSession.activate(buildDir.toPath());
LuaHotReloadSession.discardDeliveries(customMapData);
}
}
return null;
}
Expand Down Expand Up @@ -411,6 +434,28 @@ protected enum MismatchChoice {
}


/**
* Sends the running Lua map the payload of the edits since it started (docs/LUA_HOT_RELOAD.md). The map polls the
* CustomMapData folder for it; an edit a payload cannot carry fails here, with the reasons, and changes nothing.
*/
private void sendLuaHotReloadPayload(WurstGui gui, File buildDir) throws IOException {
if (luaHotReloadManifest == null) {
throw new RequestFailedException(MessageType.Error, "Lua hot reload needs a Lua build;"
+ " start the map with hot run first.");
}
LuaHotReloadSession.Sent sent;
try {
sent = LuaHotReloadSession.send(buildDir.toPath(), getCustomMapDataPath(), luaHotReloadManifest);
} catch (LuaHotReload.Rejected e) {
throw new RequestFailedException(MessageType.Warning, e.getMessage());
}
gui.sendProgress("Hot reload payload " + sent.payload().version() + " (" + String.join(", ", sent.payload().functions())
+ ") written to " + sent.file());
for (String warning : sent.payload().warnings()) {
gui.showInfoMessage(warning);
}
}

private void callJhcrUpdate(File mapScript) throws IOException, InterruptedException {
File mapScriptFolder = mapScript.getParentFile();
File commonJ = new File(mapScriptFolder, "common.j");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class RunArgs {

private final String[] args;
private final RunOption optionLua;
private final RunOption optionLuaHotReload;
private final RunOption optionCompiletimeCache;
private final List<String> files = Lists.newArrayList();
private @Nullable String mapFile = null;
Expand Down Expand Up @@ -148,14 +149,16 @@ public RunArgs(String... args) {
optionDisablePjass = addOption("noPJass", "Disables PJass checks for the generated code.");
optionLegacyJassChecks = addOption("legacyJassChecks", "Relax Jass type checks for legacy (pre-1.24) targets whose Blizzard-provided "
+ "common.j/blizzard.j contain return-type mismatches the Jass VM tolerates. Also skips PJass on the generated script.");
optionHotStartmap = addOption("hotstart", "Uses Jass Hot Code Reload (JHCR) to start the map.");
optionHotReload = addOption("hotreload", "Reloads the mapscript after running the map with Jass Hot Code Reload (JHCR).");
optionHotStartmap = addOption("hotstart", "Starts the map so that it can be reloaded: with Jass Hot Code Reload (JHCR) on Jass, with -luaHotReload on Lua.");
optionHotReload = addOption("hotreload", "Reloads the running map's script: through JHCR on Jass, with a body payload on Lua (docs/LUA_HOT_RELOAD.md).");

optionBuild = addOption("build", "Builds an output map from the input map and library directories.");
optionDevBuild = addOption("dev", "Builds an output map in development/run mode, so compiletime isProductionBuild() is false.");
addOptionWithArg("workspaceroot", "The next argument should be the root folder of the project to build.", arg -> workspaceroot = arg);
addOptionWithArg("inputmap", "The next argument should be the input map.", arg -> inputmap = arg);
optionLua = addOption("lua", "Choose Lua as the compilation target.");
optionLuaHotReload = addOption("luaHotReload", "Lua development builds only: emits the script so that the running map "
+ "can replace function bodies (docs/LUA_HOT_RELOAD.md). Not combinable with -inline or -opt.");
optionCompiletimeCache = addOption("compiletimeCache", "(Experimental) Cache results of compiletime invocations without side effects");

addOptionWithArg("functionSplitLimit", "The maximum number of operations in a function before it is split by the function splitter (used for compiletime functions)",
Expand Down Expand Up @@ -431,6 +434,11 @@ public boolean isLua() {
return optionLua.isSet;
}

/** The editor's hot run and hot reload of a Lua project ({@code -hotstart}, {@code -hotreload}) imply it. */
public boolean isLuaHotReload() {
return optionLuaHotReload.isSet || (isLua() && (isHotStartmap() || isHotReload()));
}

public boolean isCompiletimeCache() {
return optionCompiletimeCache.isSet;
}
Expand Down
Loading
Loading