diff --git a/src/CMakeSources.cmake b/src/CMakeSources.cmake index 20685715..c91541ef 100644 --- a/src/CMakeSources.cmake +++ b/src/CMakeSources.cmake @@ -24,9 +24,22 @@ set(GGPO_LIB_SRC_NOFILTER ) if(UNIX) + if(APPLE) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdeclspec") + endif(APPLE) + set(GGPO_LIB_SRC_NOFILTER ${GGPO_LIB_SRC_NOFILTER} - "lib/ggpo/platform_linux.cpp" + "lib/ggpo/platform_unix.h" + "lib/ggpo/platform_unix.cpp" + "lib/ggpo/pevents.h" + "lib/ggpo/pevents.cpp" + ) +else(WIN32) + set(GGPO_LIB_SRC_NOFILTER + ${GGPO_LIB_SRC_NOFILTER} + "lib/ggpo/platform_windows.h" + "lib/ggpo/platform_windows.cpp" ) endif() @@ -71,4 +84,4 @@ set(GGPO_LIB_SRC ${GGPO_LIB_INC_BACKENDS} ${GGPO_LIB_SRC_BACKENDS} ${GGPO_PUBLIC_INC} -) \ No newline at end of file +) diff --git a/src/apps/vectorwar/gdi_renderer.cpp b/src/apps/vectorwar/gdi_renderer.cpp index 4cb62af2..d087f808 100644 --- a/src/apps/vectorwar/gdi_renderer.cpp +++ b/src/apps/vectorwar/gdi_renderer.cpp @@ -168,7 +168,7 @@ GDIRenderer::DrawConnectState(HDC hdc, Ship &ship, PlayerConnectionInfo &info, C case Disconnecting: sprintf(status, "Waiting for player..."); - progress = (timeGetTime() - info.disconnect_start) * 100 / info.disconnect_timeout; + progress = (GetCurrentTimeMS() - info.disconnect_start) * 100 / info.disconnect_timeout; break; } diff --git a/src/apps/vectorwar/ggpo_perfmon.cpp b/src/apps/vectorwar/ggpo_perfmon.cpp index 98b41430..bc596ce8 100644 --- a/src/apps/vectorwar/ggpo_perfmon.cpp +++ b/src/apps/vectorwar/ggpo_perfmon.cpp @@ -1,8 +1,10 @@ #include #include + #include "resource.h" #include "ggponet.h" #include "ggpo_perfmon.h" +#include "vectorwar.h" #define MAX_GRAPH_SIZE 4096 #define MAX_FAIRNESS 20 @@ -114,7 +116,7 @@ ggpo_perfmon_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_INITDIALOG: { char pid[64]; - sprintf(pid, "%d", GetCurrentProcessId()); + sprintf(pid, "%d", GetProcessID()); SetWindowTextA(GetDlgItem(hwndDlg, IDC_PID), pid); return TRUE; } @@ -210,7 +212,7 @@ ggpoutil_perfmon_update(GGPOSession *ggpo, GGPOPlayerHandle players[], int num_p } } - int now = timeGetTime(); + uint32_t now = GetCurrentTimeMS(); if (_dialog) { InvalidateRect(GetDlgItem(_dialog, IDC_FAIRNESS_GRAPH), NULL, FALSE); InvalidateRect(GetDlgItem(_dialog, IDC_NETWORK_GRAPH), NULL, FALSE); diff --git a/src/apps/vectorwar/main.cpp b/src/apps/vectorwar/main.cpp index 12d56bfd..3c8bb079 100644 --- a/src/apps/vectorwar/main.cpp +++ b/src/apps/vectorwar/main.cpp @@ -48,7 +48,7 @@ CreateMainWindow(HINSTANCE hInstance) int width = 640, height = 480; WCHAR titlebuf[128]; - wsprintf(titlebuf, L"(pid:%d) ggpo sdk sample: vector war", GetCurrentProcessId()); + wsprintf(titlebuf, L"(pid:%d) ggpo sdk sample: vector war", GetProcessID()); wndclass.cbSize = sizeof(wndclass); wndclass.lpfnWndProc = MainWindowProc; wndclass.lpszClassName = L"vwwnd"; @@ -69,9 +69,9 @@ void RunMainLoop(HWND hwnd) { MSG msg = { 0 }; - int start, next, now; + uint32_t start, next, now; - start = next = now = timeGetTime(); + start = next = now = GetCurrentTimeMS(); while(1) { while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); @@ -80,7 +80,7 @@ RunMainLoop(HWND hwnd) return; } } - now = timeGetTime(); + now = GetCurrentTimeMS(); VectorWar_Idle(max(0, next - now - 1)); if (now >= next) { VectorWar_RunFrame(hwnd); @@ -184,4 +184,4 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, WSACleanup(); DestroyWindow(hwnd); return 0; -} \ No newline at end of file +} diff --git a/src/apps/vectorwar/vectorwar.cpp b/src/apps/vectorwar/vectorwar.cpp index 547060b4..b46bbd03 100644 --- a/src/apps/vectorwar/vectorwar.cpp +++ b/src/apps/vectorwar/vectorwar.cpp @@ -3,6 +3,10 @@ #include #include #include + +#include +#include + #include "gdi_renderer.h" #include "vectorwar.h" #include "ggpo_perfmon.h" @@ -82,7 +86,7 @@ vw_on_event_callback(GGPOEvent *info) break; case GGPO_EVENTCODE_CONNECTION_INTERRUPTED: ngs.SetDisconnectTimeout(info->u.connection_interrupted.player, - timeGetTime(), + GetCurrentTimeMS(), info->u.connection_interrupted.disconnect_timeout); break; case GGPO_EVENTCODE_CONNECTION_RESUMED: @@ -92,7 +96,7 @@ vw_on_event_callback(GGPOEvent *info) ngs.SetConnectState(info->u.disconnected.player, Disconnected); break; case GGPO_EVENTCODE_TIMESYNC: - Sleep(1000 * info->u.timesync.frames_ahead / 60); + std::this_thread::sleep_for(std::chrono::milliseconds(1000 * info->u.timesync.frames_ahead / 60)); break; } return true; @@ -449,3 +453,6 @@ VectorWar_Exit() } delete renderer; } + +DWORD GetProcessID() { return GetCurrentProcessId(); } +uint32_t GetCurrentTimeMS() { return timeGetTime(); } \ No newline at end of file diff --git a/src/apps/vectorwar/vectorwar.h b/src/apps/vectorwar/vectorwar.h index 60533262..612eac47 100644 --- a/src/apps/vectorwar/vectorwar.h +++ b/src/apps/vectorwar/vectorwar.h @@ -1,6 +1,8 @@ #ifndef _VECTORWAR_H #define _VECTORWAR_H +#include + #include "ggponet.h" /* @@ -28,6 +30,10 @@ void VectorWar_Idle(int time); void VectorWar_DisconnectPlayer(int player); void VectorWar_Exit(); +// Helper functions +DWORD GetProcessID(); +uint32_t GetCurrentTimeMS(); + #define ARRAY_SIZE(n) (sizeof(n) / sizeof(n[0])) #define FRAME_DELAY 2 diff --git a/src/include/ggponet.h b/src/include/ggponet.h index f24d509d..979041dd 100644 --- a/src/include/ggponet.h +++ b/src/include/ggponet.h @@ -8,6 +8,21 @@ #ifndef _GGPONET_H_ #define _GGPONET_H_ +#if defined(_MSC_VER) + // Microsoft + #define EXPORT __declspec(dllexport) +#elif defined(APPLE) || defined(__GNUC__) + // GCC + #define EXPORT __attribute__((visibility("default"))) +#else + // do nothing and hope for the best? + #define EXPORT +#endif + +#ifdef __GNUC__ +#define __cdecl __attribute__((cdecl)) +#endif + #ifdef __cplusplus extern "C" { #endif @@ -343,6 +358,14 @@ GGPO_API GGPOErrorCode __cdecl ggpo_add_player(GGPOSession *session, GGPOPlayerHandle *handle); +/* + * ggpo_in_rollback -- + * + * Returns true if ggpo is currently in rollback mode. + */ +GGPO_API bool __cdecl ggpo_in_rollback(GGPOSession *session); + + /* * ggpo_start_synctest -- * diff --git a/src/lib/ggpo/backends/backend.h b/src/lib/ggpo/backends/backend.h index fedbe5e6..69aa9b86 100644 --- a/src/lib/ggpo/backends/backend.h +++ b/src/lib/ggpo/backends/backend.h @@ -13,6 +13,7 @@ struct GGPOSession { virtual ~GGPOSession() { } + virtual bool InRollback() { return false; } virtual GGPOErrorCode DoPoll(int timeout) { return GGPO_OK; } virtual GGPOErrorCode AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle) = 0; virtual GGPOErrorCode AddLocalInput(GGPOPlayerHandle player, void *values, int size) = 0; diff --git a/src/lib/ggpo/backends/p2p.cpp b/src/lib/ggpo/backends/p2p.cpp index bd0279e6..3180e577 100644 --- a/src/lib/ggpo/backends/p2p.cpp +++ b/src/lib/ggpo/backends/p2p.cpp @@ -161,7 +161,7 @@ Peer2PeerBackend::DoPoll(int timeout) } // XXX: this is obviously a farce... if (timeout) { - Sleep(1); + Platform::SleepMS(1); } } } @@ -239,6 +239,11 @@ int Peer2PeerBackend::PollNPlayers(int current_frame) return total_min_confirmed; } +bool +Peer2PeerBackend::InRollback() +{ + return _sync.InRollback(); +} GGPOErrorCode Peer2PeerBackend::AddPlayer(GGPOPlayer *player, diff --git a/src/lib/ggpo/backends/p2p.h b/src/lib/ggpo/backends/p2p.h index 7ee2fae9..7b635782 100644 --- a/src/lib/ggpo/backends/p2p.h +++ b/src/lib/ggpo/backends/p2p.h @@ -22,6 +22,7 @@ class Peer2PeerBackend : public IQuarkBackend, IPollSink, Udp::Callbacks { public: + virtual bool InRollback(); virtual GGPOErrorCode DoPoll(int timeout); virtual GGPOErrorCode AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle); virtual GGPOErrorCode AddLocalInput(GGPOPlayerHandle player, void *values, int size); diff --git a/src/lib/ggpo/backends/synctest.cpp b/src/lib/ggpo/backends/synctest.cpp index 3f981a4d..67a081a2 100644 --- a/src/lib/ggpo/backends/synctest.cpp +++ b/src/lib/ggpo/backends/synctest.cpp @@ -167,7 +167,6 @@ SyncTestBackend::RaiseSyncError(const char *fmt, ...) va_end(args); puts(buf); - OutputDebugStringA(buf); EndLog(); DebugBreak(); } @@ -187,7 +186,7 @@ SyncTestBackend::BeginLog(int saving) EndLog(); char filename[MAX_PATH]; - CreateDirectoryA("synclogs", NULL); + Platform::CreateDirectory("synclogs", NULL); sprintf(filename, "synclogs\\%s-%04d-%s.log", saving ? "state" : "log", _sync.GetFrameCount(), diff --git a/src/lib/ggpo/log.cpp b/src/lib/ggpo/log.cpp index 7b57fa86..365dea35 100644 --- a/src/lib/ggpo/log.cpp +++ b/src/lib/ggpo/log.cpp @@ -20,22 +20,26 @@ static char logbuf[4 * 1024 * 1024]; void Log(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - Logv(fmt, args); - va_end(args); + // va_list args; + // va_start(args, fmt); + // printf(fmt, args); + // va_end(args); + // fflush(stdout); } void Logv(const char *fmt, va_list args) { - if (!getenv("ggpo.log") || getenv("ggpo.log.ignore")) { - return; - } - if (!logfile) { - sprintf(logbuf, "log-%d.log", Platform::GetProcessID()); - logfile = fopen(logbuf, "w"); - } - Logv(logfile, fmt, args); + // if (!getenv("ggpo.log") || getenv("ggpo.log.ignore")) { + // return; + // } + // if (!logfile) { + // sprintf(logbuf, "log-%d.log", Platform::GetProcessID()); + // logfile = fopen(logbuf, "w"); + // } + // Logv(logfile, fmt, args); + + // printf(fmt, args); + // fflush(stdout); } void Logv(FILE *fp, const char *fmt, va_list args) diff --git a/src/lib/ggpo/main.cpp b/src/lib/ggpo/main.cpp index 70576d20..cd062f83 100644 --- a/src/lib/ggpo/main.cpp +++ b/src/lib/ggpo/main.cpp @@ -11,13 +11,6 @@ #include "backends/spectator.h" #include "ggponet.h" -BOOL WINAPI -DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) -{ - srand(Platform::GetCurrentTimeMS() + Platform::GetProcessID()); - return TRUE; -} - void ggpo_log(GGPOSession *ggpo, const char *fmt, ...) { @@ -62,7 +55,14 @@ ggpo_add_player(GGPOSession *ggpo, return ggpo->AddPlayer(player, handle); } - +bool +ggpo_in_rollback(GGPOSession *ggpo) +{ + if (!ggpo) { + return false; + } + return ggpo->InRollback(); +} GGPOErrorCode ggpo_start_synctest(GGPOSession **ggpo, diff --git a/src/lib/ggpo/network/udp.cpp b/src/lib/ggpo/network/udp.cpp index 47fd76ec..ba9f6f3d 100644 --- a/src/lib/ggpo/network/udp.cpp +++ b/src/lib/ggpo/network/udp.cpp @@ -15,10 +15,13 @@ CreateSocket(int bind_port, int retries) sockaddr_in sin; int port; int optval = 1; + struct linger loptval; + loptval.l_onoff = 0; + loptval.l_linger = 0; s = socket(AF_INET, SOCK_DGRAM, 0); setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof optval); - setsockopt(s, SOL_SOCKET, SO_DONTLINGER, (const char *)&optval, sizeof optval); + setsockopt(s, SOL_SOCKET, SO_LINGER, (const char *)&loptval, sizeof loptval); // non-blocking... u_long iMode = 1; @@ -70,8 +73,12 @@ Udp::SendTo(char *buffer, int len, int flags, struct sockaddr *dst, int destlen) int res = sendto(_socket, buffer, len, flags, dst, destlen); if (res == SOCKET_ERROR) { +#ifdef _WIN32 DWORD err = WSAGetLastError(); DWORD e2 = WSAENOTSOCK; +#else + int err = 1; +#endif Log("unknown error in sendto (erro: %d wsaerr: %d).\n", res, err); ASSERT(FALSE && "Unknown error in sendto"); } @@ -83,7 +90,7 @@ Udp::OnLoopPoll(void *cookie) { uint8 recv_buf[MAX_UDP_PACKET_SIZE]; sockaddr_in recv_addr; - int recv_addr_len; + socklen_t recv_addr_len; for (;;) { recv_addr_len = sizeof(recv_addr); @@ -92,7 +99,11 @@ Udp::OnLoopPoll(void *cookie) // TODO: handle len == 0... indicates a disconnect. if (len == -1) { +#ifdef _WIN32 int error = WSAGetLastError(); +#else + int error = 1; +#endif if (error != WSAEWOULDBLOCK) { Log("recvfrom WSAGetLastError returned %d (%x).\n", error, error); } diff --git a/src/lib/ggpo/network/udp_msg.h b/src/lib/ggpo/network/udp_msg.h index d558cd1b..2ff10e69 100644 --- a/src/lib/ggpo/network/udp_msg.h +++ b/src/lib/ggpo/network/udp_msg.h @@ -104,4 +104,4 @@ struct UdpMsg #pragma pack(pop) -#endif +#endif diff --git a/src/lib/ggpo/network/udp_proto.cpp b/src/lib/ggpo/network/udp_proto.cpp index 20412834..bcd4da58 100644 --- a/src/lib/ggpo/network/udp_proto.cpp +++ b/src/lib/ggpo/network/udp_proto.cpp @@ -296,7 +296,7 @@ UdpProtocol::HandlesMsg(sockaddr_in &from, if (!_udp) { return false; } - return _peer_addr.sin_addr.S_un.S_addr == from.sin_addr.S_un.S_addr && + return _peer_addr.sin_addr.s_addr == from.sin_addr.s_addr && _peer_addr.sin_port == from.sin_port; } diff --git a/src/lib/ggpo/network/udp_proto.h b/src/lib/ggpo/network/udp_proto.h index 09de3e3f..545439cd 100644 --- a/src/lib/ggpo/network/udp_proto.h +++ b/src/lib/ggpo/network/udp_proto.h @@ -53,7 +53,7 @@ class UdpProtocol : public IPollSink } network_interrupted; } u; - UdpProtocol::Event(Type t = Unknown) : type(t) { } + Event(Type t = Unknown) : type(t) { } }; public: diff --git a/src/lib/ggpo/pevents.cpp b/src/lib/ggpo/pevents.cpp new file mode 100644 index 00000000..4691fd74 --- /dev/null +++ b/src/lib/ggpo/pevents.cpp @@ -0,0 +1,603 @@ +/* + * WIN32 Events for POSIX + * Author: Mahmoud Al-Qudsi + * Copyright (C) 2011 - 2019 by NeoSmart Technologies + * This code is released under the terms of the MIT License + */ + +#ifndef _WIN32 + +#include "pevents.h" +#include +#include +#include +#include +#ifdef WFMO +#include +#include +#endif + +namespace neosmart { +#ifdef WFMO + // Each call to WaitForMultipleObjects initializes a neosmart_wfmo_t object which tracks + // the progress of the caller's multi-object wait and dispatches responses accordingly. + // One neosmart_wfmo_t struct is shared for all events in a single WFMO call + struct neosmart_wfmo_t_ { + pthread_mutex_t Mutex; + pthread_cond_t CVariable; + int RefCount; + union { + int FiredEvent; // WFSO + int EventsLeft; // WFMO + } Status; + bool WaitAll; + bool StillWaiting; + + void Destroy() { + pthread_mutex_destroy(&Mutex); + pthread_cond_destroy(&CVariable); + } + }; + typedef neosmart_wfmo_t_ *neosmart_wfmo_t; + + // A neosmart_wfmo_info_t object is registered with each event waited on in a WFMO + // This reference to neosmart_wfmo_t_ is how the event knows whom to notify when triggered + struct neosmart_wfmo_info_t_ { + neosmart_wfmo_t Waiter; + int WaitIndex; + }; + typedef neosmart_wfmo_info_t_ *neosmart_wfmo_info_t; +#endif // WFMO + + // The basic event structure, passed to the caller as an opaque pointer when creating events + struct neosmart_event_t_ { + pthread_cond_t CVariable; + pthread_mutex_t Mutex; + bool AutoReset; + bool State; +#ifdef WFMO + std::deque RegisteredWaits; +#endif + }; + +#ifdef WFMO + bool RemoveExpiredWaitHelper(neosmart_wfmo_info_t_ wait) { + int result = pthread_mutex_trylock(&wait.Waiter->Mutex); + + if (result == EBUSY) { + return false; + } + + assert(result == 0); + + if (wait.Waiter->StillWaiting == false) { + --wait.Waiter->RefCount; + assert(wait.Waiter->RefCount >= 0); + bool destroy = wait.Waiter->RefCount == 0; + result = pthread_mutex_unlock(&wait.Waiter->Mutex); + assert(result == 0); + if (destroy) { + wait.Waiter->Destroy(); + delete wait.Waiter; + } + + return true; + } + + result = pthread_mutex_unlock(&wait.Waiter->Mutex); + assert(result == 0); + + return false; + } +#endif // WFMO + + neosmart_event_t CreateEvent(bool manualReset, bool initialState) { + neosmart_event_t event = new neosmart_event_t_; + + int result = pthread_cond_init(&event->CVariable, 0); + assert(result == 0); + + result = pthread_mutex_init(&event->Mutex, 0); + assert(result == 0); + + event->State = false; + event->AutoReset = !manualReset; + + if (initialState) { + result = SetEvent(event); + assert(result == 0); + } + + return event; + } + + int UnlockedWaitForEvent(neosmart_event_t event, uint64_t milliseconds) { + int result = 0; + if (!event->State) { + // Zero-timeout event state check optimization + if (milliseconds == 0) { + return WAIT_TIMEOUT; + } + + timespec ts; + if (milliseconds != (uint64_t)-1) { + timeval tv; + gettimeofday(&tv, NULL); + + uint64_t nanoseconds = ((uint64_t)tv.tv_sec) * 1000 * 1000 * 1000 + + milliseconds * 1000 * 1000 + ((uint64_t)tv.tv_usec) * 1000; + + ts.tv_sec = nanoseconds / 1000 / 1000 / 1000; + ts.tv_nsec = (nanoseconds - ((uint64_t)ts.tv_sec) * 1000 * 1000 * 1000); + } + + do { + // Regardless of whether it's an auto-reset or manual-reset event: + // wait to obtain the event, then lock anyone else out + if (milliseconds != (uint64_t)-1) { + result = pthread_cond_timedwait(&event->CVariable, &event->Mutex, &ts); + } else { + result = pthread_cond_wait(&event->CVariable, &event->Mutex); + } + } while (result == 0 && !event->State); + + if (result == 0 && event->AutoReset) { + // We've only accquired the event if the wait succeeded + event->State = false; + } + } else if (event->AutoReset) { + // It's an auto-reset event that's currently available; + // we need to stop anyone else from using it + result = 0; + event->State = false; + } + // Else we're trying to obtain a manual reset event with a signaled state; + // don't do anything + + return result; + } + + int WaitForEvent(neosmart_event_t event, uint64_t milliseconds) { + int tempResult; + if (milliseconds == 0) { + tempResult = pthread_mutex_trylock(&event->Mutex); + if (tempResult == EBUSY) { + return WAIT_TIMEOUT; + } + } else { + tempResult = pthread_mutex_lock(&event->Mutex); + } + + assert(tempResult == 0); + + int result = UnlockedWaitForEvent(event, milliseconds); + + tempResult = pthread_mutex_unlock(&event->Mutex); + assert(tempResult == 0); + + return result; + } + +#ifdef WFMO + int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, + uint64_t milliseconds) { + int unused; + return WaitForMultipleEvents(events, count, waitAll, milliseconds, unused); + } + + int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, + uint64_t milliseconds, int &waitIndex) { + neosmart_wfmo_t wfmo = new neosmart_wfmo_t_; + + int result = 0; + int tempResult = pthread_mutex_init(&wfmo->Mutex, 0); + assert(tempResult == 0); + + tempResult = pthread_cond_init(&wfmo->CVariable, 0); + assert(tempResult == 0); + + neosmart_wfmo_info_t_ waitInfo; + waitInfo.Waiter = wfmo; + waitInfo.WaitIndex = -1; + + wfmo->WaitAll = waitAll; + wfmo->StillWaiting = true; + wfmo->RefCount = 1; + + if (waitAll) { + wfmo->Status.EventsLeft = count; + } else { + wfmo->Status.FiredEvent = -1; + } + + tempResult = pthread_mutex_lock(&wfmo->Mutex); + assert(tempResult == 0); + + bool done = false; + waitIndex = -1; + + for (int i = 0; i < count; ++i) { + waitInfo.WaitIndex = i; + + // Must not release lock until RegisteredWait is potentially added + tempResult = pthread_mutex_lock(&events[i]->Mutex); + assert(tempResult == 0); + + // Before adding this wait to the list of registered waits, let's clean up old, expired + // waits while we have the event lock anyway + events[i]->RegisteredWaits.erase(std::remove_if(events[i]->RegisteredWaits.begin(), + events[i]->RegisteredWaits.end(), + RemoveExpiredWaitHelper), + events[i]->RegisteredWaits.end()); + + if (UnlockedWaitForEvent(events[i], 0) == 0) { + tempResult = pthread_mutex_unlock(&events[i]->Mutex); + assert(tempResult == 0); + + if (waitAll) { + --wfmo->Status.EventsLeft; + assert(wfmo->Status.EventsLeft >= 0); + } else { + wfmo->Status.FiredEvent = i; + waitIndex = i; + done = true; + break; + } + } else { + events[i]->RegisteredWaits.push_back(waitInfo); + ++wfmo->RefCount; + + tempResult = pthread_mutex_unlock(&events[i]->Mutex); + assert(tempResult == 0); + } + } + + // We set the `done` flag above in case of WaitAny and at least one event was set. + // But we need to check again here if we were doing a WaitAll or else we'll incorrectly + // return WAIT_TIMEOUT. + if (waitAll && wfmo->Status.EventsLeft == 0) { + done = true; + } + + timespec ts; + if (!done) { + if (milliseconds == 0) { + result = WAIT_TIMEOUT; + done = true; + } else if (milliseconds != (uint64_t)-1) { + timeval tv; + gettimeofday(&tv, NULL); + + uint64_t nanoseconds = ((uint64_t)tv.tv_sec) * 1000 * 1000 * 1000 + + milliseconds * 1000 * 1000 + ((uint64_t)tv.tv_usec) * 1000; + + ts.tv_sec = nanoseconds / 1000 / 1000 / 1000; + ts.tv_nsec = (nanoseconds - ((uint64_t)ts.tv_sec) * 1000 * 1000 * 1000); + } + } + + while (!done) { + // One (or more) of the events we're monitoring has been triggered? + + // If we're waiting for all events, assume we're done and check if there's an event that + // hasn't fired But if we're waiting for just one event, assume we're not done until we + // find a fired event + done = (waitAll && wfmo->Status.EventsLeft == 0) || + (!waitAll && wfmo->Status.FiredEvent != -1); + + if (!done) { + if (milliseconds != (uint64_t)-1) { + result = pthread_cond_timedwait(&wfmo->CVariable, &wfmo->Mutex, &ts); + } else { + result = pthread_cond_wait(&wfmo->CVariable, &wfmo->Mutex); + } + + if (result != 0) { + break; + } + } + } + + waitIndex = wfmo->Status.FiredEvent; + wfmo->StillWaiting = false; + + --wfmo->RefCount; + assert(wfmo->RefCount >= 0); + bool destroy = wfmo->RefCount == 0; + tempResult = pthread_mutex_unlock(&wfmo->Mutex); + assert(tempResult == 0); + if (destroy) { + wfmo->Destroy(); + delete wfmo; + } + + return result; + } +#endif // WFMO + + int DestroyEvent(neosmart_event_t event) { + int result = 0; + +#ifdef WFMO + result = pthread_mutex_lock(&event->Mutex); + assert(result == 0); + event->RegisteredWaits.erase(std::remove_if(event->RegisteredWaits.begin(), + event->RegisteredWaits.end(), + RemoveExpiredWaitHelper), + event->RegisteredWaits.end()); + result = pthread_mutex_unlock(&event->Mutex); + assert(result == 0); +#endif + + result = pthread_cond_destroy(&event->CVariable); + assert(result == 0); + + result = pthread_mutex_destroy(&event->Mutex); + assert(result == 0); + + delete event; + + return 0; + } + + int SetEvent(neosmart_event_t event) { + int result = pthread_mutex_lock(&event->Mutex); + assert(result == 0); + + event->State = true; + + // Depending on the event type, we either trigger everyone or only one + if (event->AutoReset) { +#ifdef WFMO + while (!event->RegisteredWaits.empty()) { + neosmart_wfmo_info_t i = &event->RegisteredWaits.front(); + + result = pthread_mutex_lock(&i->Waiter->Mutex); + assert(result == 0); + + --i->Waiter->RefCount; + assert(i->Waiter->RefCount >= 0); + if (!i->Waiter->StillWaiting) { + bool destroy = i->Waiter->RefCount == 0; + result = pthread_mutex_unlock(&i->Waiter->Mutex); + assert(result == 0); + if (destroy) { + i->Waiter->Destroy(); + delete i->Waiter; + } + event->RegisteredWaits.pop_front(); + continue; + } + + event->State = false; + + if (i->Waiter->WaitAll) { + --i->Waiter->Status.EventsLeft; + assert(i->Waiter->Status.EventsLeft >= 0); + // We technically should do i->Waiter->StillWaiting = Waiter->Status.EventsLeft + // != 0 but the only time it'll be equal to zero is if we're the last event, so + // no one else will be checking the StillWaiting flag. We're good to go without + // it. + } else { + i->Waiter->Status.FiredEvent = i->WaitIndex; + i->Waiter->StillWaiting = false; + } + + result = pthread_mutex_unlock(&i->Waiter->Mutex); + assert(result == 0); + + result = pthread_cond_signal(&i->Waiter->CVariable); + assert(result == 0); + + event->RegisteredWaits.pop_front(); + + result = pthread_mutex_unlock(&event->Mutex); + assert(result == 0); + + return 0; + } +#endif // WFMO + // event->State can be false if compiled with WFMO support + if (event->State) { + result = pthread_mutex_unlock(&event->Mutex); + assert(result == 0); + + result = pthread_cond_signal(&event->CVariable); + assert(result == 0); + + return 0; + } + } else { +#ifdef WFMO + for (size_t i = 0; i < event->RegisteredWaits.size(); ++i) { + neosmart_wfmo_info_t info = &event->RegisteredWaits[i]; + + result = pthread_mutex_lock(&info->Waiter->Mutex); + assert(result == 0); + + --info->Waiter->RefCount; + assert(info->Waiter->RefCount >= 0); + + if (!info->Waiter->StillWaiting) { + bool destroy = info->Waiter->RefCount == 0; + result = pthread_mutex_unlock(&info->Waiter->Mutex); + assert(result == 0); + if (destroy) { + info->Waiter->Destroy(); + delete info->Waiter; + } + continue; + } + + if (info->Waiter->WaitAll) { + --info->Waiter->Status.EventsLeft; + assert(info->Waiter->Status.EventsLeft >= 0); + // We technically should do i->Waiter->StillWaiting = Waiter->Status.EventsLeft + // != 0 but the only time it'll be equal to zero is if we're the last event, so + // no one else will be checking the StillWaiting flag. We're good to go without + // it. + } else { + info->Waiter->Status.FiredEvent = info->WaitIndex; + info->Waiter->StillWaiting = false; + } + + result = pthread_mutex_unlock(&info->Waiter->Mutex); + assert(result == 0); + + result = pthread_cond_signal(&info->Waiter->CVariable); + assert(result == 0); + } + event->RegisteredWaits.clear(); +#endif // WFMO + result = pthread_mutex_unlock(&event->Mutex); + assert(result == 0); + + result = pthread_cond_broadcast(&event->CVariable); + assert(result == 0); + } + + return 0; + } + + int ResetEvent(neosmart_event_t event) { + int result = pthread_mutex_lock(&event->Mutex); + assert(result == 0); + + event->State = false; + + result = pthread_mutex_unlock(&event->Mutex); + assert(result == 0); + + return 0; + } + +#ifdef PULSE + int PulseEvent(neosmart_event_t event) { + // This may look like it's a horribly inefficient kludge with the sole intention of reducing + // code duplication, but in reality this is what any PulseEvent() implementation must look + // like. The only overhead (function calls aside, which your compiler will likely optimize + // away, anyway), is if only WFMO auto-reset waits are active there will be overhead to + // unnecessarily obtain the event mutex for ResetEvent() after. In all other cases (being no + // pending waits, WFMO manual-reset waits, or any WFSO waits), the event mutex must first be + // released for the waiting thread to resume action prior to locking the mutex again in + // order to set the event state to unsignaled, or else the waiting threads will loop back + // into a wait (due to checks for spurious CVariable wakeups). + + int result = SetEvent(event); + assert(result == 0); + result = ResetEvent(event); + assert(result == 0); + + return 0; + } +#endif +} // namespace neosmart + +#else //_WIN32 + +#include +#include "pevents.h" + +namespace neosmart { + neosmart_event_t CreateEvent(bool manualReset, bool initialState) { + return static_cast(::CreateEvent(NULL, manualReset, initialState, NULL)); + } + + int DestroyEvent(neosmart_event_t event) { + HANDLE handle = static_cast(event); + return CloseHandle(handle) ? 0 : GetLastError(); + } + + int WaitForEvent(neosmart_event_t event, uint64_t milliseconds) { + uint32_t result = 0; + HANDLE handle = static_cast(event); + + // WaitForSingleObject(Ex) and WaitForMultipleObjects(Ex) only support 32-bit timeout + if (milliseconds == ((uint64_t)-1) || (milliseconds >> 32) == 0) { + result = WaitForSingleObject(handle, static_cast(milliseconds)); + } else { + // Cannot wait for 0xFFFFFFFF because that means infinity to WIN32 + uint32_t waitUnit = (INFINITE - 1); + uint64_t rounds = milliseconds / waitUnit; + uint32_t remainder = milliseconds % waitUnit; + + result = WaitForSingleObject(handle, remainder); + while (result == WAIT_TIMEOUT && rounds-- != 0) { + result = WaitForSingleObject(handle, waitUnit); + } + } + + if (result == WAIT_OBJECT_0 || result == WAIT_ABANDONED) { + // We must swallow WAIT_ABANDONED because there is no such equivalent on *nix + return 0; + } + + if (result == WAIT_TIMEOUT) { + return WAIT_TIMEOUT; + } + + return GetLastError(); + } + + int SetEvent(neosmart_event_t event) { + HANDLE handle = static_cast(event); + return ::SetEvent(handle) ? 0 : GetLastError(); + } + + int ResetEvent(neosmart_event_t event) { + HANDLE handle = static_cast(event); + return ::ResetEvent(handle) ? 0 : GetLastError(); + } + +#ifdef WFMO + int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, + uint64_t milliseconds) { + int index = 0; + return WaitForMultipleEvents(events, count, waitAll, milliseconds, index); + } + + int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, + uint64_t milliseconds, int &index) { + HANDLE *handles = reinterpret_cast(events); + uint32_t result = 0; + + // WaitForSingleObject(Ex) and WaitForMultipleObjects(Ex) only support 32-bit timeout + if (milliseconds == ((uint64_t)-1) || (milliseconds >> 32) == 0) { + result = WaitForMultipleObjects(count, handles, waitAll, + static_cast(milliseconds)); + } else { + // Cannot wait for 0xFFFFFFFF because that means infinity to WIN32 + uint32_t waitUnit = (INFINITE - 1); + uint64_t rounds = milliseconds / waitUnit; + uint32_t remainder = milliseconds % waitUnit; + + uint32_t result = WaitForMultipleObjects(count, handles, waitAll, remainder); + while (result == WAIT_TIMEOUT && rounds-- != 0) { + result = WaitForMultipleObjects(count, handles, waitAll, waitUnit); + } + } + + if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + count) { + index = result - WAIT_OBJECT_0; + return 0; + } else if (result >= WAIT_ABANDONED_0 && result < WAIT_ABANDONED_0 + count) { + index = result - WAIT_ABANDONED_0; + return 0; + } + + if (result == WAIT_FAILED) { + return GetLastError(); + } + return result; + } +#endif + +#ifdef PULSE + int PulseEvent(neosmart_event_t event) { + HANDLE handle = static_cast(event); + return ::PulseEvent(handle) ? 0 : GetLastError(); + } +#endif +} // namespace neosmart + +#endif //_WIN32 \ No newline at end of file diff --git a/src/lib/ggpo/pevents.h b/src/lib/ggpo/pevents.h new file mode 100644 index 00000000..85725471 --- /dev/null +++ b/src/lib/ggpo/pevents.h @@ -0,0 +1,42 @@ +/* + * WIN32 Events for POSIX + * Author: Mahmoud Al-Qudsi + * Copyright (C) 2011 - 2019 by NeoSmart Technologies + * This code is released under the terms of the MIT License + */ + +#pragma once + +#if defined(_WIN32) && !defined(CreateEvent) +#error Must include Windows.h prior to including pevents.h! +#endif +#ifndef WAIT_TIMEOUT +#include +#define WAIT_TIMEOUT ETIMEDOUT +#endif + +#include + +#define WFMO 1 + +namespace neosmart { + // Type declarations + struct neosmart_event_t_; + typedef neosmart_event_t_ *neosmart_event_t; + + // Function declarations + neosmart_event_t CreateEvent(bool manualReset = false, bool initialState = false); + int DestroyEvent(neosmart_event_t event); + int WaitForEvent(neosmart_event_t event, uint64_t milliseconds = -1); + int SetEvent(neosmart_event_t event); + int ResetEvent(neosmart_event_t event); +#ifdef WFMO + int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, + uint64_t milliseconds); + int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, + uint64_t milliseconds, int &index); +#endif +#ifdef PULSE + int PulseEvent(neosmart_event_t event); +#endif +} // namespace neosmart \ No newline at end of file diff --git a/src/lib/ggpo/platform_linux.cpp b/src/lib/ggpo/platform_linux.cpp deleted file mode 100644 index 84f88c76..00000000 --- a/src/lib/ggpo/platform_linux.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* ----------------------------------------------------------------------- - * GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC. - * - * Use of this software is governed by the MIT license that can be found - * in the LICENSE file. - */ - -#include "platform_linux.h" - -struct timespec start = { 0 } - -uint32 Platform::GetCurrentTimeMS() { - if (start.tv_sec == 0 && start.tv_nsec == 0) { - clock_gettime(CLOCK_MONOTONIC, &start); - return 0 - } - struct timespec current; - clock_gettime(CLOCK_MONOTONIC, ¤t); - - return ((current.tv_sec - start.tv_sec) * 1000) + - ((current.tv_nsec - start.tv_nsec ) / 1000000) + -} - diff --git a/src/lib/ggpo/platform_linux.h b/src/lib/ggpo/platform_linux.h deleted file mode 100644 index b36e0511..00000000 --- a/src/lib/ggpo/platform_linux.h +++ /dev/null @@ -1,27 +0,0 @@ -/* ----------------------------------------------------------------------- - * GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC. - * - * Use of this software is governed by the MIT license that can be found - * in the LICENSE file. - */ - -#ifndef _GGPO_LINUX_H_ -#define _GGPO_LINUX_H_ - -#include -#include -#include -#include -#include - -class Platform { -public: // types - typedef pid_t ProcessID; - -public: // functions - static ProcessID GetProcessID() { return getpid(); } - static void AssertFailed(char *msg) { } - static uint32 GetCurrentTimeMS(); -}; - -#endif diff --git a/src/lib/ggpo/platform_unix.cpp b/src/lib/ggpo/platform_unix.cpp new file mode 100644 index 00000000..84bb12bd --- /dev/null +++ b/src/lib/ggpo/platform_unix.cpp @@ -0,0 +1,33 @@ +/* ----------------------------------------------------------------------- + * GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC. + * + * Use of this software is governed by the MIT license that can be found + * in the LICENSE file. + */ + +#include "platform_unix.h" + +uint32_t Platform::GetCurrentTimeMS() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (ts.tv_sec * 1000) + (ts.tv_nsec / (1000*1000)); +} + +void Platform::SleepMS(int milliseconds) { +#if _POSIX_C_SOURCE >= 199309L + struct timespec ts; + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = (milliseconds % 1000) * 1000000; + nanosleep(&ts, NULL); +#else + usleep(milliseconds * 1000); +#endif +} + +void Platform::CreateDirectory(const char* pathname, const void* junk) { + mkdir(pathname, -1); +} + +static void __attribute__((constructor)) DllMain() { + srand(Platform::GetCurrentTimeMS() + Platform::GetProcessID()); +} diff --git a/src/lib/ggpo/platform_unix.h b/src/lib/ggpo/platform_unix.h new file mode 100644 index 00000000..a33b1f61 --- /dev/null +++ b/src/lib/ggpo/platform_unix.h @@ -0,0 +1,57 @@ +/* ----------------------------------------------------------------------- + * GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC. + * + * Use of this software is governed by the MIT license that can be found + * in the LICENSE file. + */ + +#ifndef _GGPO_UNIX_H_ +#define _GGPO_UNIX_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pevents.h" + +#define DebugBreak() (raise(SIGTRAP)) +#define ioctlsocket ioctl +#define closesocket close +#define WSAEWOULDBLOCK EWOULDBLOCK +#define INFINITE (-1) +#define WAIT_OBJECT_0 (0x00000000L) +#define FALSE (false) +#define MAX_PATH (4096) +#define INVALID_SOCKET ((SOCKET)(~0)) +#define SOCKET_ERROR (-1) + +typedef neosmart::neosmart_event_t HANDLE; +typedef uint8_t byte; +typedef int SOCKET; +typedef uint32_t DWORD; + +class Platform { +public: // types + typedef pid_t ProcessID; +public: // functions + static ProcessID GetProcessID() { return getpid(); } + static void AssertFailed(char *msg) { } + static uint32_t GetCurrentTimeMS(); + static void SleepMS(int milliseconds); + static void CreateDirectory(const char* pathname, const void* junk); +}; + +#endif diff --git a/src/lib/ggpo/platform_windows.cpp b/src/lib/ggpo/platform_windows.cpp new file mode 100644 index 00000000..98b2d544 --- /dev/null +++ b/src/lib/ggpo/platform_windows.cpp @@ -0,0 +1,15 @@ +/* ----------------------------------------------------------------------- + * GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC. + * + * Use of this software is governed by the MIT license that can be found + * in the LICENSE file. + */ + +#include "platform_windows.h" + +BOOL WINAPI +DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + srand(Platform::GetCurrentTimeMS() + Platform::GetProcessID()); + return TRUE; +} diff --git a/src/lib/ggpo/platform_windows.h b/src/lib/ggpo/platform_windows.h index dd4212a2..469cc2a2 100644 --- a/src/lib/ggpo/platform_windows.h +++ b/src/lib/ggpo/platform_windows.h @@ -9,8 +9,10 @@ #define _GGPO_WINDOWS_H_ #include +#include #include #include +#include class Platform { public: // types @@ -19,7 +21,9 @@ class Platform { public: // functions static ProcessID GetProcessID() { return GetCurrentProcessId(); } static void AssertFailed(char *msg) { MessageBoxA(NULL, msg, "GGPO Assertion Failed", MB_OK | MB_ICONEXCLAMATION); } - static uint32 GetCurrentTimeMS() { return timeGetTime(); } + static uint32_t GetCurrentTimeMS() { return timeGetTime(); } + static void SleepMS(int ms) { Sleep(ms); } + static void CreateDirectory(const char* pathname, const void* junk) { CreateDirectoryA(pathname, junk); } }; #endif diff --git a/src/lib/ggpo/poll.cpp b/src/lib/ggpo/poll.cpp index 7468d6a4..403c45b7 100644 --- a/src/lib/ggpo/poll.cpp +++ b/src/lib/ggpo/poll.cpp @@ -8,6 +8,10 @@ #include "types.h" #include "poll.h" +#ifndef _WIN32 +using namespace neosmart; +#endif + Poll::Poll(void) : _handle_count(0), _start_time(0) @@ -15,7 +19,11 @@ Poll::Poll(void) : /* * Create a dummy handle to simplify things. */ +#ifdef _WIN32 _handles[_handle_count++] = CreateEvent(NULL, true, false, NULL); +#else + _handles[_handle_count++] = CreateEvent(true, false); +#endif } void @@ -68,7 +76,11 @@ Poll::Pump(int timeout) timeout = MIN(timeout, maxwait); } +#ifdef _WIN32 res = WaitForMultipleObjects(_handle_count, _handles, false, timeout); +#else + res = WaitForMultipleEvents(_handles, _handle_count, false, timeout); +#endif if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_0 + _handle_count) { i = res - WAIT_OBJECT_0; finished = !_handle_sinks[i].sink->OnHandlePoll(_handle_sinks[i].cookie) || finished; diff --git a/src/lib/ggpo/types.h b/src/lib/ggpo/types.h index 8881a248..e46930f6 100644 --- a/src/lib/ggpo/types.h +++ b/src/lib/ggpo/types.h @@ -38,8 +38,8 @@ typedef int int32; */ #if defined(_WINDOWS) # include "platform_windows.h" -#elif defined(__GNUC__) -# include "platform_linux.h" +#elif defined(__APPLE__) or defined(__GNUC__) +# include "platform_unix.h" #else # error Unsupported platform #endif diff --git a/src/lib/ggpo/zconf.h b/src/lib/ggpo/zconf.h index 7374ff73..4f300fc3 100644 --- a/src/lib/ggpo/zconf.h +++ b/src/lib/ggpo/zconf.h @@ -181,8 +181,8 @@ # if defined (__BORLANDC__) # if (__BORLANDC__ >= 0x0500) && defined (WIN32) # include -# define ZEXPORT __declspec(dllexport) WINAPI -# define ZEXPORTRVA __declspec(dllexport) WINAPIV +# define ZEXPORT EXPORT WINAPI +# define ZEXPORTRVA EXPORT WINAPIV # else # if defined (_Windows) && defined (__DLL__) # define ZEXPORT _export @@ -194,7 +194,7 @@ #if defined (__BEOS__) # if defined (ZLIB_DLL) -# define ZEXTERN extern __declspec(dllexport) +# define ZEXTERN extern EXPORT # else # define ZEXTERN extern __declspec(dllimport) # endif