Merge pull request #22846 from hrydgard/adhoc-5-ctl-timing

Ad hoc 5/9: Accurate PSP timing for adhocctl events
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-10-30 08:48:07 +01:00
commit f2f0f7d374
2 files changed
+71 -19

No files matched your search

+60 -17
View File
@@ -96,6 +96,40 @@ std::map<int, AdhocctlHandler> adhocctlHandlers;
int adhocctlNotifyEvent = -1;
int adhocctlStateEvent = -1;
int adhocctlPollEvent = -1;
// When the game made its last request to the Adhoc Server, in emulated time. Its event isn't delivered
// earlier than on a PSP (see PspEventDelayUs).
static u64 adhocctlRequestTimeUs = 0;
// Whether the server has us in a group. The state the game sees only changes when the event is
// delivered, so a Disconnect can come while a Create or Connect is still on its way.
static bool adhocctlInServerGroup = false;
// How long after the request a PSP delivers an event (pspautotests net/adhoc/timing and
// net/adhoc/pair/timing). The state changes along with it. Over the internet the server's answer can
// come later, and then the event waits for it.
static int PspEventDelayUs(u32 flags) {
switch (flags) {
case ADHOCCTL_EVENT_CONNECT:
switch (adhocConnectionType) {
case ADHOC_CREATE: return 1110000;
case ADHOC_JOIN: return 530000;
// Connect looks for the group first: about 3.5 s to join one, 4 s to create it.
default: return getActivePeerCount() > 0 ? 3500000 : 4000000;
}
case ADHOCCTL_EVENT_SCAN: return 1015000;
case ADHOCCTL_EVENT_DISCONNECT: return 105000;
default: return 0;
}
}
// A PSP returns from Create, Connect, Join and Scan within about 30 µs (pspautotests net/adhoc/timing).
// The request goes out to the server then, and the call returns once it's sent.
static const int ADHOCCTL_REQUEST_US = 30;
static int RemainingEventDelayUs(u32 flags) {
s64 left = (s64)(adhocctlRequestTimeUs + PspEventDelayUs(flags)) - (s64)CoreTiming::GetGlobalTimeUs();
return left > 0 ? (int)left : 0;
}
int adhocSocketNotifyEvent = -1;
std::map<int, AdhocctlRequest> adhocctlRequests;
std::map<u64, AdhocSocketRequest> adhocSocketRequests;
@@ -571,6 +605,9 @@ static void __AdhocctlNotify(u64 userdata, int cyclesLate) {
switch(req.opcode) {
case OPCODE_CONNECT:{
call_status = adhocctl_connect(adhocctlHandle, (char *)req.group.data);
if (call_status == ADHOCCTL_CALL_SUCCESS) {
adhocctlInServerGroup = true;
}
break;
}
case OPCODE_SCAN:{
@@ -682,6 +719,7 @@ int WaitBlockingAdhocctlSocket(AdhocctlRequest request, int usec, const char* re
u64 param = ((u64)__KernelGetCurThread()) << 32 | uid;
adhocctlStartTime = (u64)(time_now_d() * 1000000.0);
adhocctlRequestTimeUs = CoreTiming::GetGlobalTimeUs();
adhocctlRequests[uid] = request;
CoreTiming::ScheduleEvent(usToCycles(usec), adhocctlNotifyEvent, param);
__KernelWaitCurThread(WAITTYPE_NET, uid, request.opcode, 0, false, reason);
@@ -1910,6 +1948,7 @@ static void StartFriendFinder() {
static void StopFriendFinder() {
friendFinderRunning = false;
friendFinderStop();
adhocctlInServerGroup = false;
int32_t peercount = 0;
freeFriendsRecursive(friends, &peercount);
@@ -1920,7 +1959,7 @@ static void StopFriendFinder() {
}
void __NetAdhocDoState(PointerWrap &p) {
auto s = p.Section("sceNetAdhoc", 1, 11);
auto s = p.Section("sceNetAdhoc", 1, 12);
if (!s)
return;
@@ -2019,8 +2058,15 @@ void __NetAdhocDoState(PointerWrap &p) {
adhocctlPollEvent = -1;
}
CoreTiming::RestoreRegisterEvent(adhocctlPollEvent, "__AdhocctlPoll", __AdhocctlPoll);
if (s >= 12) {
Do(p, adhocctlRequestTimeUs);
} else {
adhocctlRequestTimeUs = 0;
}
if (p.mode == p.MODE_READ) {
// The login after a load starts outside any group.
adhocctlInServerGroup = false;
// Discard leftover events
{
std::lock_guard<std::recursive_mutex> adhocGuard(adhocEvtMtx);
@@ -3596,7 +3642,7 @@ int sceNetAdhocctlScan(MIPSState *mips) {
if (friendFinderRunning) {
AdhocctlRequest req = { OPCODE_SCAN, {0} };
return hleLogDebugOrError(Log::sceNet, WaitBlockingAdhocctlSocket(req, us, "adhocctl scan"));
return hleLogDebugOrError(Log::sceNet, WaitBlockingAdhocctlSocket(req, ADHOCCTL_REQUEST_US, "adhocctl scan"));
}
else {
adhocctlState = ADHOCCTL_STATE_DISCONNECTED;
@@ -3762,15 +3808,15 @@ u32 NetAdhocctl_Disconnect() {
// Library initialized
if (netAdhocctlInited) {
int iResult, error;
// We might need to have at least 16ms (1 frame?) delay before the game calls the next Adhocctl syscall for Tekken 6 not to stuck when exiting Lobby
hleEatMicro(16667);
if (isAdhocctlBusy && CoreTiming::IsScheduled(adhocctlNotifyEvent)) {
return SCE_NET_ADHOCCTL_ERROR_BUSY;
}
// Returns at once on a PSP, and DISCONNECT follows after about 105 ms (pspautotests net/adhoc/timing).
adhocctlRequestTimeUs = CoreTiming::GetGlobalTimeUs();
// Connected State (Adhoc Mode). Attempting to leave a group while not in a group will be kicked out by Adhoc Server (ie. some games tries to disconnect more than once within a short time)
if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED) {
if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED || adhocctlInServerGroup) {
adhocctlInServerGroup = false;
isAdhocctlBusy = true;
// Clear Network Name
@@ -3865,7 +3911,7 @@ static u32 sceNetAdhocctlDelHandler(u32 handlerID) {
int NetAdhocctl_Term() {
if (netAdhocctlInited) {
if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED) {
if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED || adhocctlInServerGroup) {
// Note: This might block current thread if the first attempt to send OPCODE_DISCONNECT to AdhocServer failed with EAGAIN error
if (netAdhocGameModeEntered)
NetAdhocctl_ExitGameMode();
@@ -3876,6 +3922,7 @@ int NetAdhocctl_Term() {
// Stop the adhocctl client
friendFinderRunning = false;
friendFinderStop();
adhocctlInServerGroup = false;
// TODO: May need to block current thread to make sure all Adhocctl callbacks have been fully executed before terminating Adhoc PSPThread (ie. threadAdhocID).
@@ -4071,10 +4118,9 @@ int NetAdhocctl_Create(const char *groupName) {
//setConnectionStatus(1);
// Wait for Status to be connected to prevent Ford Street Racing from Failed to create game session
int us = adhocDefaultDelay;
if (friendFinderRunning) {
AdhocctlRequest req = { OPCODE_CONNECT, parameter.group_name };
return WaitBlockingAdhocctlSocket(req, us, "adhocctl connect");
return WaitBlockingAdhocctlSocket(req, ADHOCCTL_REQUEST_US, "adhocctl connect");
}
//Faking success, to prevent Full Auto 2 from freezing while Initializing Network
else {
@@ -4091,7 +4137,7 @@ int NetAdhocctl_Create(const char *groupName) {
}
}
hleEatMicro(us);
hleEatMicro(adhocDefaultDelay);
// Return Success
// FIXME: When tested using JPCSP + official prx files it seems sceNetAdhocctlCreate switching to a different thread for at least 100ms after returning success and before executing the next line.
return 0;
@@ -6304,18 +6350,15 @@ void __NetTriggerCallbacks()
switch (flags) {
case ADHOCCTL_EVENT_CONNECT:
newState = ADHOCCTL_STATE_CONNECTED;
if (adhocConnectionType == ADHOC_CREATE)
delayus = adhocEventDelay; // May affects Dissidia 012 and GTA VCS
else if (adhocConnectionType == ADHOC_CONNECT)
delayus = adhocEventDelay / 2;
delayus = RemainingEventDelayUs(flags);
break;
case ADHOCCTL_EVENT_SCAN: // notified only when scan completed?
case ADHOCCTL_EVENT_SCAN:
newState = ADHOCCTL_STATE_DISCONNECTED;
//delayus = adhocEventDelay / 2;
delayus = RemainingEventDelayUs(flags);
break;
case ADHOCCTL_EVENT_DISCONNECT:
newState = ADHOCCTL_STATE_DISCONNECTED;
delayus = adhocDefaultDelay; // Tekken 5 expects AdhocctlDisconnect to be done within ~17ms (a frame?)
delayus = RemainingEventDelayUs(flags);
break;
case ADHOCCTL_EVENT_GAME:
{
+11 -2
View File
@@ -61,17 +61,21 @@ AdhocServerRelayMode = 1
def run_test(headless, test, extra, relay_ini):
roles = roles_of(test)
memstick = os.path.join(os.path.dirname(headless), "memstick")
# A test that hasn't been recorded on PSPs yet has no expected files; then just show what each instance prints.
compare = all(os.path.exists(os.path.join(TESTS_DIR, "%s_%s.expected" % (test, role))) for role in roles)
procs = []
for i, role in enumerate(roles):
cmd = [headless, "--root", os.path.join(ROOT, "pspautotests") + "/", "--compare", "--timeout-wall=%d" % TIMEOUT,
cmd = [headless, "--root", os.path.join(ROOT, "pspautotests") + "/", "--timeout-wall=%d" % TIMEOUT,
"--graphics=software", "--memstick=" + memstick, "--realtime", "--instance=%d" % (i + 1)]
if compare:
cmd.append("--compare")
if relay_ini:
cmd.append("--appendconfig=" + relay_ini)
if i == 0:
cmd.append("--adhoc-server")
cmd += extra
cmd.append(os.path.join(TESTS_DIR, "%s_%s.prx" % (test, role)))
procs.append((role, subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)))
procs.append((role, subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT if compare else subprocess.DEVNULL)))
if i == 0:
# Let the host's built-in server come up before the clients log in to it.
time.sleep(1.0)
@@ -79,6 +83,11 @@ def run_test(headless, test, extra, relay_ini):
passed = True
for role, proc in procs:
out, _ = proc.communicate(timeout=TIMEOUT + 30)
if not compare:
print("OUTPUT %s_%s (no expected file):" % (test, role))
sys.stdout.write(out.decode("utf-8", "replace"))
passed = False
continue
ok = proc.returncode == 0
passed = passed and ok
print("%s %s_%s" % ("PASS" if ok else "FAIL", test, role))