Skip to content

Commit 5bbfd8b

Browse files
committed
Clear metatable cache when updating values on existing nodes
Fixes #90
1 parent 28a1fa2 commit 5bbfd8b

File tree

3 files changed

+63
-10
lines changed

3 files changed

+63
-10
lines changed

src/main/java/org/squiddev/cobalt/LuaTable.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,17 @@ private static LuaValue value(Object[] values, int slot, boolean weak) {
623623
return strengthened;
624624
}
625625

626+
/**
627+
* Set the value of a node. This is the inverse of {@link #value(int)}.
628+
*
629+
* @param slot The node slot.
630+
* @param value The new value.
631+
*/
632+
private void setNodeValue(int slot, LuaValue value) {
633+
values[slot] = weakValues ? weaken(value) : value;
634+
metatableFlags = 0;
635+
}
636+
626637
/**
627638
* Insert a new key into a hash table.
628639
* <p>
@@ -793,7 +804,7 @@ private boolean trySet(int key, LuaValue value, LuaValue keyValue) {
793804
if (hasNewIndex()) return false;
794805
} else {
795806
if (value(node) == NIL && hasNewIndex()) return false;
796-
values[node] = weakValues ? weaken(value) : value;
807+
setNodeValue(node, value);
797808
return true;
798809
}
799810

@@ -818,7 +829,7 @@ boolean trySet(LuaValue key, LuaValue value) throws LuaError {
818829
if (hasNewIndex()) return false;
819830
} else {
820831
if (value(node) == NIL && hasNewIndex()) return false;
821-
values[node] = weakValues ? weaken(value) : value;
832+
setNodeValue(node, value);
822833
return true;
823834
}
824835

@@ -846,7 +857,7 @@ private void rawset(int key, LuaValue value, LuaValue valueOf) {
846857

847858
// newKey will have handled this otherwise
848859
if (node != -1) {
849-
values[node] = weakValues ? weaken(value) : value;
860+
setNodeValue(node, value);
850861
return;
851862
}
852863
} while (true);
@@ -870,9 +881,7 @@ public void rawsetImpl(LuaValue key, LuaValue value) {
870881

871882
// newKey will have handled this otherwise
872883
if (node != -1) {
873-
// if (value.isNil() && !weakKeys) node.key = weaken((LuaValue) node.key);
874-
values[node] = weakValues ? weaken(value) : value;
875-
metatableFlags = 0;
884+
setNodeValue(node, value);
876885
return;
877886
}
878887
} while (true);

src/test/java/org/squiddev/cobalt/table/TableOperations.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package org.squiddev.cobalt.table;
22

3-
import org.squiddev.cobalt.LuaError;
4-
import org.squiddev.cobalt.LuaTable;
5-
import org.squiddev.cobalt.LuaValue;
6-
import org.squiddev.cobalt.Varargs;
3+
import org.squiddev.cobalt.*;
74

85
import java.lang.reflect.Array;
96
import java.lang.reflect.Field;
7+
import java.lang.reflect.Method;
108
import java.util.ArrayList;
119
import java.util.Collection;
1210
import java.util.List;
@@ -20,9 +18,11 @@ public final class TableOperations {
2018
private static final Field nodes;
2119
private static final Field array;
2220
private static final Field lastFree;
21+
private static final Method trySet;
2322

2423
static {
2524
Field nodesField, arrayField, lastFreeField;
25+
Method trySetMethod;
2626
try {
2727
nodesField = LuaTable.class.getDeclaredField("keys");
2828
nodesField.setAccessible(true);
@@ -32,12 +32,16 @@ public final class TableOperations {
3232

3333
lastFreeField = LuaTable.class.getDeclaredField("lastFree");
3434
lastFreeField.setAccessible(true);
35+
36+
trySetMethod = LuaTable.class.getDeclaredMethod("trySet", LuaValue.class, LuaValue.class);
37+
trySetMethod.setAccessible(true);
3538
} catch (ReflectiveOperationException e) {
3639
throw new RuntimeException(e);
3740
}
3841
nodes = nodesField;
3942
array = arrayField;
4043
lastFree = lastFreeField;
44+
trySet = trySetMethod;
4145
}
4246

4347
private TableOperations() {
@@ -105,4 +109,22 @@ public static int getLastFree(LuaTable table) {
105109
throw new RuntimeException(e);
106110
}
107111
}
112+
113+
/**
114+
* Set a value on a table, with the same behaviour as {@link OperationHelper#setTable(LuaState, LuaValue, LuaValue, LuaValue)}.
115+
*
116+
* @param table The table to update.
117+
* @param key The key to set.
118+
* @param value The value to set.
119+
*/
120+
public static void setValue(LuaTable table, LuaValue key, LuaValue value) throws LuaError {
121+
boolean success;
122+
try {
123+
success = (boolean) trySet.invoke(table, key, value);
124+
} catch (ReflectiveOperationException e) {
125+
throw new RuntimeException(e);
126+
}
127+
128+
if (!success) table.rawset(key, value);
129+
}
108130
}

src/test/java/org/squiddev/cobalt/table/TableTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import org.junit.jupiter.api.Test;
2828
import org.squiddev.cobalt.*;
29+
import org.squiddev.cobalt.function.LibFunction;
2930

3031
import java.util.ArrayList;
3132

@@ -277,4 +278,25 @@ public void testPresizeRehashes() throws LuaError {
277278
assertEquals(Constants.NIL, t.next(valueOf(7)));
278279
}
279280

281+
@Test
282+
public void testCachedMetamethod() throws LuaError {
283+
var t = new LuaTable();
284+
var f = LibFunction.create(s -> Constants.NIL);
285+
286+
// The field is absent initially.
287+
assertEquals(Constants.NIL, t.rawget(CachedMetamethod.INDEX));
288+
289+
// After setting the field, it is now present.
290+
TableOperations.setValue(t, CachedMetamethod.INDEX.getKey(), f);
291+
assertEquals(f, t.rawget(CachedMetamethod.INDEX));
292+
293+
// If we clear it, the field is no longer there.
294+
TableOperations.setValue(t, CachedMetamethod.INDEX.getKey(), Constants.NIL);
295+
assertEquals(Constants.NIL, t.rawget(CachedMetamethod.INDEX));
296+
297+
// HOWEVER, the node *is* still present. So setting it again will not create a new node. Ensure that we still
298+
// reset the cache.
299+
TableOperations.setValue(t, CachedMetamethod.INDEX.getKey(), f);
300+
assertEquals(f, t.rawget(CachedMetamethod.INDEX));
301+
}
280302
}

0 commit comments

Comments
 (0)