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