Skip to content

Commit e7532e1

Browse files
authored
More objediting fixes & AppCDS (#1198)
1 parent 663a2e1 commit e7532e1

13 files changed

Lines changed: 677 additions & 54 deletions

File tree

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434
matrix: ${{ fromJson(needs.setup.outputs.matrix) }}
3535
runs-on: ${{ matrix.os }}
3636
permissions:
37+
checks: write
3738
contents: read
3839
env:
3940
JAVA_TOOL_OPTIONS: >-

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,5 @@ WurstSetup/proguard.map
5858
de.peeeq.wurstscript/output.txt
5959
/HelperScripts/gamedata
6060
/HelperScripts/.gradle
61+
/.gradle-user-home
62+
/gradle-home-temp

de.peeeq.wurstscript/deploy.gradle

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def jreImageDir = layout.buildDirectory.dir("jre-wurst-25")
4242

4343
def distRoot = layout.buildDirectory.dir("dist/slim-${plat}")
4444
def releasesDir = layout.buildDirectory.dir("releases")
45+
def appCdsArchive = distRoot.map { it.file("wurst-runtime/wurst-lsp.jsa") }
4546

4647
// ----------------------- Toolchain / tool paths (providers) -------------------------
4748
def toolchainSvc = extensions.getByType(JavaToolchainService)
@@ -223,11 +224,51 @@ tasks.named("assembleSlimCompilerDist", Copy) { t ->
223224
}
224225
}
225226

