Created
September 18, 2023 10:54
-
-
Save donadigo/c010cd682c9b8eec0dbe3f23dab4188b to your computer and use it in GitHub Desktop.
TMInterface socket example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import socket | |
import struct | |
import time | |
import signal | |
from tminterface.structs import SimStateData, CheckpointData | |
HOST = "127.0.0.1" | |
PORT = 8477 | |
SC_RUN_STEP_SYNC = 0 | |
C_SET_SPEED = 1 | |
C_REWIND_TO_STATE = 2 | |
C_SET_INPUT_STATE = 3 | |
C_SHUTDOWN = 4 | |
sock = None | |
def signal_handler(sig, frame): | |
global sock | |
print('Shutting down...') | |
sock.sendall(struct.pack('i', C_SHUTDOWN)) | |
sock.close() | |
def rewind_to_state(sock, state): | |
sock.sendall(struct.pack('i', C_REWIND_TO_STATE)) | |
sock.sendall(struct.pack('i', len(state.data))) | |
sock.sendall(state.data) | |
def set_input_state(sock, up=-1, down=-1, steer=0x7FFFFFFF): | |
sock.sendall(struct.pack('i', C_SET_INPUT_STATE)) | |
sock.sendall(struct.pack('b', up)) | |
sock.sendall(struct.pack('b', down)) | |
sock.sendall(struct.pack('i', steer)) | |
def respond(sock, type): | |
sock.sendall(struct.pack('i', type)) | |
def main(): | |
global sock | |
first_state = 0 | |
ticks_per_second = 0 | |
now = time.time() | |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
signal.signal(signal.SIGINT, signal_handler) | |
sock.connect((HOST, PORT)) | |
print('Connected') | |
while True: | |
message_type = struct.unpack('i', sock.recv(4))[0] | |
if message_type == SC_RUN_STEP_SYNC: | |
state_length = struct.unpack('i', sock.recv(4))[0] | |
state = SimStateData(sock.recv(state_length)) | |
state.cp_data.resize(CheckpointData.cp_states_field, state.cp_data.cp_states_length) | |
state.cp_data.resize(CheckpointData.cp_times_field, state.cp_data.cp_times_length) | |
race_time = state.player_info.race_time | |
if race_time == 0: | |
first_state = state | |
set_input_state(sock, up=True) | |
if race_time == 3000: | |
set_input_state(sock, steer=-65536) | |
if race_time > 0 and race_time % 5000 == 0 and first_state: | |
rewind_to_state(sock, first_state) | |
set_input_state(sock, up=True, steer=65536) | |
respond(sock, SC_RUN_STEP_SYNC) | |
if time.time() - now > 1: | |
print(f'Effective speed: {ticks_per_second / 100}x') | |
now = time.time() | |
ticks_per_second = 0 | |
ticks_per_second += 1 | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Net::Socket@ sock = null; | |
Net::Socket@ clientSock = null; | |
enum MessageType { | |
SCRunStepSync = 0, | |
CSetSpeed, | |
CRewindToState, | |
CSetInputState, | |
CShutdown | |
} | |
const string HOST = "127.0.0.1"; | |
const uint16 PORT = 8477; | |
const uint RESPONSE_TIMEOUT = 2000; | |
void WaitForResponse(MessageType type) | |
{ | |
auto now = Time::Now; | |
while (true) { | |
auto receivedType = HandleMessage(); | |
if (receivedType == type) { | |
break; | |
} | |
if (receivedType == MessageType::CShutdown) { | |
break; | |
} | |
if (receivedType == -1 && Time::Now - now > RESPONSE_TIMEOUT) { | |
log("Client disconnected due to timeout (" + RESPONSE_TIMEOUT + "ms)"); | |
@clientSock = null; | |
break; | |
} | |
} | |
} | |
int HandleMessage() | |
{ | |
if (clientSock.Available == 0) { | |
return -1; | |
} | |
auto type = clientSock.ReadInt32(); | |
switch(type) { | |
case MessageType::SCRunStepSync: { | |
break; | |
} | |
case MessageType::CSetSpeed: { | |
auto@ simManager = GetSimulationManager(); | |
simManager.SetSpeed(clientSock.ReadFloat()); | |
break; | |
} | |
case MessageType::CRewindToState: { | |
auto stateLength = clientSock.ReadInt32(); | |
auto stateData = clientSock.ReadBytes(stateLength); | |
auto@ simManager = GetSimulationManager(); | |
if (simManager.InRace) { | |
SimulationState state(stateData); | |
simManager.RewindToState(state); | |
} | |
break; | |
} | |
case MessageType::CSetInputState: { | |
int8 up = clientSock.ReadInt8(); | |
int8 down = clientSock.ReadInt8(); | |
int steer = clientSock.ReadInt32(); | |
auto@ simManager = GetSimulationManager(); | |
if (simManager.InRace) { | |
log("Set input state"); | |
simManager.SetInputState(InputType::Up, up); | |
simManager.SetInputState(InputType::Down, down); | |
if (steer != 0x7FFFFFFF) { | |
simManager.SetInputState(InputType::Steer, steer); | |
} | |
} | |
break; | |
} | |
case MessageType::CShutdown: { | |
log("Client disconnected"); | |
@clientSock = null; | |
break; | |
} | |
} | |
return type; | |
} | |
void OnRunStep(SimulationManager@ simManager) | |
{ | |
if (@clientSock is null) { | |
return; | |
} | |
auto@ state = simManager.SaveState(); | |
auto@ data = state.ToArray(); | |
clientSock.Write(MessageType::SCRunStepSync); | |
clientSock.Write(data.Length); | |
clientSock.Write(data); | |
WaitForResponse(MessageType::SCRunStepSync); | |
} | |
void Main() | |
{ | |
if (@sock is null) { | |
@sock = Net::Socket(); | |
sock.Listen(HOST, PORT); | |
} | |
} | |
void Render() | |
{ | |
auto @newSock = sock.Accept(0); | |
if (@newSock !is null) { | |
@clientSock = @newSock; | |
log("Client connected (IP: " + clientSock.RemoteIP + ")"); | |
} | |
} | |
PluginInfo@ GetPluginInfo() | |
{ | |
PluginInfo info; | |
info.Author = "donadigo"; | |
info.Name = "Test"; | |
info.Description = "Sockets example"; | |
info.Version = "1.0.0"; | |
return info; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment