diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..7b28868 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,30 @@ +name: Build + +on: + push: + branches: [master, feature/**] + pull_request: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v6 + + - name: Configure + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: cmake --build build --config Release + + - name: Upload workflow artifact + uses: actions/upload-artifact@v7 + with: + name: psn-macos-release + path: build/examples/psn_* + if-no-files-found: error 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 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() 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