If you 'restart' (specifically restart game, the little
-ish button next to play/pause) in the Simulator, while using the C SDK, when a TCP connection is open, and a TCPConnectionCallback callback is set, the Simulator crashes.
This doesn't seem to happen when a similar approach is implemented in Lua.
SDK/Simulator: 3.1.1
Platform: macOS Tahoe 26.5.2 (and 26.6 will test on )26.6 shortly and edit this to confirm it's still occurring!
- Start game (written in C) in Simulator
- Game establishes TCP connection
- While connection is open, press 'restart' in Simulator
- (Simulator crashes)
- Comment out the following line:
tcp->setConnectionClosedCallback(tcpconn, TCPConnectionClosedCallback); - Rebuild the game, follow the same steps
- The Simulator no longer crashes
Minimal(ish
) reproduction:
I wrote this while trying to eliminate my game as the source of the issue (started with the C networking example as a base to ensure I wasn't doing anything wild). The Ruby server is super simple but most TCP servers ought to sub in as long as it doesn't immediately close the connection.
main.c
#include <stdio.h>
#include <stdlib.h>
#include "pd_api.h"
#define SERVER "localhost"
#define WAIT_PROMPT_1 "(waiting for successful connection...)"
#define WAIT_PROMPT_2 "(please do not restart Simulator yet)"
#define GO_PROMPT "press restart game to reproduce crash!"
PlaydateAPI *pd;
static int update(void *userdata);
const struct playdate_tcp *tcp;
#ifdef _WINDLL
__declspec(dllexport)
#endif
int eventHandler(PlaydateAPI *playdate, PDSystemEvent event, uint32_t arg) {
(void)arg; // arg is currently only used for event = kEventKeyPressed
if (event == kEventInit) {
pd = playdate;
pd->system->setUpdateCallback(update, pd);
tcp = pd->network->tcp;
}
return 0;
}
enum {
kRequestingTCPAccess,
kWaitingForTCPAccess,
kTCPConnect,
kTCPConnecting,
kTCPOpen,
kNoAccess,
kDone,
} state = kRequestingTCPAccess;
// --- TCP ---
TCPConnection *tcpconn;
void TCPAccessCallback(bool allowed, void *userdata) {
pd->system->logToConsole("TCPAccessCallback: %i", allowed);
state = allowed ? kTCPConnect : kNoAccess;
}
// Crashes Simulator (when restart game is clicked) even when
// function doesn't 'do' anything in body.
void TCPConnectionClosedCallback(TCPConnection *conn, PDNetErr err) {
// pd->system->logToConsole("TCP connection closed");
}
void tcpOpenCallback(TCPConnection *conn, PDNetErr err, void *ud) {
if (err == NET_OK) {
state = kTCPOpen;
} else {
pd->system->logToConsole("error opening TCP connection: %i", err);
}
}
static void tcpConnect(void) {
tcpconn = tcp->newConnection(SERVER, 65431, false);
// This callback crashes the Simulator, when "restart" (game) is
// clicked while the TCP connection is open. To prevent the crash,
// comment out this line:
tcp->setConnectionClosedCallback(tcpconn, TCPConnectionClosedCallback);
PDNetErr err = tcp->open(tcpconn, tcpOpenCallback, NULL);
state = kTCPConnecting;
}
static int update(void *userdata) {
int promptToReproduce = 0;
if (state == kRequestingTCPAccess) {
state = kWaitingForTCPAccess;
enum accessReply access = tcp->requestAccess("localhost", 65431, false, "for testing", TCPAccessCallback, NULL);
if (access == kAccessDeny) {
pd->system->logToConsole("tcp access denied");
state = kNoAccess;
} else if (access == kAccessAllow) {
state = kTCPConnect;
}
} else if (state == kWaitingForTCPAccess) {
// no-op
} else if (state == kTCPConnect) {
tcpConnect();
} else if (state == kTCPConnecting) {
// no-op
} else if (state == kTCPOpen) {
promptToReproduce = 1;
} else if (state == kNoAccess) {
pd->system->logToConsole("Internet access denied!");
state = kDone;
}
pd->graphics->clear(kColorWhite);
if (promptToReproduce == 1) {
pd->graphics->drawText(GO_PROMPT, strlen(GO_PROMPT), kASCIIEncoding, 5, 55);
} else {
pd->graphics->drawText(WAIT_PROMPT_1, strlen(WAIT_PROMPT_1), kASCIIEncoding, 5, 5);
pd->graphics->drawText(WAIT_PROMPT_2, strlen(WAIT_PROMPT_2), kASCIIEncoding, 5, 30);
}
return 1;
}
server.rb
#!/usr/bin/env ruby
require "socket"
TCP_PORT = 65431
server = TCPServer.new(TCP_PORT)
threads = []
loop do
new_client = server.accept
threads << Thread.start(new_client) do |client|
puts "new client connected"
loop do
# read:
received_data = client.gets
if received_data.nil?
puts "client disconnected"
break
end
puts "received: #{received_data}"
sleep(1.0)
# write:
response = "FROMSERV\n"
client.puts response
puts "sent: #{response}"
end
client.close
puts "connection closed"
end
end
Thanks for looking, hopefully this all made sense!!