Skip to content

Commit a8bfb86

Browse files
committed
some more good progress
1 parent 73925cc commit a8bfb86

21 files changed

Lines changed: 1528 additions & 672 deletions

File tree

de.peeeq.wurstscript/build.gradle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,19 @@ dependencies {
8585

8686

8787
// Tests
88-
testImplementation 'org.testng:testng:7.8.0'
88+
testImplementation 'org.testng:testng:7.11.0'
8989
testImplementation 'org.hamcrest:hamcrest-all:1.3'
9090

9191
// Libs
9292
implementation 'com.google.guava:guava:32.1.3-jre'
9393
implementation 'io.vavr:vavr:0.10.7'
94-
implementation 'org.eclipse.lsp4j:org.eclipse.lsp4j:0.21.1'
94+
implementation 'org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.0'
9595
implementation 'org.eclipse.jdt:org.eclipse.jdt.annotation:2.1.0'
9696
implementation 'com.google.code.gson:gson:2.10.1'
9797
implementation 'org.apache.velocity:velocity:1.7'
9898
implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
99-
implementation 'com.github.inwc3:jmpq3:3183dd7680'
100-
implementation 'com.github.inwc3:wc3libs:c3f131a0e5'
99+
implementation 'com.github.inwc3:jmpq3:1676fc7020'
100+
implementation 'com.github.inwc3:wc3libs:e33d3dc368'
101101
implementation 'com.github.wurstscript:wurstsetup:475cc7fae8'
102102
implementation 'org.slf4j:slf4j-api:1.7.25'
103103
implementation 'ch.qos.logback:logback-classic:1.5.13'
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package de.peeeq.datastructures;
2+
3+
import de.peeeq.wurstscript.intermediatelang.optimizer.ControlFlowGraph.Node;
4+
import java.util.ArrayDeque;
5+
import java.util.Collection;
6+
7+
/**
8+
* Ultra-fast worklist specifically for CFG nodes.
9+
* Uses array-based tracking for even better performance.
10+
*/
11+
public class NodeWorklist {
12+
private final ArrayDeque<Node> queue = new ArrayDeque<>();
13+
private final boolean[] inQueue;
14+
private final Node[] allNodes;
15+
16+
public NodeWorklist(Collection<Node> nodes) {
17+
this.allNodes = nodes.toArray(new Node[0]);
18+
this.inQueue = new boolean[allNodes.length];
19+
20+
// Add all nodes initially
21+
for (int i = 0; i < allNodes.length; i++) {
22+
queue.add(allNodes[i]);
23+
inQueue[i] = true;
24+
}
25+
}
26+
27+
public boolean isEmpty() {
28+
return queue.isEmpty();
29+
}
30+
31+
public Node poll() {
32+
Node n = queue.poll();
33+
if (n != null) {
34+
inQueue[getIndex(n)] = false;
35+
}
36+
return n;
37+
}
38+
39+
public boolean add(Node n) {
40+
int idx = getIndex(n);
41+
if (!inQueue[idx]) {
42+
inQueue[idx] = true;
43+
queue.add(n);
44+
return true;
45+
}
46+
return false;
47+
}
48+
49+
public void addAll(Collection<Node> nodes) {
50+
for (Node n : nodes) {
51+
add(n);
52+
}
53+
}
54+
55+
private int getIndex(Node n) {
56+
// You'd need to add an index field to Node for O(1) lookup
57+
// For now, linear search (not ideal)
58+
for (int i = 0; i < allNodes.length; i++) {
59+
if (allNodes[i] == n) return i;
60+
}
61+
return -1;
62+
}
63+
}
Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,41 @@
11
package de.peeeq.datastructures;
22

3-
import java.util.ArrayDeque;
3+
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
4+
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
5+
46
import java.util.Collection;
5-
import java.util.HashSet;
67

78
/**
8-
* A queue with efficient lookup using a hashset.
9-
* <p>
10-
* No element is added to the queue more than once.
9+
* An optimized worklist that uses fastutil collections to reduce overhead and allocations.
1110
*/
1211
public class Worklist<T> {
13-
private final ArrayDeque<T> queue = new ArrayDeque<>();
14-
private final HashSet<T> set = new HashSet<>();
12+
private final ObjectArrayFIFOQueue<T> queue = new ObjectArrayFIFOQueue<>();
13+
private final ObjectOpenHashSet<T> set = new ObjectOpenHashSet<>();
1514

1615
public Worklist() {
1716
}
1817

1918
public Worklist(Iterable<? extends T> nodes) {
2019
for (T node : nodes) {
21-
addLast(node);
20+
add(node);
2221
}
2322
}
2423

2524
public boolean isEmpty() {
2625
return queue.isEmpty();
2726
}
2827

29-
30-
public void addFirst(T node) {
28+
public void add(T node) {
3129
if (set.add(node)) {
32-
queue.addFirst(node);
33-
}
34-
}
35-
36-
public void addLast(T node) {
37-
if (set.add(node)) {
38-
queue.addLast(node);
30+
queue.enqueue(node);
3931
}
4032
}
4133

4234
public T poll() {
43-
T result = queue.poll();
44-
if (result != null) {
45-
set.remove(result);
46-
}
35+
T result = queue.dequeue();
36+
// The element is removed from the queue but must also be removed from the set
37+
// so it can be added again later in the process.
38+
set.remove(result);
4739
return result;
4840
}
4941

@@ -53,7 +45,7 @@ public int size() {
5345

5446
public void addAll(Collection<? extends T> elems) {
5547
for (T elem : elems) {
56-
addLast(elem);
48+
add(elem);
5749
}
5850
}
5951
}

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

Lines changed: 147 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,92 @@
1313
import java.util.Collections;
1414
import java.util.List;
1515
import java.util.Optional;
16-
import java.util.concurrent.CompletableFuture;
17-
import java.util.concurrent.ExecutionException;
16+
import java.util.concurrent.*;
17+
import java.util.concurrent.atomic.AtomicReference;
1818

1919
/**
20-
*
20+
* Provides configuration values from the language client with caching and timeout protection.
21+
* Cache is updated asynchronously in the background to ensure changes are picked up.
2122
*/
2223
public class ConfigProvider {
2324
private final LanguageClient languageClient;
2425

26+
// Cache with thread-safe atomic reference
27+
private final AtomicReference<CachedConfig> cachedConfig = new AtomicReference<>(null);
28+
29+
// Background refresh executor
30+
private final ScheduledExecutorService refreshExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
31+
Thread t = new Thread(r, "ConfigProvider-Refresh");
32+
t.setDaemon(true);
33+
return t;
34+
});
35+
36+
// Configuration timeouts
37+
private static final long FETCH_TIMEOUT_MS = 500; // Fast timeout for blocking calls
38+
private static final long CACHE_DURATION_MS = 5000; // 5 seconds before refresh
39+
private static final long REFRESH_INTERVAL_MS = 10000; // Check for updates every 10 seconds
40+
41+
// Track if we've shown warning to avoid spam
42+
private volatile boolean hasShownTimeoutWarning = false;
43+
2544
public ConfigProvider(LanguageClient languageClient) {
2645
this.languageClient = languageClient;
46+
47+
// Start background refresh task
48+
refreshExecutor.scheduleAtFixedRate(
49+
this::refreshCacheAsync,
50+
REFRESH_INTERVAL_MS,
51+
REFRESH_INTERVAL_MS,
52+
TimeUnit.MILLISECONDS
53+
);
54+
55+
// Initial fetch (non-blocking)
56+
refreshCacheAsync();
2757
}
2858

59+
/**
60+
* Get configuration value with caching and timeout protection.
61+
* Returns cached value if available, otherwise attempts quick fetch with timeout.
62+
*/
2963
public String getConfig(String key, String defaultValue) {
64+
CachedConfig cached = cachedConfig.get();
65+
66+
// Return cached value if valid
67+
if (cached != null && cached.isValid()) {
68+
String value = cached.getValue(key);
69+
return value != null ? value : defaultValue;
70+
}
71+
72+
// Try quick fetch if no valid cache
73+
if (cached == null) {
74+
String value = fetchConfigWithTimeout(key, defaultValue);
75+
return value;
76+
}
77+
78+
// Return stale cache while refresh happens in background
79+
String value = cached.getValue(key);
80+
return value != null ? value : defaultValue;
81+
}
82+
83+
/**
84+
* Fetch config with timeout protection
85+
*/
86+
private String fetchConfigWithTimeout(String key, String defaultValue) {
3087
ConfigurationItem ci = new ConfigurationItem();
3188
ci.setSection("wurst");
32-
CompletableFuture<List<Object>> res = languageClient.configuration(new ConfigurationParams(Collections.singletonList(ci)));
89+
CompletableFuture<List<Object>> res = languageClient.configuration(
90+
new ConfigurationParams(Collections.singletonList(ci))
91+
);
92+
3393
try {
34-
List<Object> config = res.get();
94+
List<Object> config = res.get(FETCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
3595
for (Object c : config) {
3696
if (c instanceof JsonObject) {
3797
JsonObject cfg = (JsonObject) c;
98+
99+
// Update cache with full config
100+
cachedConfig.set(new CachedConfig(cfg));
101+
38102
JsonElement result = cfg.get(key);
39103
if (result instanceof JsonNull) {
40104
return null;
@@ -44,14 +108,46 @@ public String getConfig(String key, String defaultValue) {
44108
}
45109
}
46110
return defaultValue;
47-
} catch (InterruptedException | ExecutionException e) {
48-
String msg = "Could not get config " + key + ", using default value " + defaultValue;
49-
WLogger.warning(msg, e);
50-
languageClient.showMessage(new MessageParams(MessageType.Warning, msg));
111+
} catch (TimeoutException e) {
112+
if (!hasShownTimeoutWarning) {
113+
WLogger.warning("Config request timed out for " + key + " after " + FETCH_TIMEOUT_MS + "ms, using default: " + defaultValue);
114+
hasShownTimeoutWarning = true;
115+
}
116+
return defaultValue;
117+
} catch (InterruptedException e) {
118+
Thread.currentThread().interrupt();
119+
WLogger.warning("Config request interrupted for " + key + ", using default: " + defaultValue);
120+
return defaultValue;
121+
} catch (ExecutionException e) {
122+
WLogger.warning("Could not get config " + key + ", using default value " + defaultValue, e);
51123
return defaultValue;
52124
}
53125
}
54126

127+
/**
128+
* Asynchronously refresh the cache in background
129+
*/
130+
private void refreshCacheAsync() {
131+
ConfigurationItem ci = new ConfigurationItem();
132+
ci.setSection("wurst");
133+
134+
languageClient.configuration(new ConfigurationParams(Collections.singletonList(ci)))
135+
.thenAccept(config -> {
136+
for (Object c : config) {
137+
if (c instanceof JsonObject) {
138+
JsonObject cfg = (JsonObject) c;
139+
cachedConfig.set(new CachedConfig(cfg));
140+
WLogger.trace("Config cache refreshed successfully");
141+
return;
142+
}
143+
}
144+
})
145+
.exceptionally(e -> {
146+
WLogger.trace("Background config refresh failed (this is normal if client is busy): " + e.getMessage());
147+
return null;
148+
});
149+
}
150+
55151
public String getJhcrExe() {
56152
return getConfig("jhcrExe", "jhcr.exe");
57153
}
@@ -66,4 +162,46 @@ public Optional<String> getWc3RunArgs() {
66162
public Optional<String> getMapDocumentPath() {
67163
return Optional.ofNullable(getConfig("mapDocumentPath", null));
68164
}
165+
166+
/**
167+
* Shutdown the background refresh executor
168+
*/
169+
public void shutdown() {
170+
refreshExecutor.shutdown();
171+
try {
172+
if (!refreshExecutor.awaitTermination(1, TimeUnit.SECONDS)) {
173+
refreshExecutor.shutdownNow();
174+
}
175+
} catch (InterruptedException e) {
176+
refreshExecutor.shutdownNow();
177+
Thread.currentThread().interrupt();
178+
}
179+
}
180+
181+
/**
182+
* Immutable cached configuration with timestamp
183+
*/
184+
private static class CachedConfig {
185+
private final JsonObject config;
186+
private final long timestamp;
187+
188+
CachedConfig(JsonObject config) {
189+
this.config = config;
190+
this.timestamp = System.currentTimeMillis();
191+
}
192+
193+
boolean isValid() {
194+
return (System.currentTimeMillis() - timestamp) < CACHE_DURATION_MS;
195+
}
196+
197+
String getValue(String key) {
198+
JsonElement result = config.get(key);
199+
if (result instanceof JsonNull) {
200+
return null;
201+
} else if (result != null) {
202+
return result.getAsString();
203+
}
204+
return null;
205+
}
206+
}
69207
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/RunMap.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,14 @@ private void startGame(WurstGui gui, Optional<File> testMap, CompilationResult r
128128
timeTaker.beginPhase("Starting Warcraft 3");
129129
gui.sendProgress("Starting Warcraft 3...");
130130

131-
File mapCopy = copyToWarcraftMapDir(testMap.get());
131+
File mapCopy = testMap.get();
132+
if (w3data.getWc3PatchVersion().isPresent()) {
133+
GameVersion gameVersion = w3data.getWc3PatchVersion().get();
134+
if (gameVersion != VERSION_1_32) {
135+
mapCopy = copyToWarcraftMapDir(testMap.get());
136+
}
137+
}
138+
132139

133140
WLogger.info("Starting wc3 ... ");
134141
String path = "";

0 commit comments

Comments
 (0)