227+
tasks.register("generateAppCdsArchive", Exec) {
228+
description = "Best-effort AppCDS archive generation for language-server startup."
229+
group = "distribution"
230+
dependsOn("assembleSlimCompilerDist")
231+
232+
inputs.file(fatJar)
233+
inputs.dir(distRoot)
234+
outputs.file(appCdsArchive)
235+
ignoreExitValue = true
236+
237+
doFirst {
238+
def distDir = distRoot.get().asFile
239+
def archiveFile = appCdsArchive.get().asFile
240+
def runtimeJava = new File(distDir, "wurst-runtime/bin/java${os.isWindows() ? '.exe' : ''}")
241+
def compilerJar = new File(distDir, "wurst-compiler/${fatJar.get().asFile.name}")
242+
243+
archiveFile.parentFile.mkdirs()
244+
if (archiveFile.exists()) {
245+
archiveFile.delete()
246+
}
247+
248+
executable = runtimeJava.absolutePath
249+
args "-Xshare:auto",
250+
"-XX:ArchiveClassesAtExit=${archiveFile.absolutePath}",
251+
"-jar", compilerJar.absolutePath,
252+
"-languageServerAppCdsTrain"
253+
254+
logger.lifecycle("[appcds] Training LS startup with ${runtimeJava.absolutePath}")
255+
}
256+
257+
doLast {
258+
def archiveFile = appCdsArchive.get().asFile
259+
if (executionResult.get().exitValue != 0 || !archiveFile.exists()) {
260+
logger.warn("[appcds] Archive generation did not succeed on ${plat}; continuing without shipped AppCDS archive.")
261+
} else {
262+
logger.lifecycle("[appcds] Wrote ${archiveFile.absolutePath}")
263+
}
264+
}
265+
}
266+
226267
// 4) Package ZIP on all platforms
227268
tasks.register("packageSlimCompilerDistZip", Zip) {
228269
description = "Packages slim dist as a ZIP archive (all platforms)."
229270
group = "distribution"
230-
dependsOn("assembleSlimCompilerDist")
271+
dependsOn("generateAppCdsArchive")
231272

232273
from(distRoot)
233274
destinationDirectory.set(releasesDir)

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/Main.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ public static void main(String[] args) {
7979
return;
8080
}
8181

82+
if (runArgs.isLanguageServerAppCdsTrain()) {
83+
LanguageServerStarter.trainForAppCds();
84+
return;
85+
}
86+
8287
if (runArgs.isLanguageServer()) {
8388
LanguageServerStarter.start();
8489
return;

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/ProgramStateIO.java

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -616,9 +616,11 @@ public static void exportToWurst(List<? extends ObjMod.Obj> customObjs, ObjectFi
616616
* <p>Fields that have no known wrapper method are emitted as commented-out raw
617617
* calls so the output is still useful even when coverage is incomplete.
618618
*
619-
* <p>Returns {@code false} only when there is no wrapper class at all for this
619+
* <p>Returns {@code false} when there is no wrapper class at all for this
620620
* object type / base ID (e.g. doodads, upgrades, or an unknown ability base ID),
621-
* in which case the caller should fall back to the fully raw format.
621+
* or when a mapped wrapper setter exists but its parameter type is not yet
622+
* supported for source re-emission. In those cases the caller should fall back
623+
* to the fully raw format so no object data is lost on re-export.
622624
*/
623625
private static boolean tryExportWithWrapper(Appendable out, ObjectFileType fileType,
624626
String newId, String oldId,
@@ -677,11 +679,8 @@ private static boolean tryExportWithWrapper(Appendable out, ObjectFileType fileT
677679
return false;
678680
}
679681

680-
for (ObjMod.Obj.Mod m : mods) {
681-
StdlibObjectMappings.FieldMethodInfo info = fieldMethods.get(fieldKey(m, fileType));
682-
if (info != null && !canUseWrapperForMod(m, info)) {
683-
return false;
684-
}
682+
if (hasUnsupportedMappedWrapperField(mods, fieldMethods, fileType)) {
683+
return false;
685684
}
686685

687686
out.append("@compiletime function create_").append(fileType.getExt()).append("_").append(newId)
@@ -690,7 +689,7 @@ private static boolean tryExportWithWrapper(Appendable out, ObjectFileType fileT
690689

691690
for (ObjMod.Obj.Mod m : mods) {
692691
StdlibObjectMappings.FieldMethodInfo info = fieldMethods.get(fieldKey(m, fileType));
693-
if (info != null) {
692+
if (info != null && canUseWrapperForMod(m, info)) {
694693
out.append("\t..").append(info.methodName()).append("(");
695694
if (info.hasLevel() && m instanceof ObjMod.Obj.ExtendedMod) {
696695
out.append(String.valueOf(((ObjMod.Obj.ExtendedMod) m).getLevel())).append(", ");
@@ -707,6 +706,21 @@ private static boolean tryExportWithWrapper(Appendable out, ObjectFileType fileT
707706
return true;
708707
}
709708

709+
private static boolean hasUnsupportedMappedWrapperField(List<ObjMod.Obj.Mod> mods,
710+
Map<String, StdlibObjectMappings.FieldMethodInfo> fieldMethods,
711+
ObjectFileType fileType) {
712+
for (ObjMod.Obj.Mod mod : mods) {
713+
StdlibObjectMappings.FieldMethodInfo info = fieldMethods.get(fieldKey(mod, fileType));
714+
if (info == null) {
715+
continue;
716+
}
717+
if (!canUseWrapperForMod(mod, info)) {
718+
return true;
719+
}
720+
}
721+
return false;
722+
}
723+
710724
/**
711725
* Returns the lookup key used to match a mod to a {@link StdlibObjectMappings.FieldMethodInfo}.
712726
* Uses the mod's actual dataPtr when it is an {@link ObjMod.Obj.ExtendedMod}, regardless of
@@ -724,8 +738,7 @@ static String fieldKey(ObjMod.Obj.Mod m, ObjectFileType fileType) {
724738
}
725739

726740
private static boolean canUseWrapperForMod(ObjMod.Obj.Mod m, StdlibObjectMappings.FieldMethodInfo info) {
727-
if (!info.parameterType().isEmpty() && !isPrimitiveParameter(info.parameterType())
728-
&& !isEnumParameter(info.parameterType())) {
741+
if (!supportsWrapperParameterType(info.parameterType())) {
729742
return false;
730743
}
731744
if (isEnumParameter(info.parameterType())) {
@@ -734,6 +747,12 @@ private static boolean canUseWrapperForMod(ObjMod.Obj.Mod m, StdlibObjectMapping
734747
return true;
735748
}
736749

750+
private static boolean supportsWrapperParameterType(String parameterType) {
751+
return parameterType.isEmpty()
752+
|| isPrimitiveParameter(parameterType)
753+
|| isEnumParameter(parameterType);
754+
}
755+
737756
private static boolean isEnumParameter(String parameterType) {
738757
return ENUM_OBJECT_STRING_TO_CONSTANT.containsKey(parameterType);
739758
}
@@ -848,6 +867,7 @@ private static Map<String, String> enumConstants(String... valueConstantPairs) {
848867
"mline", "MissileLine"
849868
),
850869
"WeaponSound", enumConstants(
870+
"", "Nothing",
851871
"Nothing", "Nothing",
852872
"AxeMediumChop", "AxeMediumChop",
853873
"MetalHeavyBash", "MetalHeavyBash",

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageServerStarter.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,15 @@ public static void start() {
2222
server.setRemoteEndpoint(launcher.getRemoteEndpoint());
2323
}
2424

25+
public static void trainForAppCds() {
26+
WurstLanguageServer server = new WurstLanguageServer();
27+
try {
28+
server.getTextDocumentService();
29+
server.getWorkspaceService();
30+
} finally {
31+
server.shutdown().join();
32+
}
33+
}
34+
2535

2636
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageWorker.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public void setRootPath(WFile rootPath) {
5858
lock.notify();
5959
}
6060
});
61+
private boolean initialBuildPending = false;
6162
private final BufferManager bufferManager = new BufferManager();
6263
private LanguageClient languageClient;
6364

@@ -200,9 +201,6 @@ private Workitem getNextWorkItem() {
200201
changesToReconcile = ModelManager.Changes.empty();
201202
reconcileNowRequested = false;
202203
return new Workitem("reconcile files (save)", () -> modelManager.reconcile(changes));
203-
} else if (!userRequests.isEmpty()) {
204-
UserRequest<?> req = userRequests.remove();
205-
return new Workitem(req.toString(), () -> req.run(modelManager));
206204
} else if (!changes.isEmpty()) {
207205
// TODO this can be done more efficiently than doing one at a time
208206
PendingChange change = removeFirst(changes);
@@ -246,6 +244,12 @@ private Workitem getNextWorkItem() {
246244
return new Workitem("reconcile files", () -> {
247245
modelManager.reconcile(changes);
248246
});
247+
} else if (initialBuildPending) {
248+
initialBuildPending = false;
249+
return new Workitem("initial full build", () -> doInitialBuild());
250+
} else if (!userRequests.isEmpty()) {
251+
UserRequest<?> req = userRequests.remove();
252+
return new Workitem(req.toString(), () -> req.run(modelManager));
249253
}
250254
return null;
251255
}
@@ -271,11 +275,20 @@ private void doInit(WFile rootPath) {
271275
log("Handle init " + rootPath);
272276
modelManager = new ModelManagerImpl(rootPath.getFile(), bufferManager);
273277
modelManager.onCompilationResult(this::onCompilationResult);
278+
initialBuildPending = true;
279+
} catch (Exception e) {
280+
WLogger.severe(e);
281+
}
282+
}
274283

275-
log("Start building " + rootPath);
284+
private void doInitialBuild() {
285+
try {
286+
if (modelManager == null || rootPath == null) {
287+
return;
288+
}
289+
log("Start background full build " + rootPath);
276290
modelManager.buildProject();
277-
278-
log("Finished building " + rootPath);
291+
log("Finished background full build " + rootPath);
279292
} catch (Exception e) {
280293
WLogger.severe(e);
281294
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ModelManagerImpl.java

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -516,20 +516,11 @@ private void resolveImports(WurstGui gui) {
516516
}
517517

518518
private void replaceCompilationUnit(WFile filename) {
519-
File f;
520519
try {
521-
f = filename.getFile();
522-
} catch (FileNotFoundException e) {
523-
WLogger.info("Cannot replaceCompilationUnit for " + filename + "\n" + e);
524-
return;
525-
}
526-
if (!f.exists()) {
527-
removeCompilationUnit(filename);
528-
return;
529-
}
530-
try {
531-
String contents = Files.toString(f, Charsets.UTF_8);
532-
bufferManager.updateFile(WFile.create(f), contents);
520+
String contents = readCompilationUnitContents(filename, true);
521+
if (contents == null) {
522+
return;
523+
}
533524
replaceCompilationUnit(filename, contents, true);
534525
} catch (IOException e) {
535526
WLogger.severe(e);
@@ -582,13 +573,10 @@ public Changes syncCompilationUnit(WFile f) {
582573
WLogger.debug("syncCompilationUnit File " + f);
583574
String contents;
584575
try {
585-
File file = f.getFile();
586-
if (!file.exists()) {
587-
removeCompilationUnit(f);
576+
contents = readCompilationUnitContents(f, true);
577+
if (contents == null) {
588578
return Changes.empty();
589579
}
590-
contents = Files.toString(file, Charsets.UTF_8);
591-
bufferManager.updateFile(WFile.create(file), contents);
592580
} catch (IOException e) {
593581
WLogger.severe(e);
594582
throw new ModelManagerException(e);
@@ -608,6 +596,28 @@ public Changes syncCompilationUnit(WFile f) {
608596
return new Changes(io.vavr.collection.HashSet.of(f), oldPackages);
609597
}
610598

599+
private @Nullable String readCompilationUnitContents(WFile filename, boolean preferOpenBuffer) throws IOException {
600+
if (preferOpenBuffer && bufferManager.getTextDocumentVersion(filename) >= 0) {
601+
return bufferManager.getBuffer(filename);
602+
}
603+
File file;
604+
try {
605+
file = filename.getFile();
606+
} catch (FileNotFoundException e) {
607+
WLogger.info("Cannot read compilation unit for " + filename + "\n" + e);
608+
return null;
609+
}
610+
if (!file.exists()) {
611+
removeCompilationUnit(filename);
612+
return null;
613+
}
614+
String contents = Files.toString(file, Charsets.UTF_8);
615+
if (bufferManager.getTextDocumentVersion(filename) < 0) {
616+
bufferManager.updateFile(WFile.create(file), contents);
617+
}
618+
return contents;
619+
}
620+
611621
private CompilationUnit replaceCompilationUnit(WFile filename, String contents, boolean reportErrors) {
612622
if (!isInWurstFolder(filename) && !isAlreadyLoaded(filename)) {
613623
return null;

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/RunArgs.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class RunArgs {
4242
private final RunOption optionExtractImports;
4343
private final RunOption optionStartServer;
4444
private final RunOption optionLanguageServer;
45+
private final RunOption optionLanguageServerAppCdsTrain;
4546
private final RunOption optionNoExtractMapScript;
4647
private final RunOption optionFixInstall;
4748
private final RunOption optionCopyMap;
@@ -136,6 +137,7 @@ public RunArgs(String... args) {
136137

137138
optionLanguageServer = addOption("languageServer", "Starts a language server which can be used by editors to get services "
138139
+ "like code completion, validations, and find declaration. The communication to the language server is via standard input output.");
140+
optionLanguageServerAppCdsTrain = addOption("languageServerAppCdsTrain", "Starts and immediately stops a lightweight language-server startup path for AppCDS training.");
139141

140142
optionHelp = addOption("help", "Prints this help message.");
141143
optionDisablePjass = addOption("noPJass", "Disables PJass checks for the generated code.");
@@ -352,6 +354,10 @@ public boolean isLanguageServer() {
352354
return optionLanguageServer.isSet;
353355
}
354356

357+
public boolean isLanguageServerAppCdsTrain() {
358+
return optionLanguageServerAppCdsTrain.isSet;
359+
}
360+
355361
public boolean isNoExtractMapScript() {
356362
return optionNoExtractMapScript.isSet;
357363
}

0 commit comments

Comments
 (0)