From 5222acd28137c69490e9edfa7298c5c03d18dd28 Mon Sep 17 00:00:00 2001 From: joss-dev Date: Thu, 6 Nov 2025 23:08:04 +0000 Subject: [PATCH 1/6] chore: ignore cmake build dir --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2f4074d..cd429aa 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ *.log examples/vs2015/bin/ examples/vs2015/build/ -.vs/ \ No newline at end of file +.vs/ +build \ No newline at end of file From 8285b8a0266169539592becc5bba2d48b2629f8a Mon Sep 17 00:00:00 2001 From: joss-dev Date: Thu, 6 Nov 2025 23:08:48 +0000 Subject: [PATCH 2/6] feat: add cmake build --- CMakeLists.txt | 23 +++++++++++++++++++++++ examples/CMakeLists.txt | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 examples/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7deeee4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.10) +project(PSN VERSION 2.03 LANGUAGES CXX) + +# Set C++ standard +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Create header-only library interface +add_library(psn INTERFACE) + +# Add include directory +target_include_directories(psn INTERFACE + $ + $ +) + +# Platform-specific settings for network library +if(WIN32) + target_link_libraries(psn INTERFACE ws2_32) +endif() + +# Add examples +add_subdirectory(examples) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..f218dd3 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,40 @@ +# PSN Examples CMakeLists.txt + +# psn_server executable +add_executable(psn_server + vs2015/psn_server/psn_server.cpp +) + +target_link_libraries(psn_server PRIVATE psn) + +# psn_client executable +add_executable(psn_client + vs2015/psn_client/psn_client.cpp +) + +target_link_libraries(psn_client PRIVATE psn) + +# psn_glut_client executable (optional - requires GLUT and OpenGL) +find_package(OpenGL) +find_package(GLUT) + +if(OpenGL_FOUND AND GLUT_FOUND) + add_executable(psn_glut_client + vs2015/psn_glut_client/psn_glut_client.cpp + ) + + target_link_libraries(psn_glut_client PRIVATE psn OpenGL::GL GLUT::GLUT) + target_include_directories(psn_glut_client PRIVATE ${OPENGL_INCLUDE_DIR} ${GLUT_INCLUDE_DIR}) + + # macOS specific: no need to copy DLL files + # Windows would need post-build copy for glut32.dll or glut64.dll + + message(STATUS "Building psn_glut_client (OpenGL visualizer)") +else() + if(NOT OpenGL_FOUND) + message(WARNING "OpenGL not found - skipping psn_glut_client") + endif() + if(NOT GLUT_FOUND) + message(WARNING "GLUT not found - skipping psn_glut_client") + endif() +endif() From b68931d891865bcd290f56ab0d023821673bddd1 Mon Sep 17 00:00:00 2001 From: joss-dev Date: Thu, 6 Nov 2025 23:11:13 +0000 Subject: [PATCH 3/6] feat: add mac support --- examples/vs2015/psn_client/psn_client.cpp | 18 +- .../psn_glut_client/psn_glut_client.cpp | 23 ++- examples/vs2015/psn_server/psn_server.cpp | 16 +- include/utils/udp_socket.h | 63 ++++--- include/utils/udp_socket_posix.h | 175 ++++++++++++++++++ 5 files changed, 257 insertions(+), 38 deletions(-) create mode 100644 include/utils/udp_socket_posix.h diff --git a/examples/vs2015/psn_client/psn_client.cpp b/examples/vs2015/psn_client/psn_client.cpp index 7a1e9f8..6b82769 100644 --- a/examples/vs2015/psn_client/psn_client.cpp +++ b/examples/vs2015/psn_client/psn_client.cpp @@ -30,7 +30,15 @@ #include -void main( void ) +// Platform-specific sleep function +#ifdef _WIN32 + #define SLEEP_MS(ms) Sleep(ms) +#else + #include + #define SLEEP_MS(ms) usleep((ms) * 1000) +#endif + +int main( void ) { wsa_session session ; @@ -46,9 +54,9 @@ void main( void ) //==================================================== // Main loop - while ( 1 ) + while ( 1 ) { - Sleep( 1 ) ; + SLEEP_MS( 1 ) ; // Update Client ::std::string msg ; @@ -114,6 +122,6 @@ void main( void ) } } } -} - + return 0 ; +} diff --git a/examples/vs2015/psn_glut_client/psn_glut_client.cpp b/examples/vs2015/psn_glut_client/psn_glut_client.cpp index c894d4c..78b0914 100644 --- a/examples/vs2015/psn_glut_client/psn_glut_client.cpp +++ b/examples/vs2015/psn_glut_client/psn_glut_client.cpp @@ -39,9 +39,24 @@ #include #endif -#include -#include -#include +// Platform-specific sleep function +#ifdef _WIN32 + #define SLEEP_MS(ms) Sleep(ms) +#else + #include + #define SLEEP_MS(ms) usleep((ms) * 1000) +#endif + +// Platform-specific OpenGL headers +#ifdef __APPLE__ + #include + #include + #include +#else + #include + #include + #include +#endif #define PI_DEF 3.1415926535897932384626433832795f @@ -261,7 +276,7 @@ idle( void ) ::std::cout << "\n-----------------------------------------------" << ::std::endl ; } - Sleep( 10 ) ; + SLEEP_MS( 10 ) ; // schedule GL update glutPostRedisplay() ; diff --git a/examples/vs2015/psn_server/psn_server.cpp b/examples/vs2015/psn_server/psn_server.cpp index 005a204..20fc549 100644 --- a/examples/vs2015/psn_server/psn_server.cpp +++ b/examples/vs2015/psn_server/psn_server.cpp @@ -31,7 +31,15 @@ #include #include -void main( void ) +// Platform-specific sleep function +#ifdef _WIN32 + #define SLEEP_MS(ms) Sleep(ms) +#else + #include + #define SLEEP_MS(ms) usleep((ms) * 1000) +#endif + +int main( void ) { wsa_session session ; @@ -120,7 +128,9 @@ void main( void ) ::std::cout << "-----------------------------------------------" << ::std::endl ; } - Sleep( 1 ) ; - timestamp++ ; + SLEEP_MS( 1 ) ; + timestamp++ ; } + + return 0 ; } \ No newline at end of file diff --git a/include/utils/udp_socket.h b/include/utils/udp_socket.h index 98f7fbf..f30e2e5 100644 --- a/include/utils/udp_socket.h +++ b/include/utils/udp_socket.h @@ -27,10 +27,14 @@ THE SOFTWARE. #ifndef UDP_SOCKET_H #define UDP_SOCKET_H +// Platform detection - include appropriate implementation +#ifdef _WIN32 + +// Windows implementation #include #include -#pragma message("Note: including lib: Ws2_32.lib") +#pragma message("Note: including lib: Ws2_32.lib") #pragma comment(lib, "Ws2_32.lib") #include @@ -49,7 +53,7 @@ class wsa_session WSACleanup() ; } - private : + private : WSAData data_ ; } ; @@ -59,7 +63,7 @@ class udp_socket { public : - udp_socket( void ) + udp_socket( void ) { // UDP socket socket_ = socket( AF_INET , SOCK_DGRAM , IPPROTO_UDP ) ; @@ -69,7 +73,7 @@ class udp_socket ioctlsocket( socket_ , FIONBIO , &arg ) ; } - ~udp_socket( void ) + ~udp_socket( void ) { closesocket( socket_ ) ; } @@ -80,7 +84,7 @@ class udp_socket add.sin_family = AF_INET ; InetPton( AF_INET , address.c_str() , &add.sin_addr.s_addr ) ; add.sin_port = htons( port ) ; - + if ( sendto( socket_ , message.c_str() , (int)message.length() , 0 , reinterpret_cast( &add ) , sizeof( add ) ) > 0 ) return true ; @@ -117,52 +121,59 @@ class udp_socket return false ; } - bool enable_send_message_multicast( void ) + bool enable_send_message_multicast( void ) { struct in_addr add ; add.s_addr = INADDR_ANY ; - int result = setsockopt( socket_ , - IPPROTO_IP , - IP_MULTICAST_IF , - (const char*)&add , + int result = setsockopt( socket_ , + IPPROTO_IP , + IP_MULTICAST_IF , + (const char*)&add , sizeof( add ) ) ; - return ( result != SOCKET_ERROR ) ; + return ( result != SOCKET_ERROR ) ; } - bool join_multicast_group( const ::std::string & ip_group ) + bool join_multicast_group( const ::std::string & ip_group ) { - struct ip_mreq imr; - + struct ip_mreq imr; + InetPton( AF_INET , ip_group.c_str() , &imr.imr_multiaddr.s_addr ) ; imr.imr_interface.s_addr = INADDR_ANY ; - int result = setsockopt( socket_ , - IPPROTO_IP , - IP_ADD_MEMBERSHIP , - (char*) &imr , + int result = setsockopt( socket_ , + IPPROTO_IP , + IP_ADD_MEMBERSHIP , + (char*) &imr , sizeof(struct ip_mreq) ) ; - return ( result != SOCKET_ERROR ) ; + return ( result != SOCKET_ERROR ) ; } - bool leave_multicast_group( const ::std::string & ip_group ) + bool leave_multicast_group( const ::std::string & ip_group ) { struct ip_mreq imr; - + InetPton( AF_INET , ip_group.c_str() , &imr.imr_multiaddr.s_addr ) ; imr.imr_interface.s_addr = INADDR_ANY ; - int result = setsockopt( socket_ , - IPPROTO_IP , + int result = setsockopt( socket_ , + IPPROTO_IP , IP_DROP_MEMBERSHIP , - (char*) &imr , + (char*) &imr , sizeof(struct ip_mreq) ) ; - return ( result != SOCKET_ERROR ) ; + return ( result != SOCKET_ERROR ) ; } private : - SOCKET socket_ ; + SOCKET socket_ ; } ; +#else + +// POSIX implementation (macOS, Linux, etc.) +#include "udp_socket_posix.h" + +#endif + #endif diff --git a/include/utils/udp_socket_posix.h b/include/utils/udp_socket_posix.h new file mode 100644 index 0000000..c324e54 --- /dev/null +++ b/include/utils/udp_socket_posix.h @@ -0,0 +1,175 @@ +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +/* +The MIT License (MIT) + +Copyright (c) 2014 VYV Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +#ifndef UDP_SOCKET_POSIX_H +#define UDP_SOCKET_POSIX_H + +#include +#include +#include +#include +#include +#include +#include + +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +class wsa_session +{ + public : + + wsa_session() + { + // No initialization needed on POSIX systems + } + ~wsa_session() + { + // No cleanup needed on POSIX systems + } + + private : + // Empty on POSIX +} ; + +//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +class udp_socket +{ + public : + + udp_socket( void ) + { + // UDP socket + socket_ = socket( AF_INET , SOCK_DGRAM , IPPROTO_UDP ) ; + + if ( socket_ >= 0 ) + { + // Set non-blocking mode + int flags = fcntl( socket_ , F_GETFL , 0 ) ; + fcntl( socket_ , F_SETFL , flags | O_NONBLOCK ) ; + } + } + + ~udp_socket( void ) + { + if ( socket_ >= 0 ) + { + close( socket_ ) ; + } + } + + bool send_message( const std::string & address , unsigned short port , const ::std::string & message ) + { + sockaddr_in add ; + std::memset( &add , 0 , sizeof( add ) ) ; + add.sin_family = AF_INET ; + inet_pton( AF_INET , address.c_str() , &add.sin_addr.s_addr ) ; + add.sin_port = htons( port ) ; + + ssize_t sent = sendto( socket_ , message.c_str() , message.length() , 0 , reinterpret_cast( &add ) , sizeof( add ) ) ; + return ( sent > 0 ) ; + } + + bool receive_message( ::std::string & message , int max_size = 1500 ) + { + if ( max_size <= 0 ) + return false ; + + char * buffer = (char *)malloc( max_size ) ; + + ssize_t byte_recv = recv( socket_ , buffer , max_size , 0 ) ; + + if ( byte_recv > 0 ) + message = ::std::string( buffer , byte_recv ) ; + + free( buffer ) ; + + return ( byte_recv > 0 ) ; + } + + bool bind( unsigned short port ) + { + sockaddr_in add ; + std::memset( &add , 0 , sizeof( add ) ) ; + add.sin_family = AF_INET ; + add.sin_addr.s_addr = htonl( INADDR_ANY ) ; + add.sin_port = htons( port ) ; + + if ( ::bind( socket_ , reinterpret_cast( &add ) , sizeof( add ) ) == 0 ) + return true ; + + return false ; + } + + bool enable_send_message_multicast( void ) + { + struct in_addr add ; + add.s_addr = INADDR_ANY ; + + int result = setsockopt( socket_ , + IPPROTO_IP , + IP_MULTICAST_IF , + (const char*)&add , + sizeof( add ) ) ; + return ( result == 0 ) ; + } + + bool join_multicast_group( const ::std::string & ip_group ) + { + struct ip_mreq imr; + std::memset( &imr , 0 , sizeof( imr ) ) ; + + inet_pton( AF_INET , ip_group.c_str() , &imr.imr_multiaddr.s_addr ) ; + imr.imr_interface.s_addr = INADDR_ANY ; + + int result = setsockopt( socket_ , + IPPROTO_IP , + IP_ADD_MEMBERSHIP , + (char*) &imr , + sizeof(struct ip_mreq) ) ; + return ( result == 0 ) ; + } + + bool leave_multicast_group( const ::std::string & ip_group ) + { + struct ip_mreq imr; + std::memset( &imr , 0 , sizeof( imr ) ) ; + + inet_pton( AF_INET , ip_group.c_str() , &imr.imr_multiaddr.s_addr ) ; + imr.imr_interface.s_addr = INADDR_ANY ; + + int result = setsockopt( socket_ , + IPPROTO_IP , + IP_DROP_MEMBERSHIP , + (char*) &imr , + sizeof(struct ip_mreq) ) ; + return ( result == 0 ) ; + } + + private : + + int socket_ ; +} ; + +#endif From f4e7901159d8c9e236020146e1ce11ca745299e2 Mon Sep 17 00:00:00 2001 From: Joss Gray Date: Mon, 9 Mar 2026 16:54:48 -0400 Subject: [PATCH 4/6] ci: add GitHub Actions workflow for macOS build Co-Authored-By: Claude Opus 4.6 --- .github/workflows/build.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..b0bf5a6 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,19 @@ +name: Build + +on: + push: + branches: [master, feature/**] + pull_request: + branches: [master] + +jobs: + build-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Configure + run: cmake -B build + + - name: Build + run: cmake --build build From 56bcd83503f465ef33ebfacb28d016e6b3e65a8b Mon Sep 17 00:00:00 2001 From: Joss Gray Date: Wed, 11 Mar 2026 11:37:04 -0400 Subject: [PATCH 5/6] ci: update macOS build workflow to include artifact upload. Build in release --- .github/workflows/build.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b0bf5a6..7991a9b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,6 +5,10 @@ on: branches: [master, feature/**] pull_request: branches: [master] + workflow_dispatch: + +permissions: + contents: read jobs: build-macos: @@ -13,7 +17,14 @@ jobs: - uses: actions/checkout@v4 - name: Configure - run: cmake -B build + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release - name: Build - run: cmake --build build + run: cmake --build build --config Release + + - name: Upload workflow artifact + uses: actions/upload-artifact@v4 + with: + name: psn-macos-release + path: build/examples/psn_* + if-no-files-found: error From a8ccf7c86b6433564d73ece0936121959b60302d Mon Sep 17 00:00:00 2001 From: Joss Gray Date: Wed, 11 Mar 2026 11:38:46 -0400 Subject: [PATCH 6/6] ci: update actions/checkout and actions/upload-artifact versions in macOS build workflow --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7991a9b..7b28868 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: build-macos: runs-on: macos-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Configure run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release @@ -23,7 +23,7 @@ jobs: run: cmake --build build --config Release - name: Upload workflow artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: psn-macos-release path: build/examples/psn_*