Skip to content

Commit 4476902

Browse files
committed
Really FREAKIN' cool IPC code (v1). Likely gonna move to EvLib and/or refactor heavily still, but wanted to take a snapshot
1 parent f78cb1b commit 4476902

1 file changed

Lines changed: 121 additions & 0 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package net.evmodder.evmod.apis;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.io.RandomAccessFile;
6+
import java.lang.invoke.MethodHandles;
7+
import java.lang.invoke.VarHandle;
8+
import java.nio.ByteBuffer;
9+
import java.nio.ByteOrder;
10+
import java.nio.MappedByteBuffer;
11+
import java.nio.channels.FileChannel;
12+
import java.util.ArrayList;
13+
14+
public final class JavaIPC{
15+
private static final class Holder{private static final JavaIPC INSTANCE = new JavaIPC();}
16+
public static final JavaIPC getInstance(){return Holder.INSTANCE;}
17+
18+
// Hopefully nobody is running more than this many Minecraft accounts on 1 device...
19+
private static final int MAX_SLOTS = 512;
20+
// UUID + serverHashCode + worldHashCode + x + y + z
21+
private static final int DATA_SIZE = 16 + 8 + 8 + 8 + 4 + 4;
22+
// PID + TS + lock + data
23+
private static final int SLOT_SIZE = 8 + 4 + 4 + DATA_SIZE;
24+
// 15 seconds to consider an instance "dead"
25+
private static final int TIMEOUT_MS = 15_000;
26+
27+
// Offsets within a slot
28+
private static final int PID_OFFSET = 0;
29+
private static final int TIME_OFFSET = 4;
30+
private static final int VERSION_OFFSET = 8;
31+
private static final int DATA_OFFSET = 12;
32+
33+
private static final VarHandle LONG_HANDLE = MethodHandles.byteBufferViewVarHandle(long[].class, ByteOrder.nativeOrder());
34+
private static final VarHandle INT_HANDLE = MethodHandles.byteBufferViewVarHandle(int[].class, ByteOrder.nativeOrder());
35+
36+
private static final long myPID = ProcessHandle.current().pid();
37+
private static final ByteBuffer bb = ByteBuffer.allocate(DATA_SIZE);
38+
private final MappedByteBuffer buffer;
39+
private int mySlot;
40+
41+
private JavaIPC(){
42+
final File file = new File(System.getProperty("java.io.tmpdir"), "minecraft_player_pos.dat");
43+
MappedByteBuffer temp = null;
44+
try(final RandomAccessFile raf = new RandomAccessFile(file, "rw")){
45+
final long TOTAL_SIZE = (long) MAX_SLOTS*SLOT_SIZE;
46+
if(raf.length() < TOTAL_SIZE) raf.setLength(TOTAL_SIZE);
47+
temp = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, TOTAL_SIZE);
48+
}
49+
catch(IOException e){
50+
System.err.println("[EvMod] CRITICAL: Failed to initialize shared memory file");
51+
e.printStackTrace();
52+
temp = null;
53+
return;
54+
}
55+
finally{buffer = temp;}
56+
Runtime.getRuntime().addShutdownHook(new Thread(()->{
57+
// Mark owner PID as -1 so others can take it immediately (rather than waiting for heartbeat)
58+
if(buffer != null && myPID == (long)LONG_HANDLE.getVolatile(buffer, mySlot*SLOT_SIZE + PID_OFFSET)){
59+
LONG_HANDLE.compareAndSet(buffer, mySlot*SLOT_SIZE + PID_OFFSET, myPID, -1l);
60+
}
61+
}));
62+
}
63+
64+
private record PosData(int server, int world, double x, double y, double z){}
65+
public final ArrayList<PosData> readPeerData(){
66+
final ArrayList<PosData> peerData = new ArrayList<>();
67+
final int now = (int)System.currentTimeMillis();
68+
// int idx = 0;
69+
for(int i=0; i<MAX_SLOTS; ++i){
70+
final int base = i*SLOT_SIZE;
71+
final long owner = (long)LONG_HANDLE.getVolatile(buffer, base + PID_OFFSET);
72+
if(owner < 0 || owner == myPID) continue;
73+
if(owner == 0) return peerData;
74+
if(now - (int)INT_HANDLE.getVolatile(buffer, base + TIME_OFFSET) > TIMEOUT_MS){
75+
LONG_HANDLE.compareAndSet(buffer, base + PID_OFFSET, owner, -1l);
76+
continue;
77+
}
78+
// while(((long)LONG_HANDLE.getAcquire(buffer, base + VERSION_OFFSET)&1) == 0) Thread.onSpinWait();
79+
final long version = (long)LONG_HANDLE.getAcquire(buffer, base + VERSION_OFFSET); // Snapshot the data version
80+
if((version&1) == 0) continue; // Even = data is actively being written (not safe to read)
81+
buffer.get(base + DATA_OFFSET, bb.array()); // Read data
82+
if((long)LONG_HANDLE.getVolatile(buffer, base + VERSION_OFFSET) == version){ // Ensure data wasn't changed mid-read
83+
// peerData[idx++] = data;
84+
peerData.add(new PosData(bb.getInt(), bb.getInt(), bb.getDouble(), bb.getDouble(), bb.getDouble()));
85+
bb.rewind();
86+
}
87+
}
88+
// // peerData is usually returned far earlier, due to encountering any ownerPID==0.
89+
// return idx == MAX_SLOTS ? peerData : Arrays.copyOfRange(peerData, 0, idx);
90+
return peerData;
91+
}
92+
93+
private final int claimSlot(){
94+
final int now = (int)System.currentTimeMillis();
95+
for(int i=0; i<MAX_SLOTS; ++i){
96+
final int base = i*SLOT_SIZE;
97+
final long owner = (long)LONG_HANDLE.getVolatile(buffer, base + PID_OFFSET);
98+
final int lastHeartbeat = (int)INT_HANDLE.getVolatile(buffer, base + TIME_OFFSET);
99+
if(owner > 0 && now - lastHeartbeat < TIMEOUT_MS) continue;
100+
LONG_HANDLE.setVolatile(buffer, base + TIME_OFFSET, now); // Update ts (reduces contention fighting for this slot)
101+
if(LONG_HANDLE.compareAndSet(buffer, base + PID_OFFSET, owner, myPID)) return i; // Nice, we snagged this slot!
102+
i=-1; // Another PID grabbed the slot before us; start again from i=0.
103+
}
104+
assert false : "shared-mem table is out of slots!";
105+
return -1; // Failed to acquire a slot!!
106+
}
107+
108+
public final void postMyData(final byte[] data){
109+
assert data.length == DATA_SIZE;
110+
// Ensure my slot is still valid (and update it if not).
111+
if(myPID != (long)LONG_HANDLE.getVolatile(buffer, mySlot*SLOT_SIZE + PID_OFFSET) && (mySlot=claimSlot()) == -1) return;
112+
final int base = mySlot*SLOT_SIZE;
113+
final long currentVer = (long)LONG_HANDLE.get(buffer, base + VERSION_OFFSET); // Get current version
114+
final long nextEvenVer = (currentVer+1)&-2;
115+
LONG_HANDLE.setRelease(buffer, base + VERSION_OFFSET, nextEvenVer); // Move current version to EVEN (signals write in-progress)
116+
LONG_HANDLE.setVolatile(buffer, base + TIME_OFFSET, System.currentTimeMillis()); // Heartbeat
117+
buffer.put(base + DATA_OFFSET, data); // Publish data
118+
// buffer.position(base + DATA_OFFSET).put(data); // Publish data
119+
LONG_HANDLE.setRelease(buffer, base + VERSION_OFFSET, nextEvenVer + 1); // Move current version to ODD (signals write completed)
120+
}
121+
}

0 commit comments

Comments
 (0)