v1.4.4
This commit is contained in:
commit
9c94d113d3
10260 changed files with 1237388 additions and 0 deletions
533
source/CMakeLists.txt
Normal file
533
source/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,533 @@
|
|||
PROJECT (starbound)
|
||||
|
||||
CMAKE_MINIMUM_REQUIRED (VERSION 3.0)
|
||||
SET (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../cmake)
|
||||
|
||||
SET (CMAKE_CONFIGURATION_TYPES Debug RelWithAsserts RelWithDebInfo Release)
|
||||
SET (CMAKE_EXE_LINKER_FLAGS_RELWITHASSERTS "" CACHE STRING "" FORCE)
|
||||
# Update the docstring on CMAKE_BUILD_TYPE to show what options we actually
|
||||
# allow
|
||||
SET (CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "Choose the type of build, options are: Debug RelWithAsserts RelWithDebInfo Release" FORCE)
|
||||
|
||||
# Discover all the relevant environment / system information and place the
|
||||
# result in STAR_* cmake variables.
|
||||
|
||||
# STAR_SOURCE_IDENTIFIER may be set to any string value
|
||||
IF (NOT DEFINED STAR_SOURCE_IDENTIFIER)
|
||||
INCLUDE (GetGitRevisionDescription)
|
||||
GET_GIT_HEAD_REVISION (STAR_GIT_REFSPEC STAR_GIT_HASHVAR)
|
||||
SET (STAR_SOURCE_IDENTIFIER "${STAR_GIT_HASHVAR}")
|
||||
ENDIF ()
|
||||
|
||||
# Architecture identifier, like i386, x86_64 or ppc
|
||||
IF (NOT DEFINED STAR_ARCHITECTURE)
|
||||
INCLUDE (TargetArch)
|
||||
TARGET_ARCHITECTURE (STAR_ARCHITECTURE)
|
||||
ENDIF ()
|
||||
|
||||
# Either TRUE or FALSE
|
||||
IF (NOT DEFINED STAR_LITTLE_ENDIAN)
|
||||
INCLUDE (TestBigEndian)
|
||||
TEST_BIG_ENDIAN (BIGENDIAN)
|
||||
IF (NOT BIGENDIAN)
|
||||
SET (STAR_LITTLE_ENDIAN TRUE)
|
||||
ELSE ()
|
||||
SET (STAR_LITTLE_ENDIAN FALSE)
|
||||
ENDIF ()
|
||||
ENDIF ()
|
||||
|
||||
# System name, like windows, macos, linux, freebsd, or (generic) unix
|
||||
IF (NOT DEFINED STAR_SYSTEM)
|
||||
IF (WIN32)
|
||||
SET (STAR_SYSTEM "windows")
|
||||
ELSEIF (APPLE AND ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
|
||||
SET (STAR_SYSTEM "macos")
|
||||
ELSEIF (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
SET (STAR_SYSTEM "linux")
|
||||
ELSEIF (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
|
||||
SET (STAR_SYSTEM "freebsd")
|
||||
ELSEIF (UNIX)
|
||||
SET (STAR_SYSTEM "unix")
|
||||
ELSE ()
|
||||
SET (STAR_SYSTEM "unknown")
|
||||
ENDIF ()
|
||||
ENDIF ()
|
||||
|
||||
IF (NOT DEFINED STAR_SYSTEM_FAMILY)
|
||||
IF (WIN32)
|
||||
SET (STAR_SYSTEM_FAMILY "windows")
|
||||
ELSEIF (UNIX)
|
||||
SET (STAR_SYSTEM_FAMILY "unix")
|
||||
ELSE ()
|
||||
SET (STAR_SYSTEM_FAMILY "unknown")
|
||||
ENDIF ()
|
||||
ENDIF ()
|
||||
|
||||
# C/C++ compiler ID, like clang, gnu, or msvc
|
||||
IF (NOT DEFINED STAR_COMPILER)
|
||||
IF (NOT CMAKE_C_COMPILER_ID STREQUAL CMAKE_CXX_COMPILER_ID)
|
||||
MESSAGE (FATAL_ERROR "C and C++ compiler id do not match, unsupported build configuration")
|
||||
ENDIF ()
|
||||
|
||||
IF (CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
||||
SET (STAR_COMPILER "clang")
|
||||
ELSEIF (CMAKE_COMPILER_IS_GNUC)
|
||||
SET (STAR_COMPILER "gnu")
|
||||
ELSEIF (MSVC)
|
||||
SET (STAR_COMPILER "msvc")
|
||||
ELSE ()
|
||||
STRING (TOLOWER "${CMAKE_C_COMPILER_ID}" STAR_COMPILER)
|
||||
ENDIF ()
|
||||
ENDIF ()
|
||||
|
||||
# Enable OPTIONs based on the discovered system / environment...
|
||||
|
||||
IF (STAR_COMPILER STREQUAL "gnu")
|
||||
OPTION (STAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX "Statically link libgcc and libstdc++" OFF)
|
||||
OPTION (STAR_ENABLE_GCC_PROFILING "Enable gcc/g++ profiling via the -pg flag" OFF)
|
||||
OPTION (STAR_ENABLE_GLIBCXX_DEBUG "Enable _GLIBCXX_DEBUG for g++" OFF)
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_COMPILER STREQUAL "msvc")
|
||||
OPTION (STAR_ENABLE_STATIC_MSVC_RUNTIME "Statically link with the CRT" OFF)
|
||||
ENDIF ()
|
||||
|
||||
OPTION (STAR_BUILD_GUI "Build GUI utilities and Client" ON)
|
||||
|
||||
IF (STAR_BUILD_GUI)
|
||||
OPTION (STAR_BUILD_QT_TOOLS "Build GUI utilities that require Qt" OFF)
|
||||
OPTION (STAR_ENABLE_STEAM_INTEGRATION "Use Steam platform services" OFF)
|
||||
OPTION (STAR_ENABLE_DISCORD_INTEGRATION "Use Discord platform services" OFF)
|
||||
ENDIF ()
|
||||
|
||||
OPTION (STAR_LUA_APICHECK "Use lua api checks" OFF)
|
||||
OPTION (STAR_USE_JEMALLOC "Use jemalloc allocators" OFF)
|
||||
|
||||
# Report all the discovered system / environment settings and all options.
|
||||
|
||||
MESSAGE (STATUS "Source ID: ${STAR_SOURCE_IDENTIFIER}")
|
||||
MESSAGE (STATUS "Architecture: ${STAR_ARCHITECTURE}")
|
||||
MESSAGE (STATUS "Little Endian: ${STAR_LITTLE_ENDIAN}")
|
||||
MESSAGE (STATUS "System: ${STAR_SYSTEM}")
|
||||
MESSAGE (STATUS "System family: ${STAR_SYSTEM_FAMILY}")
|
||||
MESSAGE (STATUS "C/C++ compiler: ${STAR_COMPILER}")
|
||||
|
||||
IF (DEFINED STAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX)
|
||||
MESSAGE (STATUS "Statically linking to libgcc / libstdc++: ${STAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX}")
|
||||
ENDIF ()
|
||||
|
||||
IF (DEFINED STAR_ENABLE_STATIC_MSVC_RUNTIME)
|
||||
MESSAGE (STATUS "Statically linking to CRT: ${STAR_ENABLE_STATIC_MSVC_RUNTIME}")
|
||||
ENDIF ()
|
||||
|
||||
IF (DEFINED STAR_ENABLE_GLIBCXX_DEBUG)
|
||||
MESSAGE (STATUS "Enabling _GLIBCXX_DEBUG: ${STAR_ENABLE_GLIBCXX_DEBUG}")
|
||||
ENDIF ()
|
||||
|
||||
MESSAGE (STATUS "Building GUI: ${STAR_BUILD_GUI}")
|
||||
|
||||
IF (DEFINED STAR_BUILD_QT_TOOLS)
|
||||
MESSAGE (STATUS "Building Qt tools: ${STAR_BUILD_QT_TOOLS}")
|
||||
ENDIF ()
|
||||
|
||||
IF (DEFINED STAR_ENABLE_STEAM_INTEGRATION)
|
||||
MESSAGE (STATUS "Using Steam platform services: ${STAR_ENABLE_STEAM_INTEGRATION}")
|
||||
ENDIF ()
|
||||
|
||||
IF (DEFINED STAR_ENABLE_DISCORD_INTEGRATION)
|
||||
MESSAGE (STATUS "Using Discrod platform services: ${STAR_ENABLE_DISCORD_INTEGRATION}")
|
||||
ENDIF ()
|
||||
|
||||
MESSAGE (STATUS "Using Lua API checks: ${STAR_LUA_APICHECK}")
|
||||
MESSAGE (STATUS "Using jemalloc: ${STAR_USE_JEMALLOC}")
|
||||
|
||||
# Set C defines and cmake variables based on the build settings we have now
|
||||
# determined...
|
||||
|
||||
# Set a cmake variable to true and define a corresponding C/C++ definition
|
||||
FUNCTION (SET_FLAG flagValue)
|
||||
SET (${flagValue} TRUE PARENT_SCOPE)
|
||||
ADD_DEFINITIONS (-D${flagValue})
|
||||
ENDFUNCTION ()
|
||||
|
||||
IF (STAR_LITTLE_ENDIAN)
|
||||
SET_FLAG (STAR_LITTLE_ENDIAN)
|
||||
ELSEIF ()
|
||||
SET_FLAG (STAR_BIG_ENDIAN)
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_ARCHITECTURE STREQUAL "i386")
|
||||
SET_FLAG (STAR_ARCHITECTURE_I386)
|
||||
ELSEIF (STAR_ARCHITECTURE STREQUAL "x86_64")
|
||||
SET_FLAG (STAR_ARCHITECTURE_X86_64)
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_SYSTEM STREQUAL "windows")
|
||||
SET_FLAG (STAR_SYSTEM_WINDOWS)
|
||||
ELSEIF (STAR_SYSTEM STREQUAL "macos")
|
||||
SET_FLAG (STAR_SYSTEM_MACOS)
|
||||
ELSEIF (STAR_SYSTEM STREQUAL "linux")
|
||||
SET_FLAG (STAR_SYSTEM_LINUX)
|
||||
ELSEIF (STAR_SYSTEM STREQUAL "freebsd")
|
||||
SET_FLAG (STAR_SYSTEM_FREEBSD)
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_SYSTEM_FAMILY STREQUAL "windows")
|
||||
SET_FLAG (STAR_SYSTEM_FAMILY_WINDOWS)
|
||||
ELSEIF (STAR_SYSTEM_FAMILY STREQUAL "unix")
|
||||
SET_FLAG (STAR_SYSTEM_FAMILY_UNIX)
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_COMPILER STREQUAL "gnu")
|
||||
SET_FLAG (STAR_COMPILER_GNU)
|
||||
ELSEIF (STAR_COMPILER STREQUAL "clang")
|
||||
SET_FLAG (STAR_COMPILER_CLANG)
|
||||
ELSEIF (STAR_COMPILER STREQUAL "msvc")
|
||||
SET_FLAG (STAR_COMPILER_MSVC)
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_LUA_APICHECK)
|
||||
ADD_DEFINITIONS (-DLUA_USE_APICHECK)
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_SYSTEM_WINDOWS)
|
||||
# LUA_USE_WINDOWS is automatically defined in luaconf if _WIN32 is defined
|
||||
ELSEIF (STAR_SYSTEM_MACOS)
|
||||
ADD_DEFINITIONS(-DLUA_USE_MACOSX)
|
||||
ELSEIF (STAR_SYSTEM_LINUX)
|
||||
ADD_DEFINITIONS(-DLUA_USE_LINUX)
|
||||
ELSEIF (STAR_SYSTEM_FAMILY_UNIX)
|
||||
ADD_DEFINITIONS(-DLUA_USE_POSIX)
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_ENABLE_STEAM_INTEGRATION)
|
||||
ADD_DEFINITIONS (-DSTAR_ENABLE_STEAM_INTEGRATION)
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_ENABLE_DISCORD_INTEGRATION)
|
||||
ADD_DEFINITIONS (-DSTAR_ENABLE_DISCORD_INTEGRATION)
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_USE_JEMALLOC)
|
||||
ADD_DEFINITIONS (-DSTAR_USE_JEMALLOC)
|
||||
ENDIF ()
|
||||
|
||||
# Set C/C++ compiler flags based on build environment...
|
||||
|
||||
IF (STAR_COMPILER_GNU)
|
||||
SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wuninitialized -Wunreachable-code -Wformat -no-pie")
|
||||
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -Wextra -Wuninitialized -Wunreachable-code -Wformat -no-pie")
|
||||
|
||||
IF (STAR_SYSTEM_FAMILY_WINDOWS)
|
||||
SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mthreads")
|
||||
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mthreads")
|
||||
ELSE ()
|
||||
SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread -D_REENTRANT")
|
||||
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -D_REENTRANT")
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_ENABLE_STATIC_LIBGCC_LIBSTDCXX)
|
||||
SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static-libgcc")
|
||||
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++")
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_ENABLE_GCC_PROFILING)
|
||||
SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg")
|
||||
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
|
||||
ENDIF ()
|
||||
|
||||
SET (CMAKE_C_FLAGS_DEBUG "-g -Og")
|
||||
SET (CMAKE_CXX_FLAGS_DEBUG "-g -Og")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELWITHASSERTS "-g -Ofast")
|
||||
SET (CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -Ofast")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast")
|
||||
SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELEASE "-DNDEBUG -Ofast")
|
||||
SET (CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Ofast")
|
||||
|
||||
ELSEIF (STAR_COMPILER_CLANG)
|
||||
SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wuninitialized -Wno-parentheses-equality -Wno-deprecated-declarations")
|
||||
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -Wextra -Wuninitialized -Wno-parentheses-equality -Wno-deprecated-declarations")
|
||||
|
||||
IF (STAR_SYSTEM_MACOS)
|
||||
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
SET (CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++14")
|
||||
SET (CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
|
||||
ELSEIF ()
|
||||
SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread -D_REENTRANT")
|
||||
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -D_REENTRANT")
|
||||
ENDIF ()
|
||||
|
||||
SET (CMAKE_C_FLAGS_DEBUG "-g")
|
||||
SET (CMAKE_CXX_FLAGS_DEBUG "-g")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELWITHASSERTS "-g -Ofast")
|
||||
SET (CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -Ofast")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast")
|
||||
SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -DNDEBUG -Ofast")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELEASE "-DNDEBUG -Ofast")
|
||||
SET (CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Ofast")
|
||||
|
||||
ELSEIF (STAR_COMPILER_MSVC)
|
||||
# /MP - Multi-processor building
|
||||
# /EHsc - Enable normal C++ exception handling
|
||||
# /bigobj - More sections in .obj files (Cannot build in Debug without it)
|
||||
# /MT - Use multi-threaded statically linked C runtime library
|
||||
# /GA - Optimize for windows application
|
||||
# /Ox - Full optimization
|
||||
# /fp:fast - Equivalent to -ffast-math
|
||||
# /GS- - Disable buffers security check
|
||||
# /Zi - Generates debugging information without Edit and Continue
|
||||
# /Gy - Use function-level linking
|
||||
# /wd4996 - Disable warnings about unsafe C functions
|
||||
# /wd4351 - Disable warnings about new behavior of default initialization of
|
||||
# arrays (which is the correct behavior anyway)
|
||||
# /wd4800 - Disable warnings about using non-bool as true or false (useless
|
||||
# performance warning)
|
||||
# /wd4244 - Disable warnings about type conversion loss of data, it's a nice
|
||||
# warning, but it triggers on lots and lots of harmless things that no
|
||||
# other compiler warns about, like passing an int as a float parameter
|
||||
# /wd4305 - Disable warnings about truncation from double to float
|
||||
# /wd4267 - Disable warnings about 64 - 32 bit truncation
|
||||
# /wd4456 - Disable warnings about hiding previous local declaration
|
||||
# /wd4503 - Silence warnings about MSVC generating a name so long it has to
|
||||
# truncate it
|
||||
# /wd4250 - Silence "XX inherits YY via dominance"
|
||||
# /wd4624 - Silence implicitly deleted destructor warnings that show up when
|
||||
# using unions in interesting ways.
|
||||
|
||||
SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP /EHsc /bigobj /wd4996 /wd4351 /wd4800 /wd4244 /wd4305 /wd4267 /wd4456 /wd4503 /wd4250 /wd4624")
|
||||
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /EHsc /bigobj /wd4996 /wd4351 /wd4800 /wd4244 /wd4305 /wd4267 /wd4456 /wd4503 /wd4250 /wd4624")
|
||||
|
||||
IF (STAR_ENABLE_STATIC_MSVC_RUNTIME)
|
||||
SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MT")
|
||||
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MT")
|
||||
ENDIF ()
|
||||
|
||||
SET (CMAKE_C_FLAGS_DEBUG "/Zi /Od")
|
||||
SET (CMAKE_CXX_FLAGS_DEBUG "/Zi /Od")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELWITHASSERTS "/Ox /fp:fast /GA /GS- /Zi /Gy")
|
||||
SET (CMAKE_CXX_FLAGS_RELWITHASSERTS "/Ox /fp:fast /GA /GS- /Zi /Gy")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELWITHDEBINFO "/Ox /fp:fast /GA /GS- /Zi /Gy /DNDEBUG")
|
||||
SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO "/Ox /fp:fast /GA /GS- /Zi /Gy /DNDEBUG")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELEASE "/Ox /fp:fast /GA /GS- /Gy /DNDEBUG")
|
||||
SET (CMAKE_CXX_FLAGS_RELEASE "/Ox /fp:fast /GA /GS- /Gy /DNDEBUG")
|
||||
|
||||
IF (STAR_ARCHITECTURE_I386)
|
||||
# Assume all 32 bit target cpus support MMX, SSE, and SSE2
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELWITHASSERTS "${CMAKE_C_FLAGS_RELWITHASSERTS} /arch:SSE2")
|
||||
SET (CMAKE_CXX_FLAGS_RELWITHASSERTS "${CMAKE_CXX_FLAGS_RELWITHASSERTS} /arch:SSE2")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} /arch:SSE2")
|
||||
SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /arch:SSE2")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /arch:SSE2")
|
||||
SET (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /arch:SSE2")
|
||||
ENDIF ()
|
||||
|
||||
ADD_DEFINITIONS (/DUNICODE)
|
||||
ADD_DEFINITIONS (/D_UNICODE)
|
||||
ADD_DEFINITIONS (/DNOMINMAX)
|
||||
|
||||
ELSE ()
|
||||
SET (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pthread -D_REENTRANT")
|
||||
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -pthread -D_REENTRANT")
|
||||
|
||||
SET (CMAKE_C_FLAGS_DEBUG "-g")
|
||||
SET (CMAKE_CXX_FLAGS_DEBUG "-g")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELWITHASSERTS "-g -O2")
|
||||
SET (CMAKE_CXX_FLAGS_RELWITHASSERTS "-g -O2")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELWITHDEBINFO "-DNDEBUG -g -O2")
|
||||
SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO "-DNDEBUG -g -O2")
|
||||
|
||||
SET (CMAKE_C_FLAGS_RELEASE "$-DNDEBUG -O2")
|
||||
SET (CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O2")
|
||||
|
||||
ENDIF ()
|
||||
|
||||
# Set other global build settings based on environment...
|
||||
|
||||
IF (STAR_SYSTEM_MACOS)
|
||||
SET (CMAKE_MODULE_LINKER_FLAGS "-flat_namespace -undefined suppress")
|
||||
ELSEIF (STAR_SYSTEM_WINDOWS)
|
||||
SET (CMAKE_RC_COMPILER_INIT windres)
|
||||
|
||||
ENABLE_LANGUAGE (RC)
|
||||
SET (CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_COMPILER STREQUAL "msvc")
|
||||
# /largeaddressaware - Make 32 bit build able to use 3GB addresses
|
||||
# /OPT:REF - Eliminates functions and data that are never referenced
|
||||
# /OPT:ICF - Performs identical COMDAT folding
|
||||
# /PDBCompress - Hint to windows that it should compress the resulting PDB files
|
||||
SET (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /largeaddressaware /OPT:REF /OPT:ICF /PDBCompress")
|
||||
|
||||
# Make sure RelWithAsserts has debugging enabled
|
||||
SET (CMAKE_EXE_LINKER_FLAGS_RELWITHASSERTS "${CMAKE_EXE_LINKER_FLAGS_RELWITHASSERTS} /DEBUG")
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_SYSTEM_WINDOWS)
|
||||
SET (CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} ws2_32.lib iphlpapi.lib shlwapi.lib dbghelp.lib")
|
||||
SET (CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} ws2_32.lib iphlpapi.lib shlwapi.lib dbghelp.lib")
|
||||
|
||||
ELSEIF (STAR_SYSTEM_LINUX)
|
||||
SET (CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lpthread -ldl -lrt")
|
||||
SET (CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lpthread -ldl -lrt")
|
||||
|
||||
ELSEIF (STAR_SYSTEM_FREEBSD)
|
||||
SET (CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lpthread -lrt")
|
||||
SET (CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lpthread -lrt")
|
||||
|
||||
ENDIF ()
|
||||
|
||||
# Find all required external libraries, based on build settings...
|
||||
|
||||
IF (STAR_USE_JEMALLOC)
|
||||
# Assumes jemalloc was configured with a "je_" function prefix
|
||||
FIND_PACKAGE (JeMalloc REQUIRED)
|
||||
|
||||
INCLUDE_DIRECTORIES (SYSTEM ${JEMALLOC_INCLUDE_DIR})
|
||||
SET (STAR_EXT_LIBS ${JEMALLOC_LIBRARY})
|
||||
ENDIF ()
|
||||
|
||||
FIND_PACKAGE (ZLIB REQUIRED)
|
||||
FIND_PACKAGE (PNG REQUIRED)
|
||||
FIND_PACKAGE (Freetype REQUIRED)
|
||||
FIND_PACKAGE (OggVorbis REQUIRED)
|
||||
|
||||
INCLUDE_DIRECTORIES (SYSTEM
|
||||
${ZLIB_INCLUDE_DIR}
|
||||
${PNG_INCLUDE_DIR}
|
||||
${FREETYPE_INCLUDE_DIRS}
|
||||
${OGGVORBIS_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
SET (STAR_EXT_LIBS ${STAR_EXT_LIBS}
|
||||
${VORBISFILE_LIBRARY}
|
||||
${VORBIS_LIBRARY}
|
||||
${OGG_LIBRARY}
|
||||
${FREETYPE_LIBRARY}
|
||||
${PNG_LIBRARY}
|
||||
${ZLIB_LIBRARY}
|
||||
)
|
||||
|
||||
IF (STAR_BUILD_GUI)
|
||||
FIND_PACKAGE (SDL2 REQUIRED)
|
||||
INCLUDE_DIRECTORIES (SYSTEM ${SDL2_INCLUDE_DIR})
|
||||
SET (STAR_EXT_GUI_LIBS ${SDL2_LIBRARY})
|
||||
|
||||
FIND_PACKAGE (OpenGL REQUIRED)
|
||||
FIND_PACKAGE (GLEW REQUIRED)
|
||||
|
||||
INCLUDE_DIRECTORIES (SYSTEM ${GLEW_INCLUDE_DIR} ${SDL2_INCLUDE_DIR})
|
||||
SET (STAR_EXT_GUI_LIBS ${STAR_EXT_GUI_LIBS} ${OPENGL_LIBRARY} ${GLEW_LIBRARY})
|
||||
|
||||
IF (STAR_ENABLE_STEAM_INTEGRATION)
|
||||
FIND_PACKAGE (SteamApi REQUIRED)
|
||||
|
||||
INCLUDE_DIRECTORIES (SYSTEM ${STEAM_API_INCLUDE_DIR})
|
||||
SET (STAR_EXT_GUI_LIBS ${STAR_EXT_GUI_LIBS} ${STEAM_API_LIBRARY})
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_ENABLE_DISCORD_INTEGRATION)
|
||||
FIND_PACKAGE (DiscordApi REQUIRED)
|
||||
|
||||
INCLUDE_DIRECTORIES (SYSTEM ${DISCORD_API_INCLUDE_DIR})
|
||||
SET (STAR_EXT_GUI_LIBS ${STAR_EXT_GUI_LIBS} ${DISCORD_API_LIBRARY})
|
||||
ENDIF ()
|
||||
ENDIF ()
|
||||
|
||||
# Set basic build flags, include all the relevant source directories, based on
|
||||
# build settings...
|
||||
|
||||
SET (BUILD_SHARED_LIBS false)
|
||||
|
||||
# First set output dir for the generic no-config case (e.g. macos / linux)
|
||||
SET (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../dist)
|
||||
|
||||
# Second, set output dir for multi-config builds (e.g. msvc)
|
||||
FOREACH (OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES})
|
||||
STRING (TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG)
|
||||
SET (CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${PROJECT_SOURCE_DIR}/../dist)
|
||||
ENDFOREACH (OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES)
|
||||
|
||||
# External code included with starbound source, which core depends on
|
||||
SET (STAR_EXTERN_INCLUDES ${PROJECT_SOURCE_DIR}/extern)
|
||||
ADD_SUBDIRECTORY (extern)
|
||||
|
||||
# Core support code, not specific to starbound.
|
||||
SET (STAR_CORE_INCLUDES ${PROJECT_SOURCE_DIR}/core)
|
||||
ADD_SUBDIRECTORY (core)
|
||||
|
||||
# Less general purpose code than core that is available to both the game and
|
||||
# application modules.
|
||||
SET (STAR_BASE_INCLUDES ${PROJECT_SOURCE_DIR}/base)
|
||||
ADD_SUBDIRECTORY (base)
|
||||
|
||||
# Platform APIs that are implemented by the application module
|
||||
SET (STAR_PLATFORM_INCLUDES ${PROJECT_SOURCE_DIR}/platform)
|
||||
ADD_SUBDIRECTORY (platform)
|
||||
|
||||
# Core game logic used by both server and client.
|
||||
SET (STAR_GAME_INCLUDES
|
||||
${PROJECT_SOURCE_DIR}/game
|
||||
${PROJECT_SOURCE_DIR}/game/interfaces
|
||||
${PROJECT_SOURCE_DIR}/game/items
|
||||
${PROJECT_SOURCE_DIR}/game/objects
|
||||
${PROJECT_SOURCE_DIR}/game/scripting
|
||||
${PROJECT_SOURCE_DIR}/game/terrain
|
||||
)
|
||||
ADD_SUBDIRECTORY (game)
|
||||
|
||||
# Googletest based tests
|
||||
ENABLE_TESTING()
|
||||
ADD_SUBDIRECTORY (test)
|
||||
|
||||
# Starbound stand-alone server.
|
||||
ADD_SUBDIRECTORY (server)
|
||||
|
||||
# cmdline utilities
|
||||
ADD_SUBDIRECTORY (utility)
|
||||
|
||||
IF (STAR_BUILD_GUI)
|
||||
# Handles creating windows, keyboard / mouse / joystick input, and the 2d
|
||||
# rendering model.
|
||||
SET (STAR_APPLICATION_INCLUDES ${PROJECT_SOURCE_DIR}/application)
|
||||
ADD_SUBDIRECTORY (application)
|
||||
|
||||
# Rendering code not dependent on widget system
|
||||
SET (STAR_RENDERING_INCLUDES ${PROJECT_SOURCE_DIR}/rendering)
|
||||
ADD_SUBDIRECTORY (rendering)
|
||||
|
||||
# Panes and Widgets
|
||||
SET (STAR_WINDOWING_INCLUDES ${PROJECT_SOURCE_DIR}/windowing)
|
||||
ADD_SUBDIRECTORY (windowing)
|
||||
|
||||
# Client interface code
|
||||
SET (STAR_FRONTEND_INCLUDES ${PROJECT_SOURCE_DIR}/frontend)
|
||||
ADD_SUBDIRECTORY (frontend)
|
||||
|
||||
# Starbound game / client
|
||||
ADD_SUBDIRECTORY (client)
|
||||
|
||||
# Qt GUI tools
|
||||
IF (STAR_BUILD_QT_TOOLS)
|
||||
ADD_SUBDIRECTORY (json_tool)
|
||||
|
||||
if (STAR_ENABLE_STEAM_INTEGRATION)
|
||||
ADD_SUBDIRECTORY (mod_uploader)
|
||||
ENDIF ()
|
||||
ENDIF ()
|
||||
ENDIF ()
|
||||
65
source/application/CMakeLists.txt
Normal file
65
source/application/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
INCLUDE_DIRECTORIES (
|
||||
${STAR_EXTERN_INCLUDES}
|
||||
${STAR_CORE_INCLUDES}
|
||||
${STAR_PLATFORM_INCLUDES}
|
||||
${STAR_APPLICATION_INCLUDES}
|
||||
)
|
||||
|
||||
SET (star_application_HEADERS
|
||||
StarApplication.hpp
|
||||
StarApplicationController.hpp
|
||||
StarMainApplication.hpp
|
||||
StarInputEvent.hpp
|
||||
StarRenderer.hpp
|
||||
)
|
||||
|
||||
SET (star_application_SOURCES
|
||||
StarApplication.cpp
|
||||
StarInputEvent.cpp
|
||||
StarRenderer.cpp
|
||||
)
|
||||
|
||||
SET (star_application_HEADERS ${star_application_HEADERS}
|
||||
StarP2PNetworkingService_pc.hpp
|
||||
StarPlatformServices_pc.hpp
|
||||
StarRenderer_opengl20.hpp
|
||||
)
|
||||
|
||||
SET (star_application_SOURCES ${star_application_SOURCES}
|
||||
StarMainApplication_sdl.cpp
|
||||
StarP2PNetworkingService_pc.cpp
|
||||
StarPlatformServices_pc.cpp
|
||||
StarRenderer_opengl20.cpp
|
||||
)
|
||||
|
||||
IF (STAR_ENABLE_STEAM_INTEGRATION)
|
||||
SET (star_application_HEADERS ${star_application_HEADERS}
|
||||
StarDesktopService_pc_steam.hpp
|
||||
StarStatisticsService_pc_steam.hpp
|
||||
StarUserGeneratedContentService_pc_steam.hpp
|
||||
)
|
||||
SET (star_application_SOURCES ${star_application_SOURCES}
|
||||
StarDesktopService_pc_steam.cpp
|
||||
StarStatisticsService_pc_steam.cpp
|
||||
StarUserGeneratedContentService_pc_steam.cpp
|
||||
)
|
||||
ENDIF ()
|
||||
|
||||
IF (STAR_ENABLE_DISCORD_INTEGRATION)
|
||||
SET (star_application_SOURCES ${star_application_SOURCES}
|
||||
discord/activity_manager.cpp
|
||||
discord/application_manager.cpp
|
||||
discord/core.cpp
|
||||
discord/image_manager.cpp
|
||||
discord/lobby_manager.cpp
|
||||
discord/network_manager.cpp
|
||||
discord/overlay_manager.cpp
|
||||
discord/relationship_manager.cpp
|
||||
discord/storage_manager.cpp
|
||||
discord/store_manager.cpp
|
||||
discord/types.cpp
|
||||
discord/user_manager.cpp
|
||||
)
|
||||
ENDIF ()
|
||||
|
||||
ADD_LIBRARY (star_application OBJECT ${star_application_SOURCES} ${star_application_HEADERS})
|
||||
31
source/application/StarApplication.cpp
Normal file
31
source/application/StarApplication.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#include "StarApplication.hpp"
|
||||
#include "StarTime.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
void Application::startup(StringList const&) {}
|
||||
|
||||
void Application::applicationInit(ApplicationControllerPtr appController) {
|
||||
m_appController = move(appController);
|
||||
}
|
||||
|
||||
void Application::renderInit(RendererPtr renderer) {
|
||||
m_renderer = move(renderer);
|
||||
}
|
||||
|
||||
void Application::windowChanged(WindowMode, Vec2U) {}
|
||||
|
||||
void Application::processInput(InputEvent const&) {}
|
||||
|
||||
void Application::update() {}
|
||||
|
||||
void Application::render() {}
|
||||
|
||||
void Application::getAudioData(int16_t* samples, size_t sampleCount) {
|
||||
for (size_t i = 0; i < sampleCount; ++i)
|
||||
samples[i] = 0;
|
||||
}
|
||||
|
||||
void Application::shutdown() {}
|
||||
}
|
||||
76
source/application/StarApplication.hpp
Normal file
76
source/application/StarApplication.hpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#ifndef STAR_APPLICATION_HPP
|
||||
#define STAR_APPLICATION_HPP
|
||||
|
||||
#include "StarInputEvent.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(ApplicationController);
|
||||
STAR_CLASS(Renderer);
|
||||
STAR_CLASS(Application);
|
||||
|
||||
STAR_EXCEPTION(ApplicationException, StarException);
|
||||
|
||||
enum class WindowMode {
|
||||
Normal,
|
||||
Maximized,
|
||||
Fullscreen,
|
||||
Borderless
|
||||
};
|
||||
|
||||
class Application {
|
||||
public:
|
||||
virtual ~Application() = default;
|
||||
|
||||
// Called once on application startup, before any other methods.
|
||||
virtual void startup(StringList const& cmdLineArgs);
|
||||
|
||||
// Called on application initialization, before rendering initialization. If
|
||||
// overriden, must call base class instance.
|
||||
virtual void applicationInit(ApplicationControllerPtr appController);
|
||||
|
||||
// Called immediately after application initialization on startup, and then
|
||||
// also whenever the renderer invalidated and recreated. If overridden, must
|
||||
// call base class instance.
|
||||
virtual void renderInit(RendererPtr renderer);
|
||||
|
||||
// Called when the window mode or size is changed.
|
||||
virtual void windowChanged(WindowMode windowMode, Vec2U screenSize);
|
||||
|
||||
// Called before update, once for every pending event.
|
||||
virtual void processInput(InputEvent const& event);
|
||||
|
||||
// Will be called at updateRate hz, or as close as possible.
|
||||
virtual void update();
|
||||
|
||||
// Will be called at updateRate hz, or more or less depending on settings and
|
||||
// performance. update() is always prioritized over render().
|
||||
virtual void render();
|
||||
|
||||
// Will be called *from a different thread* to retrieve audio data (if audio
|
||||
// is playing). Default implementation simply fills the buffer with silence.
|
||||
virtual void getAudioData(int16_t* sampleData, size_t frameCount);
|
||||
|
||||
// Will be called once on application shutdown, including when shutting down
|
||||
// due to an Application exception.
|
||||
virtual void shutdown();
|
||||
|
||||
ApplicationControllerPtr const& appController() const;
|
||||
RendererPtr const& renderer() const;
|
||||
|
||||
private:
|
||||
ApplicationControllerPtr m_appController;
|
||||
RendererPtr m_renderer;
|
||||
};
|
||||
|
||||
inline ApplicationControllerPtr const& Application::appController() const {
|
||||
return m_appController;
|
||||
}
|
||||
|
||||
inline RendererPtr const& Application::renderer() const {
|
||||
return m_renderer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
67
source/application/StarApplicationController.hpp
Normal file
67
source/application/StarApplicationController.hpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#ifndef STAR_APPLICATION_CONTROLLER_HPP
|
||||
#define STAR_APPLICATION_CONTROLLER_HPP
|
||||
|
||||
#include "StarApplication.hpp"
|
||||
#include "StarStatisticsService.hpp"
|
||||
#include "StarP2PNetworkingService.hpp"
|
||||
#include "StarUserGeneratedContentService.hpp"
|
||||
#include "StarDesktopService.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(ApplicationController);
|
||||
|
||||
// Audio format is always 16 bit signed integer samples
|
||||
struct AudioFormat {
|
||||
unsigned sampleRate;
|
||||
unsigned channels;
|
||||
};
|
||||
|
||||
// Window size defaults to 800x600, target update rate to 60hz, maximized and
|
||||
// fullscreen are false, vsync is on, the cursor is visible, and audio and text
|
||||
// input are disabled.
|
||||
class ApplicationController {
|
||||
public:
|
||||
virtual ~ApplicationController() = default;
|
||||
|
||||
// Target hz at which update() will be called
|
||||
virtual void setTargetUpdateRate(float targetUpdateRate) = 0;
|
||||
// Window that controls how long the update rate will be increased or
|
||||
// decreased to make up for rate errors in the past.
|
||||
virtual void setUpdateTrackWindow(float updateTrackWindow) = 0;
|
||||
// Maximum number of calls to update() that can occur before we force
|
||||
// 'render()' to be called, even if we are still behind on our update rate.
|
||||
virtual void setMaxFrameSkip(unsigned maxFrameSkip) = 0;
|
||||
|
||||
virtual void setApplicationTitle(String title) = 0;
|
||||
virtual void setFullscreenWindow(Vec2U fullScreenResolution) = 0;
|
||||
virtual void setNormalWindow(Vec2U windowSize) = 0;
|
||||
virtual void setMaximizedWindow() = 0;
|
||||
virtual void setBorderlessWindow() = 0;
|
||||
virtual void setVSyncEnabled(bool vSync) = 0;
|
||||
virtual void setCursorVisible(bool cursorVisible) = 0;
|
||||
virtual void setAcceptingTextInput(bool acceptingTextInput) = 0;
|
||||
|
||||
virtual AudioFormat enableAudio() = 0;
|
||||
virtual void disableAudio() = 0;
|
||||
|
||||
virtual void setClipboard(String text) = 0;
|
||||
virtual Maybe<String> getClipboard() = 0;
|
||||
|
||||
// Returns the latest actual measured update and render rate, which may be
|
||||
// different than the target update rate.
|
||||
virtual float updateRate() const = 0;
|
||||
virtual float renderFps() const = 0;
|
||||
|
||||
virtual StatisticsServicePtr statisticsService() const = 0;
|
||||
virtual P2PNetworkingServicePtr p2pNetworkingService() const = 0;
|
||||
virtual UserGeneratedContentServicePtr userGeneratedContentService() const = 0;
|
||||
virtual DesktopServicePtr desktopService() const = 0;
|
||||
|
||||
// Signals the application to quit
|
||||
virtual void quit() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
11
source/application/StarDesktopService_pc_steam.cpp
Normal file
11
source/application/StarDesktopService_pc_steam.cpp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#include "StarDesktopService_pc_steam.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
SteamDesktopService::SteamDesktopService(PcPlatformServicesStatePtr) {}
|
||||
|
||||
void SteamDesktopService::openUrl(String const& url) {
|
||||
SteamFriends()->ActivateGameOverlayToWebPage(url.utf8Ptr());
|
||||
}
|
||||
|
||||
}
|
||||
19
source/application/StarDesktopService_pc_steam.hpp
Normal file
19
source/application/StarDesktopService_pc_steam.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef STAR_DESKTOP_SERVICE_PC_STEAM_HPP
|
||||
#define STAR_DESKTOP_SERVICE_PC_STEAM_HPP
|
||||
|
||||
#include "StarPlatformServices_pc.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(SteamDesktopService);
|
||||
|
||||
class SteamDesktopService : public DesktopService {
|
||||
public:
|
||||
SteamDesktopService(PcPlatformServicesStatePtr state);
|
||||
|
||||
void openUrl(String const& url) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
166
source/application/StarInputEvent.cpp
Normal file
166
source/application/StarInputEvent.cpp
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
#include "StarInputEvent.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
EnumMap<Key> const KeyNames{
|
||||
{Key::Backspace, "Backspace"},
|
||||
{Key::Tab, "Tab"},
|
||||
{Key::Clear, "Clear"},
|
||||
{Key::Return, "Return"},
|
||||
{Key::Escape, "Esc"},
|
||||
{Key::Space, "Space"},
|
||||
{Key::Exclaim, "!"},
|
||||
{Key::QuotedBL, "\""},
|
||||
{Key::Hash, "#"},
|
||||
{Key::Dollar, "$"},
|
||||
{Key::Ampersand, "&"},
|
||||
{Key::Quote, "\'"},
|
||||
{Key::LeftParen, "("},
|
||||
{Key::RightParen, ")"},
|
||||
{Key::Asterisk, "*"},
|
||||
{Key::Plus, "+"},
|
||||
{Key::Comma, ","},
|
||||
{Key::Minus, "-"},
|
||||
{Key::Period, "."},
|
||||
{Key::Slash, "/"},
|
||||
{Key::Zero, "0"},
|
||||
{Key::One, "1"},
|
||||
{Key::Two, "2"},
|
||||
{Key::Three, "3"},
|
||||
{Key::Four, "4"},
|
||||
{Key::Five, "5"},
|
||||
{Key::Six, "6"},
|
||||
{Key::Seven, "7"},
|
||||
{Key::Eight, "8"},
|
||||
{Key::Nine, "9"},
|
||||
{Key::Colon, ":"},
|
||||
{Key::Semicolon, ";"},
|
||||
{Key::Less, "<"},
|
||||
{Key::Equals, "="},
|
||||
{Key::Greater, ">"},
|
||||
{Key::Question, "?"},
|
||||
{Key::At, "@"},
|
||||
{Key::LeftBracket, "["},
|
||||
{Key::Backslash, "\\"},
|
||||
{Key::RightBracket, "]"},
|
||||
{Key::Caret, "^"},
|
||||
{Key::Underscore, "_"},
|
||||
{Key::Backquote, "`"},
|
||||
{Key::A, "A"},
|
||||
{Key::B, "B"},
|
||||
{Key::C, "C"},
|
||||
{Key::D, "D"},
|
||||
{Key::E, "E"},
|
||||
{Key::F, "F"},
|
||||
{Key::G, "G"},
|
||||
{Key::H, "H"},
|
||||
{Key::I, "I"},
|
||||
{Key::J, "J"},
|
||||
{Key::K, "K"},
|
||||
{Key::L, "L"},
|
||||
{Key::M, "M"},
|
||||
{Key::N, "N"},
|
||||
{Key::O, "O"},
|
||||
{Key::P, "P"},
|
||||
{Key::Q, "Q"},
|
||||
{Key::R, "R"},
|
||||
{Key::S, "S"},
|
||||
{Key::T, "T"},
|
||||
{Key::U, "U"},
|
||||
{Key::V, "V"},
|
||||
{Key::W, "W"},
|
||||
{Key::X, "X"},
|
||||
{Key::Y, "Y"},
|
||||
{Key::Z, "Z"},
|
||||
{Key::Delete, "Del"},
|
||||
{Key::Kp0, "Kp0"},
|
||||
{Key::Kp1, "Kp1"},
|
||||
{Key::Kp2, "Kp2"},
|
||||
{Key::Kp3, "Kp3"},
|
||||
{Key::Kp4, "Kp4"},
|
||||
{Key::Kp5, "Kp5"},
|
||||
{Key::Kp6, "Kp6"},
|
||||
{Key::Kp7, "Kp7"},
|
||||
{Key::Kp8, "Kp8"},
|
||||
{Key::Kp9, "Kp9"},
|
||||
{Key::Kp_period, "Kp_period"},
|
||||
{Key::Kp_divide, "Kp_divide"},
|
||||
{Key::Kp_multiply, "Kp_multiply"},
|
||||
{Key::Kp_minus, "Kp_minus"},
|
||||
{Key::Kp_plus, "Kp_plus"},
|
||||
{Key::Kp_enter, "Kp_enter"},
|
||||
{Key::Kp_equals, "Kp_equals"},
|
||||
{Key::Up, "Up"},
|
||||
{Key::Down, "Down"},
|
||||
{Key::Right, "Right"},
|
||||
{Key::Left, "Left"},
|
||||
{Key::Insert, "Ins"},
|
||||
{Key::Home, "Home"},
|
||||
{Key::End, "End"},
|
||||
{Key::PageUp, "PageUp"},
|
||||
{Key::PageDown, "PageDown"},
|
||||
{Key::F1, "F1"},
|
||||
{Key::F2, "F2"},
|
||||
{Key::F3, "F3"},
|
||||
{Key::F4, "F4"},
|
||||
{Key::F5, "F5"},
|
||||
{Key::F6, "F6"},
|
||||
{Key::F7, "F7"},
|
||||
{Key::F8, "F8"},
|
||||
{Key::F9, "F9"},
|
||||
{Key::F10, "F10"},
|
||||
{Key::F11, "F11"},
|
||||
{Key::F12, "F12"},
|
||||
{Key::F13, "F13"},
|
||||
{Key::F14, "F14"},
|
||||
{Key::F15, "F15"},
|
||||
{Key::NumLock, "NumLock"},
|
||||
{Key::CapsLock, "CapsLock"},
|
||||
{Key::ScrollLock, "ScrollLock"},
|
||||
{Key::RShift, "RShift"},
|
||||
{Key::LShift, "LShift"},
|
||||
{Key::RCtrl, "RCtrl"},
|
||||
{Key::LCtrl, "LCtrl"},
|
||||
{Key::RAlt, "RAlt"},
|
||||
{Key::LAlt, "LAlt"},
|
||||
{Key::RGui, "RGui"},
|
||||
{Key::LGui, "LGui"},
|
||||
{Key::AltGr, "AltGr"},
|
||||
{Key::Compose, "Compose"},
|
||||
{Key::Help, "Help"},
|
||||
{Key::PrintScreen, "PrintScreen"},
|
||||
{Key::SysReq, "SysReq"},
|
||||
{Key::Pause, "Pause"},
|
||||
{Key::Menu, "Menu"},
|
||||
{Key::Power, "Power"}
|
||||
};
|
||||
|
||||
EnumMap<KeyMod> const KeyModNames{
|
||||
{KeyMod::NoMod, "NoMod"},
|
||||
{KeyMod::LShift, "LShift"},
|
||||
{KeyMod::RShift, "RShift"},
|
||||
{KeyMod::LCtrl, "LCtrl"},
|
||||
{KeyMod::RCtrl, "RCtrl"},
|
||||
{KeyMod::LAlt, "LAlt"},
|
||||
{KeyMod::RAlt, "RAlt"},
|
||||
{KeyMod::LGui, "LMeta"},
|
||||
{KeyMod::RGui, "RMeta"},
|
||||
{KeyMod::Num, "Num"},
|
||||
{KeyMod::Caps, "Caps"},
|
||||
{KeyMod::AltGr, "AltGr"}
|
||||
};
|
||||
|
||||
EnumMap<MouseButton> const MouseButtonNames{
|
||||
{MouseButton::Left, "MouseLeft"},
|
||||
{MouseButton::Middle, "MouseMiddle"},
|
||||
{MouseButton::Right, "MouseRight"},
|
||||
{MouseButton::FourthButton, "MouseFourth"},
|
||||
{MouseButton::FifthButton, "MouseFifth"}
|
||||
};
|
||||
|
||||
EnumMap<MouseWheel> const MouseWheelNames{
|
||||
{MouseWheel::Up, "MouseWheelUp"},
|
||||
{MouseWheel::Down, "MouseWheelDown"}
|
||||
};
|
||||
|
||||
}
|
||||
274
source/application/StarInputEvent.hpp
Normal file
274
source/application/StarInputEvent.hpp
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
#ifndef STAR_INPUT_EVENT_HPP
|
||||
#define STAR_INPUT_EVENT_HPP
|
||||
|
||||
#include "StarString.hpp"
|
||||
#include "StarBiMap.hpp"
|
||||
#include "StarVariant.hpp"
|
||||
#include "StarVector.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
enum class Key : uint16_t {
|
||||
Backspace,
|
||||
Tab,
|
||||
Clear,
|
||||
Return,
|
||||
Escape,
|
||||
Space,
|
||||
Exclaim,
|
||||
QuotedBL,
|
||||
Hash,
|
||||
Dollar,
|
||||
Ampersand,
|
||||
Quote,
|
||||
LeftParen,
|
||||
RightParen,
|
||||
Asterisk,
|
||||
Plus,
|
||||
Comma,
|
||||
Minus,
|
||||
Period,
|
||||
Slash,
|
||||
Zero,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
Five,
|
||||
Six,
|
||||
Seven,
|
||||
Eight,
|
||||
Nine,
|
||||
Colon,
|
||||
Semicolon,
|
||||
Less,
|
||||
Equals,
|
||||
Greater,
|
||||
Question,
|
||||
At,
|
||||
LeftBracket,
|
||||
Backslash,
|
||||
RightBracket,
|
||||
Caret,
|
||||
Underscore,
|
||||
Backquote,
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
Delete,
|
||||
Kp0,
|
||||
Kp1,
|
||||
Kp2,
|
||||
Kp3,
|
||||
Kp4,
|
||||
Kp5,
|
||||
Kp6,
|
||||
Kp7,
|
||||
Kp8,
|
||||
Kp9,
|
||||
Kp_period,
|
||||
Kp_divide,
|
||||
Kp_multiply,
|
||||
Kp_minus,
|
||||
Kp_plus,
|
||||
Kp_enter,
|
||||
Kp_equals,
|
||||
Up,
|
||||
Down,
|
||||
Right,
|
||||
Left,
|
||||
Insert,
|
||||
Home,
|
||||
End,
|
||||
PageUp,
|
||||
PageDown,
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
F13,
|
||||
F14,
|
||||
F15,
|
||||
NumLock,
|
||||
CapsLock,
|
||||
ScrollLock,
|
||||
RShift,
|
||||
LShift,
|
||||
RCtrl,
|
||||
LCtrl,
|
||||
RAlt,
|
||||
LAlt,
|
||||
RGui,
|
||||
LGui,
|
||||
AltGr,
|
||||
Compose,
|
||||
Help,
|
||||
PrintScreen,
|
||||
SysReq,
|
||||
Pause,
|
||||
Menu,
|
||||
Power
|
||||
};
|
||||
extern EnumMap<Key> const KeyNames;
|
||||
|
||||
enum class KeyMod : uint16_t {
|
||||
NoMod = 0x0000,
|
||||
LShift = 0x0001,
|
||||
RShift = 0x0002,
|
||||
LCtrl = 0x0040,
|
||||
RCtrl = 0x0080,
|
||||
LAlt = 0x0100,
|
||||
RAlt = 0x0200,
|
||||
LGui = 0x0400,
|
||||
RGui = 0x0800,
|
||||
Num = 0x1000,
|
||||
Caps = 0x2000,
|
||||
AltGr = 0x4000
|
||||
};
|
||||
extern EnumMap<KeyMod> const KeyModNames;
|
||||
|
||||
KeyMod operator|(KeyMod a, KeyMod b);
|
||||
KeyMod operator&(KeyMod a, KeyMod b);
|
||||
KeyMod operator~(KeyMod a);
|
||||
KeyMod& operator|=(KeyMod& a, KeyMod b);
|
||||
KeyMod& operator&=(KeyMod& a, KeyMod b);
|
||||
|
||||
enum class MouseButton : uint8_t {
|
||||
Left,
|
||||
Middle,
|
||||
Right,
|
||||
FourthButton,
|
||||
FifthButton
|
||||
};
|
||||
extern EnumMap<MouseButton> const MouseButtonNames;
|
||||
|
||||
enum class MouseWheel : uint8_t {
|
||||
Up,
|
||||
Down
|
||||
};
|
||||
extern EnumMap<MouseWheel> const MouseWheelNames;
|
||||
|
||||
typedef uint32_t ControllerId;
|
||||
|
||||
struct KeyDownEvent {
|
||||
Key key;
|
||||
KeyMod mods;
|
||||
};
|
||||
|
||||
struct KeyUpEvent {
|
||||
Key key;
|
||||
};
|
||||
|
||||
struct TextInputEvent {
|
||||
String text;
|
||||
};
|
||||
|
||||
struct MouseMoveEvent {
|
||||
Vec2I mouseMove;
|
||||
Vec2I mousePosition;
|
||||
};
|
||||
|
||||
struct MouseButtonDownEvent {
|
||||
MouseButton mouseButton;
|
||||
Vec2I mousePosition;
|
||||
};
|
||||
|
||||
struct MouseButtonUpEvent {
|
||||
MouseButton mouseButton;
|
||||
Vec2I mousePosition;
|
||||
};
|
||||
|
||||
struct MouseWheelEvent {
|
||||
MouseWheel mouseWheel;
|
||||
Vec2I mousePosition;
|
||||
};
|
||||
|
||||
struct ControllerAxisEvent {
|
||||
ControllerId controller;
|
||||
unsigned controllerAxis;
|
||||
float controllerAxisValue;
|
||||
};
|
||||
|
||||
struct ControllerButtonDownEvent {
|
||||
ControllerId controller;
|
||||
unsigned controllerButton;
|
||||
};
|
||||
|
||||
struct ControllerButtonUpEvent {
|
||||
ControllerId controller;
|
||||
unsigned controllerButton;
|
||||
};
|
||||
|
||||
typedef Variant<
|
||||
KeyDownEvent,
|
||||
KeyUpEvent,
|
||||
TextInputEvent,
|
||||
MouseMoveEvent,
|
||||
MouseButtonDownEvent,
|
||||
MouseButtonUpEvent,
|
||||
MouseWheelEvent,
|
||||
ControllerAxisEvent,
|
||||
ControllerButtonDownEvent,
|
||||
ControllerButtonUpEvent>
|
||||
InputEvent;
|
||||
|
||||
inline KeyMod operator|(KeyMod a, KeyMod b) {
|
||||
return (KeyMod)((uint16_t)a | (uint16_t)b);
|
||||
}
|
||||
|
||||
inline KeyMod operator&(KeyMod a, KeyMod b) {
|
||||
return (KeyMod)((uint16_t)a & (uint16_t)b);
|
||||
}
|
||||
|
||||
inline KeyMod operator~(KeyMod a) {
|
||||
return (KeyMod) ~(uint16_t)a;
|
||||
}
|
||||
|
||||
inline KeyMod& operator|=(KeyMod& a, KeyMod b) {
|
||||
uint16_t a_cast = (uint16_t)a;
|
||||
a_cast |= (uint16_t)b;
|
||||
a = (KeyMod)a_cast;
|
||||
return a;
|
||||
}
|
||||
|
||||
inline KeyMod& operator&=(KeyMod& a, KeyMod b) {
|
||||
uint16_t a_cast = (uint16_t)a;
|
||||
a_cast &= (uint16_t)b;
|
||||
a = (KeyMod)a_cast;
|
||||
return a;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
34
source/application/StarMainApplication.hpp
Normal file
34
source/application/StarMainApplication.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef STAR_MAIN_APPLICATION_HPP
|
||||
#define STAR_MAIN_APPLICATION_HPP
|
||||
|
||||
#include "StarApplication.hpp"
|
||||
#include "StarApplicationController.hpp"
|
||||
#include "StarRenderer.hpp"
|
||||
|
||||
namespace Star {
|
||||
int runMainApplication(ApplicationUPtr application, StringList cmdLineArgs);
|
||||
}
|
||||
|
||||
#if defined STAR_SYSTEM_WINDOWS
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#define STAR_MAIN_APPLICATION(ApplicationClass) \
|
||||
int __stdcall WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { \
|
||||
int nArgs; \
|
||||
LPWSTR* argsList = CommandLineToArgvW(GetCommandLineW(), &nArgs); \
|
||||
Star::StringList args; \
|
||||
for (int i = 0; i < nArgs; ++i) args.append(Star::String(argsList[i])); \
|
||||
return Star::runMainApplication(Star::make_unique<ApplicationClass>(), args); \
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define STAR_MAIN_APPLICATION(ApplicationClass) \
|
||||
int main(int argc, char** argv) { \
|
||||
return Star::runMainApplication(Star::make_unique<ApplicationClass>(), Star::StringList(argc, argv)); \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
692
source/application/StarMainApplication_sdl.cpp
Normal file
692
source/application/StarMainApplication_sdl.cpp
Normal file
|
|
@ -0,0 +1,692 @@
|
|||
#include "StarMainApplication.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarSignalHandler.hpp"
|
||||
#include "StarTickRateMonitor.hpp"
|
||||
#include "StarRenderer_opengl20.hpp"
|
||||
|
||||
#include "SDL.h"
|
||||
#include "StarPlatformServices_pc.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
Maybe<Key> keyFromSdlKeyCode(SDL_Keycode sym) {
|
||||
static HashMap<int, Key> KeyCodeMap{
|
||||
{SDLK_BACKSPACE, Key::Backspace},
|
||||
{SDLK_TAB, Key::Tab},
|
||||
{SDLK_CLEAR, Key::Clear},
|
||||
{SDLK_RETURN, Key::Return},
|
||||
{SDLK_PAUSE, Key::Pause},
|
||||
{SDLK_ESCAPE, Key::Escape},
|
||||
{SDLK_SPACE, Key::Space},
|
||||
{SDLK_EXCLAIM, Key::Exclaim},
|
||||
{SDLK_QUOTEDBL, Key::QuotedBL},
|
||||
{SDLK_HASH, Key::Hash},
|
||||
{SDLK_DOLLAR, Key::Dollar},
|
||||
{SDLK_AMPERSAND, Key::Ampersand},
|
||||
{SDLK_QUOTE, Key::Quote},
|
||||
{SDLK_LEFTPAREN, Key::LeftParen},
|
||||
{SDLK_RIGHTPAREN, Key::RightParen},
|
||||
{SDLK_ASTERISK, Key::Asterisk},
|
||||
{SDLK_PLUS, Key::Plus},
|
||||
{SDLK_COMMA, Key::Comma},
|
||||
{SDLK_MINUS, Key::Minus},
|
||||
{SDLK_PERIOD, Key::Period},
|
||||
{SDLK_SLASH, Key::Slash},
|
||||
{SDLK_0, Key::Zero},
|
||||
{SDLK_1, Key::One},
|
||||
{SDLK_2, Key::Two},
|
||||
{SDLK_3, Key::Three},
|
||||
{SDLK_4, Key::Four},
|
||||
{SDLK_5, Key::Five},
|
||||
{SDLK_6, Key::Six},
|
||||
{SDLK_7, Key::Seven},
|
||||
{SDLK_8, Key::Eight},
|
||||
{SDLK_9, Key::Nine},
|
||||
{SDLK_COLON, Key::Colon},
|
||||
{SDLK_SEMICOLON, Key::Semicolon},
|
||||
{SDLK_LESS, Key::Less},
|
||||
{SDLK_EQUALS, Key::Equals},
|
||||
{SDLK_GREATER, Key::Greater},
|
||||
{SDLK_QUESTION, Key::Question},
|
||||
{SDLK_AT, Key::At},
|
||||
{SDLK_LEFTBRACKET, Key::LeftBracket},
|
||||
{SDLK_BACKSLASH, Key::Backslash},
|
||||
{SDLK_RIGHTBRACKET, Key::RightBracket},
|
||||
{SDLK_CARET, Key::Caret},
|
||||
{SDLK_UNDERSCORE, Key::Underscore},
|
||||
{SDLK_BACKQUOTE, Key::Backquote},
|
||||
{SDLK_a, Key::A},
|
||||
{SDLK_b, Key::B},
|
||||
{SDLK_c, Key::C},
|
||||
{SDLK_d, Key::D},
|
||||
{SDLK_e, Key::E},
|
||||
{SDLK_f, Key::F},
|
||||
{SDLK_g, Key::G},
|
||||
{SDLK_h, Key::H},
|
||||
{SDLK_i, Key::I},
|
||||
{SDLK_j, Key::J},
|
||||
{SDLK_k, Key::K},
|
||||
{SDLK_l, Key::L},
|
||||
{SDLK_m, Key::M},
|
||||
{SDLK_n, Key::N},
|
||||
{SDLK_o, Key::O},
|
||||
{SDLK_p, Key::P},
|
||||
{SDLK_q, Key::Q},
|
||||
{SDLK_r, Key::R},
|
||||
{SDLK_s, Key::S},
|
||||
{SDLK_t, Key::T},
|
||||
{SDLK_u, Key::U},
|
||||
{SDLK_v, Key::V},
|
||||
{SDLK_w, Key::W},
|
||||
{SDLK_x, Key::X},
|
||||
{SDLK_y, Key::Y},
|
||||
{SDLK_z, Key::Z},
|
||||
{SDLK_DELETE, Key::Delete},
|
||||
{SDLK_KP_0, Key::Kp0},
|
||||
{SDLK_KP_1, Key::Kp1},
|
||||
{SDLK_KP_2, Key::Kp2},
|
||||
{SDLK_KP_3, Key::Kp3},
|
||||
{SDLK_KP_4, Key::Kp4},
|
||||
{SDLK_KP_5, Key::Kp5},
|
||||
{SDLK_KP_6, Key::Kp6},
|
||||
{SDLK_KP_7, Key::Kp7},
|
||||
{SDLK_KP_8, Key::Kp8},
|
||||
{SDLK_KP_9, Key::Kp9},
|
||||
{SDLK_KP_PERIOD, Key::Kp_period},
|
||||
{SDLK_KP_DIVIDE, Key::Kp_divide},
|
||||
{SDLK_KP_MULTIPLY, Key::Kp_multiply},
|
||||
{SDLK_KP_MINUS, Key::Kp_minus},
|
||||
{SDLK_KP_PLUS, Key::Kp_plus},
|
||||
{SDLK_KP_ENTER, Key::Kp_enter},
|
||||
{SDLK_KP_EQUALS, Key::Kp_equals},
|
||||
{SDLK_UP, Key::Up},
|
||||
{SDLK_DOWN, Key::Down},
|
||||
{SDLK_RIGHT, Key::Right},
|
||||
{SDLK_LEFT, Key::Left},
|
||||
{SDLK_INSERT, Key::Insert},
|
||||
{SDLK_HOME, Key::Home},
|
||||
{SDLK_END, Key::End},
|
||||
{SDLK_PAGEUP, Key::PageUp},
|
||||
{SDLK_PAGEDOWN, Key::PageDown},
|
||||
{SDLK_F1, Key::F1},
|
||||
{SDLK_F2, Key::F2},
|
||||
{SDLK_F3, Key::F3},
|
||||
{SDLK_F4, Key::F4},
|
||||
{SDLK_F5, Key::F5},
|
||||
{SDLK_F6, Key::F6},
|
||||
{SDLK_F7, Key::F7},
|
||||
{SDLK_F8, Key::F8},
|
||||
{SDLK_F9, Key::F9},
|
||||
{SDLK_F10, Key::F10},
|
||||
{SDLK_F11, Key::F11},
|
||||
{SDLK_F12, Key::F12},
|
||||
{SDLK_F13, Key::F13},
|
||||
{SDLK_F14, Key::F14},
|
||||
{SDLK_F15, Key::F15},
|
||||
{SDLK_NUMLOCKCLEAR, Key::NumLock},
|
||||
{SDLK_CAPSLOCK, Key::CapsLock},
|
||||
{SDLK_SCROLLLOCK, Key::ScrollLock},
|
||||
{SDLK_RSHIFT, Key::RShift},
|
||||
{SDLK_LSHIFT, Key::LShift},
|
||||
{SDLK_RCTRL, Key::RCtrl},
|
||||
{SDLK_LCTRL, Key::LCtrl},
|
||||
{SDLK_RALT, Key::RAlt},
|
||||
{SDLK_LALT, Key::LAlt},
|
||||
{SDLK_RGUI, Key::RGui},
|
||||
{SDLK_LGUI, Key::LGui},
|
||||
{SDLK_MODE, Key::AltGr},
|
||||
{SDLK_APPLICATION, Key::Compose},
|
||||
{SDLK_HELP, Key::Help},
|
||||
{SDLK_PRINTSCREEN, Key::PrintScreen},
|
||||
{SDLK_SYSREQ, Key::SysReq},
|
||||
{SDLK_PAUSE, Key::Pause},
|
||||
{SDLK_MENU, Key::Menu},
|
||||
{SDLK_POWER, Key::Power}
|
||||
};
|
||||
|
||||
return KeyCodeMap.maybe(sym);
|
||||
}
|
||||
|
||||
KeyMod keyModsFromSdlKeyMods(uint16_t mod) {
|
||||
KeyMod keyMod = KeyMod::NoMod;
|
||||
|
||||
if (mod & KMOD_LSHIFT)
|
||||
keyMod |= KeyMod::LShift;
|
||||
if (mod & KMOD_RSHIFT)
|
||||
keyMod |= KeyMod::RShift;
|
||||
if (mod & KMOD_LCTRL)
|
||||
keyMod |= KeyMod::LCtrl;
|
||||
if (mod & KMOD_RCTRL)
|
||||
keyMod |= KeyMod::RCtrl;
|
||||
if (mod & KMOD_LALT)
|
||||
keyMod |= KeyMod::LAlt;
|
||||
if (mod & KMOD_RALT)
|
||||
keyMod |= KeyMod::RAlt;
|
||||
if (mod & KMOD_LGUI)
|
||||
keyMod |= KeyMod::LGui;
|
||||
if (mod & KMOD_RGUI)
|
||||
keyMod |= KeyMod::RGui;
|
||||
if (mod & KMOD_NUM)
|
||||
keyMod |= KeyMod::Num;
|
||||
if (mod & KMOD_CAPS)
|
||||
keyMod |= KeyMod::Caps;
|
||||
if (mod & KMOD_MODE)
|
||||
keyMod |= KeyMod::AltGr;
|
||||
|
||||
return keyMod;
|
||||
}
|
||||
|
||||
MouseButton mouseButtonFromSdlMouseButton(uint8_t button) {
|
||||
if (button == SDL_BUTTON_LEFT)
|
||||
return MouseButton::Left;
|
||||
else if (button == SDL_BUTTON_MIDDLE)
|
||||
return MouseButton::Middle;
|
||||
else if (button == SDL_BUTTON_RIGHT)
|
||||
return MouseButton::Right;
|
||||
else if (button == SDL_BUTTON_X1)
|
||||
return MouseButton::FourthButton;
|
||||
else
|
||||
return MouseButton::FifthButton;
|
||||
}
|
||||
|
||||
class SdlPlatform {
|
||||
public:
|
||||
SdlPlatform(ApplicationUPtr application, StringList cmdLineArgs) {
|
||||
m_application = move(application);
|
||||
|
||||
// extract application path from command line args
|
||||
String applicationPath = cmdLineArgs.first();
|
||||
cmdLineArgs = cmdLineArgs.slice(1);
|
||||
|
||||
StringList platformArguments;
|
||||
eraseWhere(cmdLineArgs, [&platformArguments](String& argument) {
|
||||
if (argument.beginsWith("+platform")) {
|
||||
platformArguments.append(move(argument));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
Logger::info("Application: Initializing SDL");
|
||||
if (SDL_Init(0))
|
||||
throw ApplicationException(strf("Couldn't initialize SDL: %s", SDL_GetError()));
|
||||
|
||||
if (char* basePath = SDL_GetBasePath()) {
|
||||
File::changeDirectory(basePath);
|
||||
SDL_free(basePath);
|
||||
}
|
||||
|
||||
m_signalHandler.setHandleInterrupt(true);
|
||||
m_signalHandler.setHandleFatal(true);
|
||||
|
||||
try {
|
||||
Logger::info("Application: startup...");
|
||||
m_application->startup(cmdLineArgs);
|
||||
} catch (std::exception const& e) {
|
||||
throw ApplicationException("Application threw exception during startup", e);
|
||||
}
|
||||
|
||||
Logger::info("Application: Initializing SDL Video");
|
||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO))
|
||||
throw ApplicationException(strf("Couldn't initialize SDL Video: %s", SDL_GetError()));
|
||||
|
||||
Logger::info("Application: Initializing SDL Joystick");
|
||||
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK))
|
||||
throw ApplicationException(strf("Couldn't initialize SDL Joystick: %s", SDL_GetError()));
|
||||
|
||||
Logger::info("Application: Initializing SDL Sound");
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO))
|
||||
throw ApplicationException(strf("Couldn't initialize SDL Sound: %s", SDL_GetError()));
|
||||
|
||||
SDL_JoystickEventState(SDL_ENABLE);
|
||||
|
||||
m_platformServices = PcPlatformServices::create(applicationPath, platformArguments);
|
||||
if (!m_platformServices)
|
||||
Logger::info("Application: No platform services available");
|
||||
|
||||
Logger::info("Application: Creating SDL Window");
|
||||
m_sdlWindow = SDL_CreateWindow(m_windowTitle.utf8Ptr(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||
m_windowSize[0], m_windowSize[1], SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
|
||||
if (!m_sdlWindow)
|
||||
throw ApplicationException::format("Application: Could not create SDL window: %s", SDL_GetError());
|
||||
|
||||
SDL_ShowWindow(m_sdlWindow);
|
||||
SDL_RaiseWindow(m_sdlWindow);
|
||||
|
||||
int width;
|
||||
int height;
|
||||
SDL_GetWindowSize(m_sdlWindow, &width, &height);
|
||||
m_windowSize = Vec2U(width, height);
|
||||
|
||||
m_sdlGlContext = SDL_GL_CreateContext(m_sdlWindow);
|
||||
if (!m_sdlGlContext)
|
||||
throw ApplicationException::format("Application: Could not create OpenGL context: %s", SDL_GetError());
|
||||
|
||||
setVSyncEnabled(m_windowVSync);
|
||||
|
||||
SDL_StopTextInput();
|
||||
|
||||
SDL_AudioSpec desired = {};
|
||||
desired.freq = 44100;
|
||||
desired.format = AUDIO_S16SYS;
|
||||
desired.samples = 2048;
|
||||
desired.channels = 2;
|
||||
desired.userdata = this;
|
||||
desired.callback = [](void* userdata, Uint8* stream, int len) {
|
||||
((SdlPlatform*)(userdata))->getAudioData(stream, len);
|
||||
};
|
||||
|
||||
SDL_AudioSpec obtained = {};
|
||||
if (SDL_OpenAudio(&desired, &obtained) < 0) {
|
||||
Logger::error("Application: Could not open audio device, no sound available!");
|
||||
} else if (obtained.freq != desired.freq || obtained.channels != desired.channels || obtained.format != desired.format) {
|
||||
SDL_CloseAudio();
|
||||
Logger::error("Application: Could not open 44.1khz / 16 bit stereo audio device, no sound available!");
|
||||
} else {
|
||||
Logger::info("Application: Opened default audio device with 44.1khz / 16 bit stereo audio, %s sample size buffer", obtained.samples);
|
||||
}
|
||||
|
||||
m_renderer = make_shared<OpenGl20Renderer>();
|
||||
m_renderer->setScreenSize(m_windowSize);
|
||||
}
|
||||
|
||||
~SdlPlatform() {
|
||||
SDL_CloseAudio();
|
||||
|
||||
m_renderer.reset();
|
||||
|
||||
Logger::info("Application: Destroying SDL Window");
|
||||
SDL_DestroyWindow(m_sdlWindow);
|
||||
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
void run() {
|
||||
try {
|
||||
Logger::info("Application: initialization...");
|
||||
m_application->applicationInit(make_shared<Controller>(this));
|
||||
|
||||
Logger::info("Application: renderer initialization...");
|
||||
m_application->renderInit(m_renderer);
|
||||
|
||||
Logger::info("Application: main update loop...");
|
||||
|
||||
m_updateTicker.reset();
|
||||
m_renderTicker.reset();
|
||||
|
||||
bool quit = false;
|
||||
while (true) {
|
||||
for (auto const& event : processEvents())
|
||||
m_application->processInput(event);
|
||||
|
||||
if (m_platformServices)
|
||||
m_platformServices->update();
|
||||
|
||||
if (m_platformServices->overlayActive())
|
||||
SDL_ShowCursor(1);
|
||||
else
|
||||
SDL_ShowCursor(m_cursorVisible ? 1 : 0);
|
||||
|
||||
int updatesBehind = max<int>(round(m_updateTicker.ticksBehind()), 1);
|
||||
updatesBehind = min<int>(updatesBehind, m_maxFrameSkip + 1);
|
||||
for (int i = 0; i < updatesBehind; ++i) {
|
||||
m_application->update();
|
||||
m_updateRate = m_updateTicker.tick();
|
||||
}
|
||||
|
||||
m_renderer->startFrame();
|
||||
m_application->render();
|
||||
m_renderer->finishFrame();
|
||||
SDL_GL_SwapWindow(m_sdlWindow);
|
||||
m_renderRate = m_renderTicker.tick();
|
||||
|
||||
if (m_quitRequested) {
|
||||
Logger::info("Application: quit requested");
|
||||
quit = true;
|
||||
}
|
||||
|
||||
if (m_signalHandler.interruptCaught()) {
|
||||
Logger::info("Application: Interrupt caught");
|
||||
quit = true;
|
||||
}
|
||||
|
||||
if (quit) {
|
||||
Logger::info("Application: quitting...");
|
||||
break;
|
||||
}
|
||||
|
||||
int64_t spareMilliseconds = round(m_updateTicker.spareTime() * 1000);
|
||||
if (spareMilliseconds > 0)
|
||||
Thread::sleepPrecise(spareMilliseconds);
|
||||
}
|
||||
} catch (std::exception const& e) {
|
||||
Logger::error("Application: exception thrown, shutting down: %s", outputException(e, true));
|
||||
}
|
||||
|
||||
try {
|
||||
Logger::info("Application: shutdown...");
|
||||
m_application->shutdown();
|
||||
} catch (std::exception const& e) {
|
||||
Logger::error("Application: threw exception during shutdown: %s", outputException(e, true));
|
||||
}
|
||||
|
||||
SDL_CloseAudio();
|
||||
m_application.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
struct Controller : public ApplicationController {
|
||||
Controller(SdlPlatform* parent)
|
||||
: parent(parent) {}
|
||||
|
||||
Maybe<String> getClipboard() override {
|
||||
if (SDL_HasClipboardText())
|
||||
return String(SDL_GetClipboardText());
|
||||
return {};
|
||||
}
|
||||
|
||||
void setClipboard(String text) override {
|
||||
SDL_SetClipboardText(text.utf8Ptr());
|
||||
}
|
||||
|
||||
void setTargetUpdateRate(float targetUpdateRate) override {
|
||||
parent->m_updateTicker.setTargetTickRate(targetUpdateRate);
|
||||
}
|
||||
|
||||
void setUpdateTrackWindow(float updateTrackWindow) override {
|
||||
parent->m_updateTicker.setWindow(updateTrackWindow);
|
||||
}
|
||||
|
||||
void setApplicationTitle(String title) override {
|
||||
parent->m_windowTitle = move(title);
|
||||
if (parent->m_sdlWindow)
|
||||
SDL_SetWindowTitle(parent->m_sdlWindow, parent->m_windowTitle.utf8Ptr());
|
||||
}
|
||||
|
||||
void setFullscreenWindow(Vec2U fullScreenResolution) override {
|
||||
if (parent->m_windowMode != WindowMode::Fullscreen || parent->m_windowSize != fullScreenResolution) {
|
||||
SDL_DisplayMode requestedDisplayMode = {SDL_PIXELFORMAT_RGB888, (int)fullScreenResolution[0], (int)fullScreenResolution[1], 0, 0};
|
||||
int currentDisplayIndex = SDL_GetWindowDisplayIndex(parent->m_sdlWindow);
|
||||
|
||||
SDL_DisplayMode targetDisplayMode;
|
||||
if (SDL_GetClosestDisplayMode(currentDisplayIndex, &requestedDisplayMode, &targetDisplayMode) != NULL) {
|
||||
if (SDL_SetWindowDisplayMode(parent->m_sdlWindow, &requestedDisplayMode) == 0) {
|
||||
if (parent->m_windowMode == WindowMode::Fullscreen)
|
||||
SDL_SetWindowFullscreen(parent->m_sdlWindow, 0);
|
||||
else
|
||||
parent->m_windowMode = WindowMode::Fullscreen;
|
||||
SDL_SetWindowFullscreen(parent->m_sdlWindow, SDL_WINDOW_FULLSCREEN);
|
||||
} else {
|
||||
Logger::warn("Failed to set resolution %s, %s", (unsigned)requestedDisplayMode.w, (unsigned)requestedDisplayMode.h);
|
||||
}
|
||||
} else {
|
||||
Logger::warn("Unable to set requested display resolution %s, %s", (int)fullScreenResolution[0], (int)fullScreenResolution[1]);
|
||||
}
|
||||
|
||||
SDL_DisplayMode actualDisplayMode;
|
||||
if (SDL_GetWindowDisplayMode(parent->m_sdlWindow, &actualDisplayMode) == 0) {
|
||||
parent->m_windowSize = {(unsigned)actualDisplayMode.w, (unsigned)actualDisplayMode.h};
|
||||
|
||||
// call these manually since no SDL_WindowEvent is triggered when changing between fullscreen resolutions for some reason
|
||||
parent->m_renderer->setScreenSize(parent->m_windowSize);
|
||||
parent->m_application->windowChanged(parent->m_windowMode, parent->m_windowSize);
|
||||
} else {
|
||||
Logger::error("Couldn't get window display mode!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setNormalWindow(Vec2U windowSize) override {
|
||||
if (parent->m_windowMode != WindowMode::Normal || parent->m_windowSize != windowSize) {
|
||||
if (parent->m_windowMode == WindowMode::Fullscreen || parent->m_windowMode == WindowMode::Borderless)
|
||||
SDL_SetWindowFullscreen(parent->m_sdlWindow, 0);
|
||||
else if (parent->m_windowMode == WindowMode::Maximized)
|
||||
SDL_RestoreWindow(parent->m_sdlWindow);
|
||||
|
||||
SDL_SetWindowSize(parent->m_sdlWindow, windowSize[0], windowSize[1]);
|
||||
SDL_SetWindowPosition(parent->m_sdlWindow, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
|
||||
parent->m_windowMode = WindowMode::Normal;
|
||||
parent->m_windowSize = windowSize;
|
||||
}
|
||||
}
|
||||
|
||||
void setMaximizedWindow() override {
|
||||
if (parent->m_windowMode != WindowMode::Maximized) {
|
||||
if (parent->m_windowMode == WindowMode::Fullscreen || parent->m_windowMode == WindowMode::Borderless)
|
||||
SDL_SetWindowFullscreen(parent->m_sdlWindow, 0);
|
||||
|
||||
SDL_MaximizeWindow(parent->m_sdlWindow);
|
||||
parent->m_windowMode = WindowMode::Maximized;
|
||||
}
|
||||
}
|
||||
|
||||
void setBorderlessWindow() override {
|
||||
if (parent->m_windowMode != WindowMode::Borderless) {
|
||||
SDL_SetWindowFullscreen(parent->m_sdlWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
parent->m_windowMode = WindowMode::Borderless;
|
||||
|
||||
SDL_DisplayMode actualDisplayMode;
|
||||
if (SDL_GetWindowDisplayMode(parent->m_sdlWindow, &actualDisplayMode) == 0) {
|
||||
parent->m_windowSize = {(unsigned)actualDisplayMode.w, (unsigned)actualDisplayMode.h};
|
||||
|
||||
parent->m_renderer->setScreenSize(parent->m_windowSize);
|
||||
parent->m_application->windowChanged(parent->m_windowMode, parent->m_windowSize);
|
||||
} else {
|
||||
Logger::error("Couldn't get window display mode!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setVSyncEnabled(bool vSync) override {
|
||||
if (parent->m_windowVSync != vSync) {
|
||||
parent->setVSyncEnabled(vSync);
|
||||
parent->m_windowVSync = vSync;
|
||||
}
|
||||
}
|
||||
|
||||
void setMaxFrameSkip(unsigned maxFrameSkip) override {
|
||||
parent->m_maxFrameSkip = maxFrameSkip;
|
||||
}
|
||||
|
||||
void setCursorVisible(bool cursorVisible) override {
|
||||
parent->m_cursorVisible = cursorVisible;
|
||||
}
|
||||
|
||||
void setAcceptingTextInput(bool acceptingTextInput) override {
|
||||
if (acceptingTextInput != parent->m_acceptingTextInput) {
|
||||
if (acceptingTextInput)
|
||||
SDL_StartTextInput();
|
||||
else
|
||||
SDL_StopTextInput();
|
||||
|
||||
parent->m_acceptingTextInput = acceptingTextInput;
|
||||
}
|
||||
}
|
||||
|
||||
AudioFormat enableAudio() override {
|
||||
parent->m_audioEnabled = true;
|
||||
SDL_PauseAudio(false);
|
||||
return AudioFormat{44100, 2};
|
||||
}
|
||||
|
||||
void disableAudio() override {
|
||||
parent->m_audioEnabled = false;
|
||||
SDL_PauseAudio(true);
|
||||
}
|
||||
|
||||
float updateRate() const override {
|
||||
return parent->m_updateRate;
|
||||
}
|
||||
|
||||
float renderFps() const override {
|
||||
return parent->m_renderRate;
|
||||
}
|
||||
|
||||
StatisticsServicePtr statisticsService() const override {
|
||||
if (parent->m_platformServices)
|
||||
return parent->m_platformServices->statisticsService();
|
||||
return {};
|
||||
}
|
||||
|
||||
P2PNetworkingServicePtr p2pNetworkingService() const override {
|
||||
if (parent->m_platformServices)
|
||||
return parent->m_platformServices->p2pNetworkingService();
|
||||
return {};
|
||||
}
|
||||
|
||||
UserGeneratedContentServicePtr userGeneratedContentService() const override {
|
||||
if (parent->m_platformServices)
|
||||
return parent->m_platformServices->userGeneratedContentService();
|
||||
return {};
|
||||
}
|
||||
|
||||
DesktopServicePtr desktopService() const override {
|
||||
if (parent->m_platformServices)
|
||||
return parent->m_platformServices->desktopService();
|
||||
return {};
|
||||
}
|
||||
|
||||
void quit() override {
|
||||
parent->m_quitRequested = true;
|
||||
}
|
||||
|
||||
SdlPlatform* parent;
|
||||
};
|
||||
|
||||
List<InputEvent> processEvents() {
|
||||
List<InputEvent> inputEvents;
|
||||
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
Maybe<InputEvent> starEvent;
|
||||
if (event.type == SDL_WINDOWEVENT) {
|
||||
if (event.window.event == SDL_WINDOWEVENT_MAXIMIZED || event.window.event == SDL_WINDOWEVENT_RESTORED) {
|
||||
auto windowFlags = SDL_GetWindowFlags(m_sdlWindow);
|
||||
|
||||
if (windowFlags & SDL_WINDOW_MAXIMIZED) {
|
||||
m_windowMode = WindowMode::Maximized;
|
||||
} else if (windowFlags & SDL_WINDOW_FULLSCREEN || windowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP) {
|
||||
if (m_windowMode != WindowMode::Fullscreen && m_windowMode != WindowMode::Borderless)
|
||||
m_windowMode = WindowMode::Fullscreen;
|
||||
} else {
|
||||
m_windowMode = WindowMode::Normal;
|
||||
}
|
||||
|
||||
m_application->windowChanged(m_windowMode, m_windowSize);
|
||||
|
||||
} else if (event.window.event == SDL_WINDOWEVENT_RESIZED || event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
|
||||
m_windowSize = Vec2U(event.window.data1, event.window.data2);
|
||||
m_renderer->setScreenSize(m_windowSize);
|
||||
m_application->windowChanged(m_windowMode, m_windowSize);
|
||||
}
|
||||
|
||||
} else if (event.type == SDL_KEYDOWN) {
|
||||
if (!event.key.repeat) {
|
||||
if (auto key = keyFromSdlKeyCode(event.key.keysym.sym))
|
||||
starEvent.set(KeyDownEvent{*key, keyModsFromSdlKeyMods(event.key.keysym.mod)});
|
||||
}
|
||||
|
||||
} else if (event.type == SDL_KEYUP) {
|
||||
if (auto key = keyFromSdlKeyCode(event.key.keysym.sym))
|
||||
starEvent.set(KeyUpEvent{*key});
|
||||
|
||||
} else if (event.type == SDL_TEXTINPUT) {
|
||||
starEvent.set(TextInputEvent{String(event.text.text)});
|
||||
|
||||
} else if (event.type == SDL_MOUSEMOTION) {
|
||||
starEvent.set(MouseMoveEvent{
|
||||
{event.motion.xrel, -event.motion.yrel}, {event.motion.x, (int)m_windowSize[1] - event.motion.y}});
|
||||
|
||||
} else if (event.type == SDL_MOUSEBUTTONDOWN) {
|
||||
starEvent.set(MouseButtonDownEvent{mouseButtonFromSdlMouseButton(event.button.button),
|
||||
{event.button.x, (int)m_windowSize[1] - event.button.y}});
|
||||
|
||||
} else if (event.type == SDL_MOUSEBUTTONUP) {
|
||||
starEvent.set(MouseButtonUpEvent{mouseButtonFromSdlMouseButton(event.button.button),
|
||||
{event.button.x, (int)m_windowSize[1] - event.button.y}});
|
||||
|
||||
} else if (event.type == SDL_MOUSEWHEEL) {
|
||||
int x, y;
|
||||
SDL_GetMouseState(&x, &y);
|
||||
starEvent.set(MouseWheelEvent{event.wheel.y < 0 ? MouseWheel::Down : MouseWheel::Up, {x, (int)m_windowSize[1] - y}});
|
||||
|
||||
} else if (event.type == SDL_QUIT) {
|
||||
m_quitRequested = true;
|
||||
starEvent.reset();
|
||||
}
|
||||
|
||||
if (starEvent)
|
||||
inputEvents.append(starEvent.take());
|
||||
}
|
||||
|
||||
return inputEvents;
|
||||
}
|
||||
|
||||
void getAudioData(Uint8* stream, int len) {
|
||||
if (m_audioEnabled) {
|
||||
m_application->getAudioData((int16_t*)stream, len / 4);
|
||||
} else {
|
||||
for (int i = 0; i < len; ++i)
|
||||
stream[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void setVSyncEnabled(bool vsyncEnabled) {
|
||||
if (vsyncEnabled) {
|
||||
// If VSync is requested, try for late swap tearing first, then fall back
|
||||
// to regular VSync
|
||||
Logger::info("Application: Enabling VSync with late swap tearing");
|
||||
if (SDL_GL_SetSwapInterval(-1) < 0) {
|
||||
Logger::info("Application: Enabling VSync late swap tearing failed, falling back to full VSync");
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
}
|
||||
} else {
|
||||
Logger::info("Application: Disabling VSync");
|
||||
SDL_GL_SetSwapInterval(0);
|
||||
}
|
||||
}
|
||||
|
||||
SignalHandler m_signalHandler;
|
||||
|
||||
TickRateApproacher m_updateTicker = TickRateApproacher(60.0f, 1.0f);
|
||||
float m_updateRate = 0.0f;
|
||||
TickRateMonitor m_renderTicker = TickRateMonitor(1.0f);
|
||||
float m_renderRate = 0.0f;
|
||||
|
||||
SDL_Window* m_sdlWindow = nullptr;
|
||||
SDL_GLContext m_sdlGlContext = nullptr;
|
||||
|
||||
Vec2U m_windowSize = {800, 600};
|
||||
WindowMode m_windowMode = WindowMode::Normal;
|
||||
|
||||
String m_windowTitle = "";
|
||||
bool m_windowVSync = true;
|
||||
unsigned m_maxFrameSkip = 5;
|
||||
bool m_cursorVisible = true;
|
||||
bool m_acceptingTextInput = false;
|
||||
bool m_audioEnabled = false;
|
||||
bool m_quitRequested = false;
|
||||
|
||||
OpenGl20RendererPtr m_renderer;
|
||||
ApplicationUPtr m_application;
|
||||
PcPlatformServicesUPtr m_platformServices;
|
||||
};
|
||||
|
||||
int runMainApplication(ApplicationUPtr application, StringList cmdLineArgs) {
|
||||
try {
|
||||
{
|
||||
SdlPlatform platform(move(application), move(cmdLineArgs));
|
||||
platform.run();
|
||||
}
|
||||
Logger::info("Application: stopped gracefully");
|
||||
return 0;
|
||||
} catch (std::exception const& e) {
|
||||
fatalException(e, true);
|
||||
} catch (...) {
|
||||
fatalError("Unknown Exception", true);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
616
source/application/StarP2PNetworkingService_pc.cpp
Normal file
616
source/application/StarP2PNetworkingService_pc.cpp
Normal file
|
|
@ -0,0 +1,616 @@
|
|||
#include "StarP2PNetworkingService_pc.hpp"
|
||||
#include "StarLexicalCast.hpp"
|
||||
#include "StarEither.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarRandom.hpp"
|
||||
#include "StarEncode.hpp"
|
||||
#include "StarUuid.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
|
||||
discord::NetworkChannelId const DiscordMainNetworkChannel = 0;
|
||||
|
||||
#endif
|
||||
|
||||
PcP2PNetworkingService::PcP2PNetworkingService(PcPlatformServicesStatePtr state)
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
: m_callbackConnectionFailure(this, &PcP2PNetworkingService::steamOnConnectionFailure),
|
||||
m_callbackJoinRequested(this, &PcP2PNetworkingService::steamOnJoinRequested),
|
||||
m_callbackSessionRequest(this, &PcP2PNetworkingService::steamOnSessionRequest),
|
||||
m_state(move(state)) {
|
||||
#else
|
||||
: m_state(move(state)) {
|
||||
#endif
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
if (m_state->discordAvailable) {
|
||||
MutexLocker discordLocker(m_state->discordMutex);
|
||||
|
||||
m_discordPartySize = {};
|
||||
|
||||
m_discordOnActivityJoinToken = m_state->discordCore->ActivityManager().OnActivityJoin.Connect([this](char const* peerId) {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
Logger::info("Joining discord peer at '%s'", peerId);
|
||||
addPendingJoin(strf("+platform:%s", peerId));
|
||||
});
|
||||
m_discordOnActivityRequestToken = m_state->discordCore->ActivityManager().OnActivityJoinRequest.Connect([this](discord::User const& user) {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
String userName = String(user.GetUsername());
|
||||
Logger::info("Received join request from user '%s'", userName);
|
||||
m_discordJoinRequests.emplace_back(make_pair(user.GetId(), userName));
|
||||
});
|
||||
m_discordOnReceiveMessage = m_state->discordCore->LobbyManager().OnNetworkMessage.Connect(bind(&PcP2PNetworkingService::discordOnReceiveMessage, this, _1, _2, _3, _4, _5));
|
||||
m_discordOnLobbyMemberConnect = m_state->discordCore->LobbyManager().OnMemberConnect.Connect(bind(&PcP2PNetworkingService::discordOnLobbyMemberConnect, this, _1, _2));
|
||||
m_discordOnLobbyMemberUpdate = m_state->discordCore->LobbyManager().OnMemberUpdate.Connect(bind(&PcP2PNetworkingService::discordOnLobbyMemberUpdate, this, _1, _2));
|
||||
m_discordOnLobbyMemberDisconnect = m_state->discordCore->LobbyManager().OnMemberDisconnect.Connect(bind(&PcP2PNetworkingService::discordOnLobbyMemberDisconnect, this, _1, _2));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
PcP2PNetworkingService::~PcP2PNetworkingService() {
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
if (m_state->discordAvailable) {
|
||||
MutexLocker discordLocker(m_state->discordMutex);
|
||||
if (m_discordServerLobby) {
|
||||
Logger::info("Deleting discord server lobby %s", m_discordServerLobby->first);
|
||||
m_state->discordCore->LobbyManager().DeleteLobby(m_discordServerLobby->first, [](discord::Result res) {
|
||||
Logger::error("Could not connect delete server lobby (err %s)", (int)res);
|
||||
});
|
||||
}
|
||||
|
||||
m_state->discordCore->ActivityManager().OnActivityJoin.Disconnect(m_discordOnActivityJoinToken);
|
||||
m_state->discordCore->LobbyManager().OnNetworkMessage.Disconnect(m_discordOnReceiveMessage);
|
||||
m_state->discordCore->LobbyManager().OnMemberConnect.Disconnect(m_discordOnLobbyMemberConnect);
|
||||
m_state->discordCore->LobbyManager().OnMemberUpdate.Disconnect(m_discordOnLobbyMemberUpdate);
|
||||
m_state->discordCore->LobbyManager().OnMemberDisconnect.Disconnect(m_discordOnLobbyMemberDisconnect);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::setJoinUnavailable() {
|
||||
setJoinLocation(JoinUnavailable());
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::setJoinLocal(uint32_t capacity) {
|
||||
setJoinLocation(JoinLocal{capacity});
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::setJoinRemote(HostAddressWithPort location) {
|
||||
setJoinLocation(JoinRemote(location));
|
||||
}
|
||||
|
||||
void Star::PcP2PNetworkingService::setActivityData(String const& title, Maybe<pair<uint16_t, uint16_t>> party) {
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
MutexLocker discordLocker(m_state->discordMutex);
|
||||
#endif
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
if (m_state->discordAvailable && m_state->discordCurrentUser) {
|
||||
if (m_discordUpdatingActivity)
|
||||
return;
|
||||
|
||||
if (title != m_discordActivityTitle || party != m_discordPartySize || m_discordForceUpdateActivity) {
|
||||
m_discordForceUpdateActivity = false;
|
||||
m_discordPartySize = party;
|
||||
m_discordActivityTitle = title;
|
||||
|
||||
discord::Activity activity = {};
|
||||
activity.SetType(discord::ActivityType::Playing);
|
||||
activity.SetName("Starbound");
|
||||
activity.SetState(title.utf8Ptr());
|
||||
|
||||
if (auto p = party) {
|
||||
activity.GetParty().GetSize().SetCurrentSize(p->first);
|
||||
activity.GetParty().GetSize().SetMaxSize(p->second);
|
||||
}
|
||||
|
||||
if (auto lobby = m_discordServerLobby)
|
||||
activity.GetParty().SetId(strf("%s", lobby->first).c_str());
|
||||
|
||||
if (m_joinLocation.is<JoinLocal>()) {
|
||||
if (auto lobby = m_discordServerLobby) {
|
||||
String joinSecret = strf("connect:discord_%s_%s_%s", m_state->discordCurrentUser->GetId(), lobby->first, lobby->second);
|
||||
Logger::info("Setting discord join secret as %s", joinSecret);
|
||||
activity.GetSecrets().SetJoin(joinSecret.utf8Ptr());
|
||||
}
|
||||
} else if (m_joinLocation.is<JoinRemote>()) {
|
||||
String address = strf("%s", (HostAddressWithPort)m_joinLocation.get<JoinRemote>());
|
||||
String joinSecret = strf("connect:address_%s", address);
|
||||
Logger::info("Setting discord join secret as %s", joinSecret);
|
||||
activity.GetSecrets().SetJoin(joinSecret.utf8Ptr());
|
||||
|
||||
activity.GetParty().SetId(address.utf8Ptr());
|
||||
}
|
||||
|
||||
m_discordUpdatingActivity = true;
|
||||
m_state->discordCore->ActivityManager().UpdateActivity(activity, [this](discord::Result res) {
|
||||
if (res != discord::Result::Ok)
|
||||
Logger::error("failed to set discord activity (err %s)", (int)res);
|
||||
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
m_discordUpdatingActivity = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MVariant<P2PNetworkingPeerId, HostAddressWithPort> PcP2PNetworkingService::pullPendingJoin() {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
return take(m_pendingJoin);
|
||||
}
|
||||
|
||||
Maybe<pair<String, RpcPromiseKeeper<P2PJoinRequestReply>>> Star::PcP2PNetworkingService::pullJoinRequest() {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
if (auto request = m_discordJoinRequests.maybeTakeLast()) {
|
||||
auto promisePair = RpcPromise<P2PJoinRequestReply>::createPair();
|
||||
m_pendingDiscordJoinRequests.push_back(make_pair(request->first, promisePair.first));
|
||||
return make_pair(request->second, promisePair.second);
|
||||
}
|
||||
#endif
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::setAcceptingP2PConnections(bool acceptingP2PConnections) {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
m_acceptingP2PConnections = acceptingP2PConnections;
|
||||
if (!m_acceptingP2PConnections)
|
||||
m_pendingIncomingConnections.clear();
|
||||
}
|
||||
|
||||
List<P2PSocketUPtr> PcP2PNetworkingService::acceptP2PConnections() {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
return take(m_pendingIncomingConnections);
|
||||
}
|
||||
|
||||
void Star::PcP2PNetworkingService::update() {
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
MutexLocker discordLocker(m_state->discordMutex);
|
||||
#endif
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
for (auto& p : m_pendingDiscordJoinRequests) {
|
||||
if (auto res = p.second.result()) {
|
||||
discord::ActivityJoinRequestReply reply;
|
||||
switch (*res) {
|
||||
case P2PJoinRequestReply::Yes:
|
||||
reply = discord::ActivityJoinRequestReply::Yes;
|
||||
break;
|
||||
case P2PJoinRequestReply::No:
|
||||
reply = discord::ActivityJoinRequestReply::No;
|
||||
break;
|
||||
case P2PJoinRequestReply::Ignore:
|
||||
reply = discord::ActivityJoinRequestReply::Ignore;
|
||||
break;
|
||||
}
|
||||
|
||||
m_state->discordCore->ActivityManager().SendRequestReply(p.first, reply, [](discord::Result res) {
|
||||
if (res != discord::Result::Ok)
|
||||
Logger::error("Could not send discord activity join response (err %s)", (int)res);
|
||||
});
|
||||
}
|
||||
}
|
||||
m_pendingDiscordJoinRequests = m_pendingDiscordJoinRequests.filtered([this](pair<discord::UserId, RpcPromise<P2PJoinRequestReply>>& p) {
|
||||
return !p.second.finished();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
Either<String, P2PSocketUPtr> PcP2PNetworkingService::connectToPeer(P2PNetworkingPeerId peerId) {
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
MutexLocker discordLocker(m_state->discordMutex);
|
||||
#endif
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
String type = peerId.extract("_");
|
||||
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
if (m_state->steamAvailable) {
|
||||
if (type == "steamid") {
|
||||
CSteamID steamId(lexicalCast<uint64>(peerId));
|
||||
return makeRight(createSteamP2PSocket(steamId));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
if (m_state->discordAvailable) {
|
||||
if (type == "discord") {
|
||||
auto remoteUserId = lexicalCast<discord::UserId>(peerId.extract("_"));
|
||||
auto lobbyId = lexicalCast<discord::LobbyId>(peerId.extract("_"));
|
||||
String lobbySecret = move(peerId);
|
||||
return makeRight(discordConnectRemote(remoteUserId, lobbyId, lobbySecret));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return makeLeft(strf("Unsupported peer type '%s'", type));
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::addPendingJoin(String connectionString) {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
|
||||
if (connectionString.extract(":") != "+platform")
|
||||
throw ApplicationException::format("malformed connection string '%s'", connectionString);
|
||||
|
||||
if (connectionString.extract(":") != "connect")
|
||||
throw ApplicationException::format("malformed connection string '%s'", connectionString);
|
||||
|
||||
String target = move(connectionString);
|
||||
String targetType = target.extract("_");
|
||||
|
||||
if (targetType == "address")
|
||||
m_pendingJoin = HostAddressWithPort(target);
|
||||
else
|
||||
m_pendingJoin = P2PNetworkingPeerId(strf("%s_%s", targetType, target));
|
||||
}
|
||||
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
|
||||
PcP2PNetworkingService::SteamP2PSocket::~SteamP2PSocket() {
|
||||
MutexLocker serviceLocker(parent->m_mutex);
|
||||
MutexLocker socketLocker(mutex);
|
||||
parent->steamCloseSocket(this);
|
||||
}
|
||||
|
||||
bool PcP2PNetworkingService::SteamP2PSocket::isOpen() {
|
||||
MutexLocker socketLocker(mutex);
|
||||
return connected;
|
||||
}
|
||||
|
||||
bool PcP2PNetworkingService::SteamP2PSocket::sendMessage(ByteArray const& message) {
|
||||
MutexLocker socketLocker(mutex);
|
||||
if (!connected)
|
||||
return false;
|
||||
|
||||
if (!SteamNetworking()->SendP2PPacket(steamId, message.ptr(), message.size(), k_EP2PSendReliable))
|
||||
throw ApplicationException("SteamNetworking::SendP2PPacket unexpectedly returned false");
|
||||
return true;
|
||||
}
|
||||
|
||||
Maybe<ByteArray> PcP2PNetworkingService::SteamP2PSocket::receiveMessage() {
|
||||
MutexLocker socketLocker(mutex);
|
||||
if (!incoming.empty())
|
||||
return incoming.takeFirst();
|
||||
|
||||
if (connected) {
|
||||
socketLocker.unlock();
|
||||
{
|
||||
MutexLocker serviceLocker(parent->m_mutex);
|
||||
parent->steamReceiveAll();
|
||||
}
|
||||
|
||||
socketLocker.lock();
|
||||
if (!incoming.empty())
|
||||
return incoming.takeFirst();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto PcP2PNetworkingService::createSteamP2PSocket(CSteamID steamId) -> unique_ptr<SteamP2PSocket> {
|
||||
if (auto oldSocket = m_steamOpenSockets.value(steamId.ConvertToUint64())) {
|
||||
MutexLocker socketLocker(oldSocket->mutex);
|
||||
steamCloseSocket(oldSocket);
|
||||
}
|
||||
|
||||
unique_ptr<SteamP2PSocket> socket(new SteamP2PSocket);
|
||||
socket->parent = this;
|
||||
socket->steamId = steamId;
|
||||
socket->connected = true;
|
||||
|
||||
m_steamOpenSockets[steamId.ConvertToUint64()] = socket.get();
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::steamOnConnectionFailure(P2PSessionConnectFail_t* callback) {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
Logger::warn("Connection with steam user %s failed", callback->m_steamIDRemote.ConvertToUint64());
|
||||
if (auto socket = m_steamOpenSockets.value(callback->m_steamIDRemote.ConvertToUint64())) {
|
||||
MutexLocker socketLocker(socket->mutex);
|
||||
steamCloseSocket(socket);
|
||||
}
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::steamOnJoinRequested(GameRichPresenceJoinRequested_t* callback) {
|
||||
Logger::info("Queueing join request with steam friend id %s to address %s", callback->m_steamIDFriend.ConvertToUint64(), callback->m_rgchConnect);
|
||||
addPendingJoin(callback->m_rgchConnect);
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::steamOnSessionRequest(P2PSessionRequest_t* callback) {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
// Not sure whether this HasFriend call is actually necessary, or whether
|
||||
// non-friends can even initiate P2P sessions.
|
||||
if (m_acceptingP2PConnections && SteamFriends()->HasFriend(callback->m_steamIDRemote, k_EFriendFlagImmediate)) {
|
||||
if (SteamNetworking()->AcceptP2PSessionWithUser(callback->m_steamIDRemote)) {
|
||||
Logger::info("Accepted steam p2p connection with user %s", callback->m_steamIDRemote.ConvertToUint64());
|
||||
m_pendingIncomingConnections.append(createSteamP2PSocket(callback->m_steamIDRemote));
|
||||
} else {
|
||||
Logger::error("Accepting steam p2p connection from user %s failed!", callback->m_steamIDRemote.ConvertToUint64());
|
||||
}
|
||||
} else {
|
||||
Logger::error("Ignoring steam p2p connection from user %s", callback->m_steamIDRemote.ConvertToUint64());
|
||||
}
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::steamCloseSocket(SteamP2PSocket* socket) {
|
||||
if (socket->connected) {
|
||||
Logger::info("Closing p2p connection with steam user %s", socket->steamId.ConvertToUint64());
|
||||
m_steamOpenSockets.remove(socket->steamId.ConvertToUint64());
|
||||
socket->connected = false;
|
||||
}
|
||||
SteamNetworking()->CloseP2PSessionWithUser(socket->steamId);
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::steamReceiveAll() {
|
||||
uint32_t messageSize;
|
||||
CSteamID messageRemoteUser;
|
||||
while (SteamNetworking()->IsP2PPacketAvailable(&messageSize)) {
|
||||
ByteArray data(messageSize, 0);
|
||||
SteamNetworking()->ReadP2PPacket(data.ptr(), messageSize, &messageSize, &messageRemoteUser);
|
||||
if (auto openSocket = m_steamOpenSockets.value(messageRemoteUser.ConvertToUint64())) {
|
||||
MutexLocker socketLocker(openSocket->mutex);
|
||||
openSocket->incoming.append(move(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
|
||||
PcP2PNetworkingService::DiscordP2PSocket::~DiscordP2PSocket() {
|
||||
MutexLocker discordLocker(parent->m_state->discordMutex);
|
||||
MutexLocker serviceLocker(parent->m_mutex);
|
||||
MutexLocker socketLocker(mutex);
|
||||
parent->discordCloseSocket(this);
|
||||
}
|
||||
|
||||
bool PcP2PNetworkingService::DiscordP2PSocket::isOpen() {
|
||||
MutexLocker socketLocker(mutex);
|
||||
return mode != DiscordSocketMode::Disconnected;
|
||||
}
|
||||
|
||||
bool PcP2PNetworkingService::DiscordP2PSocket::sendMessage(ByteArray const& message) {
|
||||
MutexLocker discordLocker(parent->m_state->discordMutex);
|
||||
MutexLocker socketLocker(mutex);
|
||||
if (mode != DiscordSocketMode::Connected)
|
||||
return false;
|
||||
|
||||
discord::Result res = parent->m_state->discordCore->LobbyManager().SendNetworkMessage(lobbyId, remoteUserId, DiscordMainNetworkChannel, (uint8_t*)message.ptr(), message.size());
|
||||
if (res != discord::Result::Ok)
|
||||
throw ApplicationException::format("discord::Network::Send returned error (err %s)", (int)res);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Maybe<ByteArray> PcP2PNetworkingService::DiscordP2PSocket::receiveMessage() {
|
||||
MutexLocker socketLocker(mutex);
|
||||
if (!incoming.empty())
|
||||
return incoming.takeFirst();
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::discordCloseSocket(DiscordP2PSocket* socket) {
|
||||
if (socket->mode != DiscordSocketMode::Disconnected) {
|
||||
m_discordOpenSockets.remove(socket->remoteUserId);
|
||||
|
||||
if (socket->mode == DiscordSocketMode::Connected) {
|
||||
if (!m_joinLocation.is<JoinLocal>() && m_discordOpenSockets.empty()) {
|
||||
auto res = m_state->discordCore->LobbyManager().DisconnectNetwork(socket->lobbyId);
|
||||
if (res != discord::Result::Ok)
|
||||
Logger::error("failed to leave network for lobby %s (err %s)", socket->lobbyId, (int)res);
|
||||
|
||||
m_state->discordCore->LobbyManager().DisconnectLobby(socket->lobbyId, [this, lobbyId = socket->lobbyId](discord::Result res) {
|
||||
if (res != discord::Result::Ok)
|
||||
Logger::error("failed to leave discord lobby %s", lobbyId);
|
||||
|
||||
Logger::info("Left discord lobby %s", lobbyId);
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
m_discordServerLobby = {};
|
||||
m_discordForceUpdateActivity = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
socket->mode = DiscordSocketMode::Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
P2PSocketUPtr PcP2PNetworkingService::discordConnectRemote(discord::UserId remoteUserId, discord::LobbyId lobbyId, String const& lobbySecret) {
|
||||
if (auto oldSocket = m_discordOpenSockets.value(remoteUserId)) {
|
||||
MutexLocker socketLocker(oldSocket->mutex);
|
||||
discordCloseSocket(oldSocket);
|
||||
}
|
||||
|
||||
unique_ptr<DiscordP2PSocket> socket(new DiscordP2PSocket);
|
||||
socket->parent = this;
|
||||
socket->mode = DiscordSocketMode::Startup;
|
||||
socket->remoteUserId = remoteUserId;
|
||||
socket->lobbyId = lobbyId;
|
||||
m_discordOpenSockets[remoteUserId] = socket.get();
|
||||
|
||||
Logger::info("Connect to discord lobby %s", lobbyId);
|
||||
m_state->discordCore->LobbyManager().ConnectLobby(lobbyId, lobbySecret.utf8Ptr(), [this, remoteUserId, lobbyId](discord::Result res, discord::Lobby const& lobby) {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
if (res == discord::Result::Ok) {
|
||||
if (auto socket = m_discordOpenSockets.value(remoteUserId)) {
|
||||
MutexLocker socketLocker(socket->mutex);
|
||||
|
||||
res = m_state->discordCore->LobbyManager().ConnectNetwork(lobbyId);
|
||||
if (res != discord::Result::Ok) {
|
||||
discordCloseSocket(socket);
|
||||
return Logger::error("Could not connect to discord lobby network (err %s)", (int)res);
|
||||
}
|
||||
|
||||
res = m_state->discordCore->LobbyManager().OpenNetworkChannel(lobbyId, DiscordMainNetworkChannel, true);
|
||||
if (res != discord::Result::Ok) {
|
||||
discordCloseSocket(socket);
|
||||
return Logger::error("Could not open discord main network channel (err %s)", (int)res);
|
||||
}
|
||||
|
||||
socket->mode = DiscordSocketMode::Connected;
|
||||
Logger::info("Discord p2p connection opened to remote user %s via lobby %s", remoteUserId, lobbyId);
|
||||
|
||||
m_discordServerLobby = make_pair(lobbyId, String());
|
||||
m_discordForceUpdateActivity = true;
|
||||
} else {
|
||||
Logger::error("discord::Lobbies::Connect callback no matching remoteUserId %s found", remoteUserId);
|
||||
}
|
||||
} else {
|
||||
Logger::error("failed to connect to remote lobby (err %s)", (int)res);
|
||||
if (auto socket = m_discordOpenSockets.value(remoteUserId)) {
|
||||
MutexLocker socketLocker(socket->mutex);
|
||||
discordCloseSocket(socket);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return unique_ptr<P2PSocket>(move(socket));
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::discordOnReceiveMessage(discord::LobbyId lobbyId, discord::UserId userId, discord::NetworkChannelId channel, uint8_t* data, uint32_t size) {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
|
||||
if (lobbyId != m_discordServerLobby->first)
|
||||
return Logger::error("Received message from unexpected lobby %s", lobbyId);
|
||||
|
||||
if (auto socket = m_discordOpenSockets.value(userId)) {
|
||||
if (channel == DiscordMainNetworkChannel) {
|
||||
MutexLocker socketLocker(socket->mutex);
|
||||
socket->incoming.append(ByteArray((char const*)data, size));
|
||||
} else {
|
||||
Logger::error("Received discord message on unexpected channel %s, ignoring", channel);
|
||||
}
|
||||
} else {
|
||||
Logger::error("Could not find associated discord socket for user id %s", userId);
|
||||
}
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::discordOnLobbyMemberConnect(discord::LobbyId lobbyId, discord::UserId userId) {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
|
||||
if (m_discordServerLobby && m_discordServerLobby->first == lobbyId && userId != m_state->discordCurrentUser->GetId()) {
|
||||
if (!m_discordOpenSockets.contains(userId)) {
|
||||
unique_ptr<DiscordP2PSocket> socket(new DiscordP2PSocket);
|
||||
socket->parent = this;
|
||||
socket->lobbyId = lobbyId;
|
||||
socket->remoteUserId = userId;
|
||||
socket->mode = DiscordSocketMode::Connected;
|
||||
|
||||
m_discordOpenSockets[userId] = socket.get();
|
||||
m_pendingIncomingConnections.append(move(socket));
|
||||
Logger::info("Accepted new discord connection from remote user %s", userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::discordOnLobbyMemberUpdate(discord::LobbyId lobbyId, discord::UserId userId) {
|
||||
discordOnLobbyMemberConnect(lobbyId, userId);
|
||||
}
|
||||
|
||||
void PcP2PNetworkingService::discordOnLobbyMemberDisconnect(discord::LobbyId lobbyId, discord::UserId userId) {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
|
||||
if (m_discordServerLobby && m_discordServerLobby->first == lobbyId && userId != m_state->discordCurrentUser->GetId()) {
|
||||
if (auto socket = m_discordOpenSockets.value(userId)) {
|
||||
MutexLocker socketLocker(socket->mutex);
|
||||
discordCloseSocket(socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void PcP2PNetworkingService::setJoinLocation(JoinLocation location) {
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
MutexLocker discordLocker(m_state->discordMutex);
|
||||
#endif
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
|
||||
if (location == m_joinLocation)
|
||||
return;
|
||||
m_joinLocation = location;
|
||||
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
if (m_state->steamAvailable) {
|
||||
if (m_joinLocation.is<JoinUnavailable>()) {
|
||||
Logger::info("Clearing steam rich presence connection");
|
||||
SteamFriends()->SetRichPresence("connect", "");
|
||||
|
||||
} else if (m_joinLocation.is<JoinLocal>()) {
|
||||
auto steamId = SteamUser()->GetSteamID().ConvertToUint64();
|
||||
Logger::info("Setting steam rich presence connection as steamid_%s", steamId);
|
||||
SteamFriends()->SetRichPresence("connect", strf("+platform:connect:steamid_%s", steamId).c_str());
|
||||
|
||||
} else if (m_joinLocation.is<JoinRemote>()) {
|
||||
auto address = (HostAddressWithPort)location.get<JoinRemote>();
|
||||
Logger::info("Setting steam rich presence connection as address_%s", address);
|
||||
SteamFriends()->SetRichPresence("connect", strf("+platform:connect:address_%s", address).c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
if (m_state->discordAvailable && m_state->discordCurrentUser) {
|
||||
if (m_discordServerLobby) {
|
||||
Logger::info("Deleting discord server lobby %s", m_discordServerLobby->first);
|
||||
m_state->discordCore->LobbyManager().DeleteLobby(m_discordServerLobby->first, [](discord::Result res) {
|
||||
Logger::error("Could not connect delete server lobby (err %s)", (int)res);
|
||||
});
|
||||
}
|
||||
|
||||
if (auto joinLocal = m_joinLocation.maybe<JoinLocal>()) {
|
||||
discord::LobbyTransaction createLobby{};
|
||||
if (m_state->discordCore->LobbyManager().GetLobbyCreateTransaction(&createLobby) != discord::Result::Ok)
|
||||
throw ApplicationException::format("discord::Lobbies::CreateLobbyTransaction failed");
|
||||
|
||||
createLobby.SetCapacity(joinLocal->capacity);
|
||||
createLobby.SetType(discord::LobbyType::Private);
|
||||
m_state->discordCore->LobbyManager().CreateLobby(createLobby, [this](discord::Result res, discord::Lobby const& lobby) {
|
||||
if (res == discord::Result::Ok) {
|
||||
MutexLocker serviceLocker(m_mutex);
|
||||
|
||||
discord::LobbyId lobbyId = lobby.GetId();
|
||||
|
||||
res = m_state->discordCore->LobbyManager().ConnectNetwork(lobbyId);
|
||||
if (res == discord::Result::Ok) {
|
||||
res = m_state->discordCore->LobbyManager().OpenNetworkChannel(lobbyId, DiscordMainNetworkChannel, true);
|
||||
if (res == discord::Result::Ok) {
|
||||
m_discordServerLobby = make_pair(lobbyId, String(lobby.GetSecret()));
|
||||
m_discordForceUpdateActivity = true;
|
||||
|
||||
// successfully joined lobby network
|
||||
return;
|
||||
} else {
|
||||
Logger::error("Failed to open discord main network channel (err %s)", (int)res);
|
||||
}
|
||||
} else {
|
||||
Logger::error("Failed to join discord lobby network (err %s)", (int)res);
|
||||
}
|
||||
|
||||
// Created lobby but failed to join the lobby network, delete lobby
|
||||
Logger::error("Failed to join discord lobby network (err %s)", (int)res);
|
||||
|
||||
Logger::info("Deleting discord lobby %s", lobbyId);
|
||||
m_state->discordCore->LobbyManager().DeleteLobby(lobbyId, [lobbyId](discord::Result res) {
|
||||
Logger::error("failed to delete lobby %s (err %s)", lobbyId, (int)res);
|
||||
});
|
||||
} else {
|
||||
Logger::error("failed to create discord lobby (err %s)", (int)res);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
146
source/application/StarP2PNetworkingService_pc.hpp
Normal file
146
source/application/StarP2PNetworkingService_pc.hpp
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#ifndef STAR_P2P_NETWORKING_SERVICE_PC_HPP
|
||||
#define STAR_P2P_NETWORKING_SERVICE_PC_HPP
|
||||
|
||||
#include "StarPlatformServices_pc.hpp"
|
||||
#include "StarAlgorithm.hpp"
|
||||
#include "StarThread.hpp"
|
||||
#include "StarStrongTypedef.hpp"
|
||||
#include "StarRpcPromise.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(PcP2PNetworkingService);
|
||||
|
||||
class PcP2PNetworkingService : public P2PNetworkingService {
|
||||
public:
|
||||
PcP2PNetworkingService(PcPlatformServicesStatePtr state);
|
||||
~PcP2PNetworkingService();
|
||||
|
||||
void setJoinUnavailable() override;
|
||||
void setJoinLocal(uint32_t capacity) override;
|
||||
void setJoinRemote(HostAddressWithPort location) override;
|
||||
void setActivityData(String const& title, Maybe<pair<uint16_t, uint16_t>>) override;
|
||||
|
||||
MVariant<P2PNetworkingPeerId, HostAddressWithPort> pullPendingJoin() override;
|
||||
Maybe<pair<String, RpcPromiseKeeper<P2PJoinRequestReply>>> pullJoinRequest() override;
|
||||
|
||||
void setAcceptingP2PConnections(bool acceptingP2PConnections) override;
|
||||
List<P2PSocketUPtr> acceptP2PConnections() override;
|
||||
void update() override;
|
||||
Either<String, P2PSocketUPtr> connectToPeer(P2PNetworkingPeerId peerId) override;
|
||||
|
||||
void addPendingJoin(String connectionString);
|
||||
|
||||
private:
|
||||
strong_typedef(Empty, JoinUnavailable);
|
||||
struct JoinLocal {
|
||||
bool operator==(JoinLocal const& rhs) const { return capacity == rhs.capacity; };
|
||||
uint32_t capacity;
|
||||
};
|
||||
strong_typedef(HostAddressWithPort, JoinRemote);
|
||||
typedef Variant<JoinUnavailable, JoinLocal, JoinRemote> JoinLocation;
|
||||
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
|
||||
struct SteamP2PSocket : P2PSocket {
|
||||
SteamP2PSocket() = default;
|
||||
~SteamP2PSocket();
|
||||
|
||||
bool isOpen() override;
|
||||
bool sendMessage(ByteArray const& message) override;
|
||||
Maybe<ByteArray> receiveMessage() override;
|
||||
|
||||
Mutex mutex;
|
||||
PcP2PNetworkingService* parent = nullptr;
|
||||
CSteamID steamId = CSteamID();
|
||||
Deque<ByteArray> incoming;
|
||||
bool connected = false;
|
||||
};
|
||||
|
||||
unique_ptr<SteamP2PSocket> createSteamP2PSocket(CSteamID steamId);
|
||||
|
||||
STEAM_CALLBACK(PcP2PNetworkingService, steamOnConnectionFailure, P2PSessionConnectFail_t, m_callbackConnectionFailure);
|
||||
STEAM_CALLBACK(PcP2PNetworkingService, steamOnJoinRequested, GameRichPresenceJoinRequested_t, m_callbackJoinRequested);
|
||||
STEAM_CALLBACK(PcP2PNetworkingService, steamOnSessionRequest, P2PSessionRequest_t, m_callbackSessionRequest);
|
||||
|
||||
void steamCloseSocket(SteamP2PSocket* socket);
|
||||
void steamReceiveAll();
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
|
||||
enum class DiscordSocketMode {
|
||||
Startup,
|
||||
Connected,
|
||||
Disconnected
|
||||
};
|
||||
|
||||
struct DiscordP2PSocket : P2PSocket {
|
||||
DiscordP2PSocket() = default;
|
||||
~DiscordP2PSocket();
|
||||
|
||||
bool isOpen() override;
|
||||
bool sendMessage(ByteArray const& message) override;
|
||||
Maybe<ByteArray> receiveMessage() override;
|
||||
|
||||
Mutex mutex;
|
||||
PcP2PNetworkingService* parent = nullptr;
|
||||
DiscordSocketMode mode = DiscordSocketMode::Disconnected;
|
||||
discord::LobbyId lobbyId = {};
|
||||
discord::UserId remoteUserId;
|
||||
Deque<ByteArray> incoming;
|
||||
};
|
||||
|
||||
P2PSocketUPtr discordConnectRemote(discord::UserId remoteUserId, discord::LobbyId lobbyId, String const& lobbySecret);
|
||||
void discordCloseSocket(DiscordP2PSocket* socket);
|
||||
|
||||
void discordOnReceiveMessage(discord::LobbyId lobbyId, discord::UserId userId, discord::NetworkChannelId channel, uint8_t* data, uint32_t size);
|
||||
void discordOnLobbyMemberConnect(discord::LobbyId lobbyId, discord::UserId userId);
|
||||
void discordOnLobbyMemberUpdate(discord::LobbyId lobbyId, discord::UserId userId);
|
||||
void discordOnLobbyMemberDisconnect(discord::LobbyId lobbyId, discord::UserId userId);
|
||||
|
||||
#endif
|
||||
|
||||
void setJoinLocation(JoinLocation joinLocation);
|
||||
|
||||
PcPlatformServicesStatePtr m_state;
|
||||
|
||||
Mutex m_mutex;
|
||||
JoinLocation m_joinLocation;
|
||||
bool m_acceptingP2PConnections = false;
|
||||
List<P2PSocketUPtr> m_pendingIncomingConnections;
|
||||
MVariant<P2PNetworkingPeerId, HostAddressWithPort> m_pendingJoin;
|
||||
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
|
||||
HashMap<uint64, SteamP2PSocket*> m_steamOpenSockets;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
|
||||
|
||||
List<pair<discord::UserId, String>> m_discordJoinRequests;
|
||||
List<pair<discord::UserId, RpcPromise<P2PJoinRequestReply>>> m_pendingDiscordJoinRequests;
|
||||
|
||||
HashMap<discord::UserId, DiscordP2PSocket*> m_discordOpenSockets;
|
||||
String m_discordActivityTitle;
|
||||
Maybe<pair<uint16_t, uint16_t>> m_discordPartySize;
|
||||
bool m_discordForceUpdateActivity = false;
|
||||
bool m_discordUpdatingActivity = false;
|
||||
Maybe<pair<discord::LobbyId, String>> m_discordServerLobby = {};
|
||||
|
||||
int m_discordOnActivityJoinToken = 0;
|
||||
int m_discordOnActivityRequestToken = 0;
|
||||
int m_discordOnReceiveMessage = 0;
|
||||
int m_discordOnLobbyMemberConnect = 0;
|
||||
int m_discordOnLobbyMemberUpdate = 0;
|
||||
int m_discordOnLobbyMemberDisconnect = 0;
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
175
source/application/StarPlatformServices_pc.cpp
Normal file
175
source/application/StarPlatformServices_pc.cpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
#include "StarLogging.hpp"
|
||||
#include "StarPlatformServices_pc.hpp"
|
||||
#include "StarP2PNetworkingService_pc.hpp"
|
||||
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
#include "StarStatisticsService_pc_steam.hpp"
|
||||
#include "StarUserGeneratedContentService_pc_steam.hpp"
|
||||
#include "StarDesktopService_pc_steam.hpp"
|
||||
#endif
|
||||
|
||||
namespace Star {
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
uint64_t const DiscordClientId = 467102538278109224;
|
||||
#endif
|
||||
|
||||
PcPlatformServicesState::PcPlatformServicesState()
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
: callbackGameOverlayActivated(this, &PcPlatformServicesState::onGameOverlayActivated) {
|
||||
#else
|
||||
{
|
||||
#endif
|
||||
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
if (SteamAPI_Init()) {
|
||||
steamAvailable = true;
|
||||
Logger::info("Initialized Steam platform services");
|
||||
} else {
|
||||
Logger::info("Failed to initialize Steam platform services");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
static int64_t const DiscordEventSleep = 3;
|
||||
|
||||
discord::Core* discordCorePtr = nullptr;
|
||||
discord::Result res = discord::Core::Create(DiscordClientId, DiscordCreateFlags_NoRequireDiscord, &discordCorePtr);
|
||||
if (res == discord::Result::Ok && discordCorePtr) {
|
||||
discordCore.reset(discordCorePtr);
|
||||
discordAvailable = true;
|
||||
|
||||
discordCore->UserManager().OnCurrentUserUpdate.Connect([this](){
|
||||
discord::User user;
|
||||
auto res = discordCore->UserManager().GetCurrentUser(&user);
|
||||
if (res != discord::Result::Ok)
|
||||
Logger::error("Could not get current discord user. (err %s)", (int)res);
|
||||
else
|
||||
discordCurrentUser = user;
|
||||
});
|
||||
|
||||
} else {
|
||||
Logger::error("Failed to instantiate discord core (err %s)", (int)res);
|
||||
}
|
||||
|
||||
if (discordAvailable) {
|
||||
MutexLocker locker(discordMutex);
|
||||
discordCore->SetLogHook(discord::LogLevel::Info, [](discord::LogLevel level, char const* msg) {
|
||||
if (level == discord::LogLevel::Debug)
|
||||
Logger::debug("[DISCORD]: %s", msg);
|
||||
else if (level == discord::LogLevel::Error)
|
||||
Logger::debug("[DISCORD]: %s", msg);
|
||||
else if (level == discord::LogLevel::Info)
|
||||
Logger::info("[DISCORD]: %s", msg);
|
||||
else if (level == discord::LogLevel::Warn)
|
||||
Logger::warn("[DISCORD]: %s", msg);
|
||||
});
|
||||
discordEventShutdown = false;
|
||||
discordEventThread = Thread::invoke("PcPlatformServices::discordEventThread", [this]() {
|
||||
while (!discordEventShutdown) {
|
||||
{
|
||||
MutexLocker locker(discordMutex);
|
||||
discordCore->RunCallbacks();
|
||||
discordCore->LobbyManager().FlushNetwork();
|
||||
}
|
||||
Thread::sleep(DiscordEventSleep);
|
||||
}
|
||||
});
|
||||
|
||||
Logger::info("Initialized Discord platform services");
|
||||
} else {
|
||||
Logger::info("Was not able to authenticate with Discord and create all components, Discord services will be unavailable");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
PcPlatformServicesState::~PcPlatformServicesState() {
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
if (discordAvailable) {
|
||||
discordEventShutdown = true;
|
||||
discordEventThread.finish();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
void PcPlatformServicesState::onGameOverlayActivated(GameOverlayActivated_t* callback) {
|
||||
overlayActive = callback->m_bActive;
|
||||
}
|
||||
#endif
|
||||
|
||||
PcPlatformServicesUPtr PcPlatformServices::create(String const& path, StringList platformArguments) {
|
||||
auto services = unique_ptr<PcPlatformServices>(new PcPlatformServices);
|
||||
|
||||
services->m_state = make_shared<PcPlatformServicesState>();
|
||||
|
||||
bool provideP2PNetworking = false;
|
||||
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
provideP2PNetworking |= services->m_state->steamAvailable;
|
||||
#endif
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
provideP2PNetworking |= services->m_state->discordAvailable;
|
||||
#endif
|
||||
|
||||
if (provideP2PNetworking) {
|
||||
auto p2pNetworkingService = make_shared<PcP2PNetworkingService>(services->m_state);
|
||||
for (auto& argument : platformArguments) {
|
||||
if (argument.beginsWith("+platform:connect:")) {
|
||||
Logger::info("PC platform services joining from command line argument '%s'", argument);
|
||||
p2pNetworkingService->addPendingJoin(move(argument));
|
||||
} else {
|
||||
throw ApplicationException::format("Unrecognized PC platform services command line argument '%s'", argument);
|
||||
}
|
||||
}
|
||||
|
||||
services->m_p2pNetworkingService = p2pNetworkingService;
|
||||
}
|
||||
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
if (services->m_state->steamAvailable) {
|
||||
services->m_statisticsService = make_shared<SteamStatisticsService>(services->m_state);
|
||||
services->m_userGeneratedContentService = make_shared<SteamUserGeneratedContentService>(services->m_state);
|
||||
services->m_desktopService = make_shared<SteamDesktopService>(services->m_state);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
MutexLocker discordLocker(services->m_state->discordMutex);
|
||||
if (services->m_state->discordAvailable) {
|
||||
Logger::debug("Registering starbound to discord at path: %s", path);
|
||||
services->m_state->discordCore->ActivityManager().RegisterCommand(path.utf8Ptr());
|
||||
}
|
||||
#endif
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
StatisticsServicePtr PcPlatformServices::statisticsService() const {
|
||||
return m_statisticsService;
|
||||
}
|
||||
|
||||
P2PNetworkingServicePtr PcPlatformServices::p2pNetworkingService() const {
|
||||
return m_p2pNetworkingService;
|
||||
}
|
||||
|
||||
UserGeneratedContentServicePtr PcPlatformServices::userGeneratedContentService() const {
|
||||
return m_userGeneratedContentService;
|
||||
}
|
||||
|
||||
DesktopServicePtr PcPlatformServices::desktopService() const {
|
||||
return m_desktopService;
|
||||
}
|
||||
|
||||
bool PcPlatformServices::overlayActive() const {
|
||||
return m_state->overlayActive;
|
||||
}
|
||||
|
||||
void PcPlatformServices::update() {
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
SteamAPI_RunCallbacks();
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
83
source/application/StarPlatformServices_pc.hpp
Normal file
83
source/application/StarPlatformServices_pc.hpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#ifndef STAR_PLATFORM_SERVICES_PC_HPP
|
||||
#define STAR_PLATFORM_SERVICES_PC_HPP
|
||||
|
||||
#include "StarThread.hpp"
|
||||
#include "StarApplication.hpp"
|
||||
#include "StarStatisticsService.hpp"
|
||||
#include "StarP2PNetworkingService.hpp"
|
||||
#include "StarUserGeneratedContentService.hpp"
|
||||
#include "StarDesktopService.hpp"
|
||||
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
#include "steam/steam_api.h"
|
||||
#endif
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
#include "discord/discord.h"
|
||||
#endif
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(PcPlatformServices);
|
||||
STAR_STRUCT(PcPlatformServicesState);
|
||||
|
||||
struct PcPlatformServicesState {
|
||||
PcPlatformServicesState();
|
||||
~PcPlatformServicesState();
|
||||
|
||||
#ifdef STAR_ENABLE_STEAM_INTEGRATION
|
||||
STEAM_CALLBACK(PcPlatformServicesState, onGameOverlayActivated, GameOverlayActivated_t, callbackGameOverlayActivated);
|
||||
|
||||
bool steamAvailable = false;
|
||||
#endif
|
||||
|
||||
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
|
||||
bool discordAvailable = false;
|
||||
|
||||
// Must lock discordMutex before accessing any of the managers when not inside
|
||||
// a discord callback.
|
||||
Mutex discordMutex;
|
||||
|
||||
unique_ptr<discord::Core> discordCore;
|
||||
|
||||
Maybe<discord::User> discordCurrentUser;
|
||||
ThreadFunction<void> discordEventThread;
|
||||
atomic<bool> discordEventShutdown;
|
||||
#endif
|
||||
|
||||
bool overlayActive = false;
|
||||
};
|
||||
|
||||
|
||||
class PcPlatformServices {
|
||||
public:
|
||||
// Any command line arguments that start with '+platform' will be stripped
|
||||
// out and passed here
|
||||
static PcPlatformServicesUPtr create(String const& path, StringList platformArguments);
|
||||
|
||||
StatisticsServicePtr statisticsService() const;
|
||||
P2PNetworkingServicePtr p2pNetworkingService() const;
|
||||
UserGeneratedContentServicePtr userGeneratedContentService() const;
|
||||
DesktopServicePtr desktopService() const;
|
||||
|
||||
// Will return true if there is an in-game overlay active. This is important
|
||||
// because the cursor must be visible when such an overlay is active,
|
||||
// regardless of the ApplicationController setting.
|
||||
bool overlayActive() const;
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
PcPlatformServices() = default;
|
||||
|
||||
PcPlatformServicesStatePtr m_state;
|
||||
|
||||
StatisticsServicePtr m_statisticsService;
|
||||
P2PNetworkingServicePtr m_p2pNetworkingService;
|
||||
UserGeneratedContentServicePtr m_userGeneratedContentService;
|
||||
DesktopServicePtr m_desktopService;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
60
source/application/StarRenderer.cpp
Normal file
60
source/application/StarRenderer.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#include "StarRenderer.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
EnumMap<TextureAddressing> const TextureAddressingNames{
|
||||
{TextureAddressing::Clamp, "Clamp"},
|
||||
{TextureAddressing::Wrap, "Wrap"}
|
||||
};
|
||||
|
||||
EnumMap<TextureFiltering> const TextureFilteringNames{
|
||||
{TextureFiltering::Nearest, "Nearest"},
|
||||
{TextureFiltering::Linear, "Linear"}
|
||||
};
|
||||
|
||||
RenderQuad renderTexturedRect(TexturePtr texture, Vec2F minPosition, float textureScale, Vec4B color, float param1) {
|
||||
if (!texture)
|
||||
throw RendererException("renderTexturedRect called with null texture");
|
||||
|
||||
auto textureSize = Vec2F(texture->size());
|
||||
return {
|
||||
move(texture),
|
||||
RenderVertex{minPosition, Vec2F(0, 0), color, param1},
|
||||
RenderVertex{minPosition + Vec2F(textureSize[0], 0) * textureScale, Vec2F(textureSize[0], 0), color, param1},
|
||||
RenderVertex{minPosition + Vec2F(textureSize[0], textureSize[1]) * textureScale, Vec2F(textureSize[0], textureSize[1]), color, param1},
|
||||
RenderVertex{minPosition + Vec2F(0, textureSize[1]) * textureScale, Vec2F(0, textureSize[1]), color, param1}
|
||||
};
|
||||
}
|
||||
|
||||
RenderQuad renderTexturedRect(TexturePtr texture, RectF const& screenCoords, Vec4B color, float param1) {
|
||||
if (!texture)
|
||||
throw RendererException("renderTexturedRect called with null texture");
|
||||
|
||||
auto textureSize = Vec2F(texture->size());
|
||||
return {
|
||||
move(texture),
|
||||
RenderVertex{{screenCoords.xMin(), screenCoords.yMin()}, Vec2F(0, 0), color, param1},
|
||||
RenderVertex{{screenCoords.xMax(), screenCoords.yMin()}, Vec2F(textureSize[0], 0), color, param1},
|
||||
RenderVertex{{screenCoords.xMax(), screenCoords.yMax()}, Vec2F(textureSize[0], textureSize[1]), color, param1},
|
||||
RenderVertex{{screenCoords.xMin(), screenCoords.yMax()}, Vec2F(0, textureSize[1]), color, param1}
|
||||
};
|
||||
}
|
||||
|
||||
RenderQuad renderFlatRect(RectF const& rect, Vec4B color, float param1) {
|
||||
return {
|
||||
{},
|
||||
RenderVertex{{rect.xMin(), rect.yMin()}, {}, color, param1},
|
||||
RenderVertex{{rect.xMax(), rect.yMin()}, {}, color, param1},
|
||||
RenderVertex{{rect.xMax(), rect.yMax()}, {}, color, param1},
|
||||
RenderVertex{{rect.xMin(), rect.yMax()}, {}, color, param1}
|
||||
};
|
||||
}
|
||||
|
||||
RenderPoly renderFlatPoly(PolyF const& poly, Vec4B color, float param1) {
|
||||
RenderPoly renderPoly;
|
||||
for (auto const& v : poly)
|
||||
renderPoly.vertexes.append({v, {}, color, param1});
|
||||
return renderPoly;
|
||||
}
|
||||
|
||||
}
|
||||
147
source/application/StarRenderer.hpp
Normal file
147
source/application/StarRenderer.hpp
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#ifndef STAR_RENDERER_HPP
|
||||
#define STAR_RENDERER_HPP
|
||||
|
||||
#include "StarVariant.hpp"
|
||||
#include "StarImage.hpp"
|
||||
#include "StarPoly.hpp"
|
||||
#include "StarJson.hpp"
|
||||
#include "StarBiMap.hpp"
|
||||
#include "StarRefPtr.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_EXCEPTION(RendererException, StarException);
|
||||
|
||||
class Texture;
|
||||
typedef RefPtr<Texture> TexturePtr;
|
||||
|
||||
STAR_CLASS(TextureGroup);
|
||||
STAR_CLASS(RenderBuffer);
|
||||
STAR_CLASS(Renderer);
|
||||
|
||||
enum class TextureAddressing {
|
||||
Clamp,
|
||||
Wrap
|
||||
};
|
||||
extern EnumMap<TextureAddressing> const TextureAddressingNames;
|
||||
|
||||
enum class TextureFiltering {
|
||||
Nearest,
|
||||
Linear
|
||||
};
|
||||
extern EnumMap<TextureFiltering> const TextureFilteringNames;
|
||||
|
||||
// Medium is the maximum guaranteed texture group size
|
||||
// Where a Medium sized texture group is expected to fill a single page Large can be used,
|
||||
// but is not guaranteed to be supported by all systems.
|
||||
// Where Large sized textures are not supported, a Medium one is used
|
||||
enum class TextureGroupSize {
|
||||
Small,
|
||||
Medium,
|
||||
Large
|
||||
};
|
||||
|
||||
// Both screen coordinates and texture coordinates are in pixels from the
|
||||
// bottom left to top right.
|
||||
struct RenderVertex {
|
||||
Vec2F screenCoordinate;
|
||||
Vec2F textureCoordinate;
|
||||
Vec4B color;
|
||||
float param1;
|
||||
};
|
||||
|
||||
struct RenderTriangle {
|
||||
TexturePtr texture;
|
||||
RenderVertex a, b, c;
|
||||
};
|
||||
|
||||
struct RenderQuad {
|
||||
TexturePtr texture;
|
||||
RenderVertex a, b, c, d;
|
||||
};
|
||||
|
||||
struct RenderPoly {
|
||||
TexturePtr texture;
|
||||
List<RenderVertex> vertexes;
|
||||
};
|
||||
|
||||
RenderQuad renderTexturedRect(TexturePtr texture, Vec2F minScreen, float textureScale = 1.0f, Vec4B color = Vec4B::filled(255), float param1 = 0.0f);
|
||||
RenderQuad renderTexturedRect(TexturePtr texture, RectF const& screenCoords, Vec4B color = Vec4B::filled(255), float param1 = 0.0f);
|
||||
RenderQuad renderFlatRect(RectF const& rect, Vec4B color, float param1 = 0.0f);
|
||||
RenderPoly renderFlatPoly(PolyF const& poly, Vec4B color, float param1 = 0.0f);
|
||||
|
||||
typedef Variant<RenderTriangle, RenderQuad, RenderPoly> RenderPrimitive;
|
||||
|
||||
class Texture : public RefCounter {
|
||||
public:
|
||||
virtual ~Texture() = default;
|
||||
|
||||
virtual Vec2U size() const = 0;
|
||||
virtual TextureFiltering filtering() const = 0;
|
||||
virtual TextureAddressing addressing() const = 0;
|
||||
};
|
||||
|
||||
// Textures may be created individually, or in a texture group. Textures in
|
||||
// a texture group will be faster to render when rendered together, and will
|
||||
// use less texture memory when many small textures are in a common group.
|
||||
// Texture groups must all have the same texture parameters, and will always
|
||||
// use clamped texture addressing.
|
||||
class TextureGroup {
|
||||
public:
|
||||
virtual ~TextureGroup() = default;
|
||||
|
||||
virtual TextureFiltering filtering() const = 0;
|
||||
virtual TexturePtr create(Image const& texture) = 0;
|
||||
};
|
||||
|
||||
class RenderBuffer {
|
||||
public:
|
||||
virtual ~RenderBuffer() = default;
|
||||
|
||||
// Transforms the given primitives into a form suitable for the underlying
|
||||
// graphics system and stores it for fast replaying.
|
||||
virtual void set(List<RenderPrimitive> primitives) = 0;
|
||||
};
|
||||
|
||||
typedef Variant<bool, int, float, Vec2F, Vec3F, Vec4F> RenderEffectParameter;
|
||||
|
||||
class Renderer {
|
||||
public:
|
||||
virtual ~Renderer() = default;
|
||||
|
||||
virtual String rendererId() const = 0;
|
||||
virtual Vec2U screenSize() const = 0;
|
||||
|
||||
// The actual shaders used by this renderer will be in a default no effects
|
||||
// state when constructed, but can be overridden here. This config will be
|
||||
// specific to each type of renderer, so it will be necessary to key the
|
||||
// configuration off of the renderId string. This should not be called every
|
||||
// frame, because it will result in a recompile of the underlying shader set.
|
||||
virtual void setEffectConfig(Json const& effectConfig) = 0;
|
||||
|
||||
// The effect config will specify named parameters and textures which can be
|
||||
// set here.
|
||||
virtual void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) = 0;
|
||||
virtual void setEffectTexture(String const& textureName, Image const& image) = 0;
|
||||
|
||||
// Any further rendering will be scissored based on this rect, specified in
|
||||
// pixels
|
||||
virtual void setScissorRect(Maybe<RectI> const& scissorRect) = 0;
|
||||
|
||||
virtual TexturePtr createTexture(Image const& texture,
|
||||
TextureAddressing addressing = TextureAddressing::Clamp,
|
||||
TextureFiltering filtering = TextureFiltering::Nearest) = 0;
|
||||
virtual void setSizeLimitEnabled(bool enabled) = 0;
|
||||
virtual void setMultiTexturingEnabled(bool enabled) = 0;
|
||||
virtual TextureGroupPtr createTextureGroup(TextureGroupSize size = TextureGroupSize::Medium, TextureFiltering filtering = TextureFiltering::Nearest) = 0;
|
||||
virtual RenderBufferPtr createRenderBuffer() = 0;
|
||||
|
||||
virtual void render(RenderPrimitive primitive) = 0;
|
||||
virtual void renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation = Mat3F::identity()) = 0;
|
||||
|
||||
virtual void flush() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
828
source/application/StarRenderer_opengl20.cpp
Normal file
828
source/application/StarRenderer_opengl20.cpp
Normal file
|
|
@ -0,0 +1,828 @@
|
|||
#include "StarRenderer_opengl20.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarCasting.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
size_t const MultiTextureCount = 4;
|
||||
|
||||
char const* DefaultEffectConfig = R"JSON(
|
||||
{
|
||||
"vertexShader" : "
|
||||
#version 110
|
||||
|
||||
uniform vec2 textureSize0;
|
||||
uniform vec2 textureSize1;
|
||||
uniform vec2 textureSize2;
|
||||
uniform vec2 textureSize3;
|
||||
uniform vec2 screenSize;
|
||||
uniform mat3 vertexTransform;
|
||||
|
||||
attribute vec2 vertexPosition;
|
||||
attribute vec2 vertexTextureCoordinate;
|
||||
attribute float vertexTextureIndex;
|
||||
attribute vec4 vertexColor;
|
||||
attribute float vertexParam1;
|
||||
|
||||
varying vec2 fragmentTextureCoordinate;
|
||||
varying float fragmentTextureIndex;
|
||||
varying vec4 fragmentColor;
|
||||
|
||||
void main() {
|
||||
vec2 screenPosition = (vertexTransform * vec3(vertexPosition, 1.0)).xy;
|
||||
gl_Position = vec4(screenPosition / screenSize * 2.0 - 1.0, 0.0, 1.0);
|
||||
if (vertexTextureIndex > 2.9) {
|
||||
fragmentTextureCoordinate = vertexTextureCoordinate / textureSize3;
|
||||
} else if (vertexTextureIndex > 1.9) {
|
||||
fragmentTextureCoordinate = vertexTextureCoordinate / textureSize2;
|
||||
} else if (vertexTextureIndex > 0.9) {
|
||||
fragmentTextureCoordinate = vertexTextureCoordinate / textureSize1;
|
||||
} else {
|
||||
fragmentTextureCoordinate = vertexTextureCoordinate / textureSize0;
|
||||
}
|
||||
fragmentTextureIndex = vertexTextureIndex;
|
||||
fragmentColor = vertexColor;
|
||||
}
|
||||
",
|
||||
|
||||
"fragmentShader" : "
|
||||
#version 110
|
||||
|
||||
uniform sampler2D texture0;
|
||||
uniform sampler2D texture1;
|
||||
uniform sampler2D texture2;
|
||||
uniform sampler2D texture3;
|
||||
|
||||
varying vec2 fragmentTextureCoordinate;
|
||||
varying float fragmentTextureIndex;
|
||||
varying vec4 fragmentColor;
|
||||
|
||||
void main() {
|
||||
if (fragmentTextureIndex > 2.9) {
|
||||
gl_FragColor = texture2D(texture3, fragmentTextureCoordinate) * fragmentColor;
|
||||
} else if (fragmentTextureIndex > 1.9) {
|
||||
gl_FragColor = texture2D(texture2, fragmentTextureCoordinate) * fragmentColor;
|
||||
} else if (fragmentTextureIndex > 0.9) {
|
||||
gl_FragColor = texture2D(texture1, fragmentTextureCoordinate) * fragmentColor;
|
||||
} else {
|
||||
gl_FragColor = texture2D(texture0, fragmentTextureCoordinate) * fragmentColor;
|
||||
}
|
||||
}
|
||||
"
|
||||
}
|
||||
)JSON";
|
||||
|
||||
OpenGl20Renderer::OpenGl20Renderer() {
|
||||
if (glewInit() != GLEW_OK)
|
||||
throw RendererException("Could not initialize GLEW");
|
||||
|
||||
if (!GLEW_VERSION_2_0)
|
||||
throw RendererException("OpenGL 2.0 not available!");
|
||||
|
||||
Logger::info("OpenGL version: '%s' vendor: '%s' renderer: '%s' shader: '%s'",
|
||||
glGetString(GL_VERSION),
|
||||
glGetString(GL_VENDOR),
|
||||
glGetString(GL_RENDERER),
|
||||
glGetString(GL_SHADING_LANGUAGE_VERSION));
|
||||
|
||||
glClearColor(0.0, 0.0, 0.0, 1.0);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
m_whiteTexture = createGlTexture(Image::filled({1, 1}, Vec4B(255, 255, 255, 255), PixelFormat::RGBA32),
|
||||
TextureAddressing::Clamp,
|
||||
TextureFiltering::Nearest);
|
||||
m_immediateRenderBuffer = createGlRenderBuffer();
|
||||
|
||||
setEffectConfig(Json::parse(DefaultEffectConfig));
|
||||
|
||||
m_limitTextureGroupSize = false;
|
||||
m_useMultiTexturing = true;
|
||||
|
||||
logGlErrorSummary("OpenGL errors during renderer initialization");
|
||||
}
|
||||
|
||||
OpenGl20Renderer::~OpenGl20Renderer() {
|
||||
glDeleteProgram(m_program);
|
||||
|
||||
logGlErrorSummary("OpenGL errors during shutdown");
|
||||
}
|
||||
|
||||
String OpenGl20Renderer::rendererId() const {
|
||||
return "OpenGL20";
|
||||
}
|
||||
|
||||
Vec2U OpenGl20Renderer::screenSize() const {
|
||||
return m_screenSize;
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::setEffectConfig(Json const& effectConfig) {
|
||||
flushImmediatePrimitives();
|
||||
|
||||
GLint status = 0;
|
||||
char logBuffer[1024];
|
||||
|
||||
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||||
String vertexSource = effectConfig.getString("vertexShader");
|
||||
char const* vertexSourcePtr = vertexSource.utf8Ptr();
|
||||
glShaderSource(vertexShader, 1, &vertexSourcePtr, NULL);
|
||||
glCompileShader(vertexShader);
|
||||
|
||||
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status);
|
||||
if (!status) {
|
||||
glGetShaderInfoLog(vertexShader, sizeof(logBuffer), NULL, logBuffer);
|
||||
throw RendererException(strf("Failed to compile vertex shader: %s\n", logBuffer));
|
||||
}
|
||||
|
||||
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
String fragmentSource = effectConfig.getString("fragmentShader");
|
||||
char const* fragmentSourcePtr = fragmentSource.utf8Ptr();
|
||||
glShaderSource(fragmentShader, 1, &fragmentSourcePtr, NULL);
|
||||
glCompileShader(fragmentShader);
|
||||
|
||||
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status);
|
||||
if (!status) {
|
||||
glGetShaderInfoLog(fragmentShader, sizeof(logBuffer), NULL, logBuffer);
|
||||
throw RendererException(strf("Failed to compile fragment shader: %s\n", logBuffer));
|
||||
}
|
||||
|
||||
GLuint program = glCreateProgram();
|
||||
|
||||
glAttachShader(program, vertexShader);
|
||||
glAttachShader(program, fragmentShader);
|
||||
glLinkProgram(program);
|
||||
|
||||
glDeleteShader(vertexShader);
|
||||
glDeleteShader(fragmentShader);
|
||||
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &status);
|
||||
if (!status) {
|
||||
glGetProgramInfoLog(program, sizeof(logBuffer), NULL, logBuffer);
|
||||
glDeleteProgram(program);
|
||||
throw RendererException(strf("Failed to link program: %s\n", logBuffer));
|
||||
}
|
||||
|
||||
if (m_program != 0)
|
||||
glDeleteProgram(m_program);
|
||||
m_program = program;
|
||||
glUseProgram(m_program);
|
||||
|
||||
m_positionAttribute = glGetAttribLocation(m_program, "vertexPosition");
|
||||
m_texCoordAttribute = glGetAttribLocation(m_program, "vertexTextureCoordinate");
|
||||
m_texIndexAttribute = glGetAttribLocation(m_program, "vertexTextureIndex");
|
||||
m_colorAttribute = glGetAttribLocation(m_program, "vertexColor");
|
||||
m_param1Attribute = glGetAttribLocation(m_program, "vertexParam1");
|
||||
|
||||
m_textureUniforms.clear();
|
||||
m_textureSizeUniforms.clear();
|
||||
for (size_t i = 0; i < MultiTextureCount; ++i) {
|
||||
m_textureUniforms.append(glGetUniformLocation(m_program, strf("texture%s", i).c_str()));
|
||||
m_textureSizeUniforms.append(glGetUniformLocation(m_program, strf("textureSize%s", i).c_str()));
|
||||
}
|
||||
m_screenSizeUniform = glGetUniformLocation(m_program, "screenSize");
|
||||
m_vertexTransformUniform = glGetUniformLocation(m_program, "vertexTransform");
|
||||
|
||||
for (size_t i = 0; i < MultiTextureCount; ++i) {
|
||||
glUniform1i(m_textureUniforms[i], i);
|
||||
}
|
||||
glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]);
|
||||
|
||||
m_effectParameters.clear();
|
||||
|
||||
for (auto const& p : effectConfig.getObject("effectParameters", {})) {
|
||||
EffectParameter effectParameter;
|
||||
|
||||
effectParameter.parameterUniform = glGetUniformLocation(m_program, p.second.getString("uniform").utf8Ptr());
|
||||
if (effectParameter.parameterUniform == -1) {
|
||||
Logger::warn("OpenGL20 effect parameter '%s' has no associated uniform, skipping", p.first);
|
||||
} else {
|
||||
String type = p.second.getString("type");
|
||||
if (type == "bool") {
|
||||
effectParameter.parameterType = RenderEffectParameter::typeIndexOf<bool>();
|
||||
} else if (type == "int") {
|
||||
effectParameter.parameterType = RenderEffectParameter::typeIndexOf<int>();
|
||||
} else if (type == "float") {
|
||||
effectParameter.parameterType = RenderEffectParameter::typeIndexOf<float>();
|
||||
} else if (type == "vec2") {
|
||||
effectParameter.parameterType = RenderEffectParameter::typeIndexOf<Vec2F>();
|
||||
} else if (type == "vec3") {
|
||||
effectParameter.parameterType = RenderEffectParameter::typeIndexOf<Vec3F>();
|
||||
} else if (type == "vec4") {
|
||||
effectParameter.parameterType = RenderEffectParameter::typeIndexOf<Vec4F>();
|
||||
} else {
|
||||
throw RendererException::format("Unrecognized effect parameter type '%s'", type);
|
||||
}
|
||||
|
||||
m_effectParameters[p.first] = effectParameter;
|
||||
|
||||
if (Json def = p.second.get("default", {})) {
|
||||
if (type == "bool") {
|
||||
setEffectParameter(p.first, def.toBool());
|
||||
} else if (type == "int") {
|
||||
setEffectParameter(p.first, (int)def.toInt());
|
||||
} else if (type == "float") {
|
||||
setEffectParameter(p.first, def.toFloat());
|
||||
} else if (type == "vec2") {
|
||||
setEffectParameter(p.first, jsonToVec2F(def));
|
||||
} else if (type == "vec3") {
|
||||
setEffectParameter(p.first, jsonToVec3F(def));
|
||||
} else if (type == "vec4") {
|
||||
setEffectParameter(p.first, jsonToVec4F(def));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_effectTextures.clear();
|
||||
|
||||
// Assign each texture parameter a texture unit starting with MultiTextureCount, the first
|
||||
// few texture units are used by the primary textures being drawn. Currently,
|
||||
// maximum texture units are not checked.
|
||||
unsigned parameterTextureUnit = MultiTextureCount;
|
||||
|
||||
for (auto const& p : effectConfig.getObject("effectTextures", {})) {
|
||||
EffectTexture effectTexture;
|
||||
effectTexture.textureUniform = glGetUniformLocation(m_program, p.second.getString("textureUniform").utf8Ptr());
|
||||
if (effectTexture.textureUniform == -1) {
|
||||
Logger::warn("OpenGL20 effect parameter '%s' has no associated uniform, skipping", p.first);
|
||||
} else {
|
||||
effectTexture.textureUnit = parameterTextureUnit++;
|
||||
glUniform1i(effectTexture.textureUniform, effectTexture.textureUnit);
|
||||
|
||||
effectTexture.textureAddressing = TextureAddressingNames.getLeft(p.second.getString("textureAddressing", "clamp"));
|
||||
effectTexture.textureFiltering = TextureFilteringNames.getLeft(p.second.getString("textureFiltering", "nearest"));
|
||||
if (auto tsu = p.second.optString("textureSizeUniform")) {
|
||||
effectTexture.textureSizeUniform = glGetUniformLocation(m_program, tsu->utf8Ptr());
|
||||
if (effectTexture.textureSizeUniform == -1)
|
||||
Logger::warn("OpenGL20 effect parameter '%s' has textureSizeUniform '%s' with no associated uniform", p.first, *tsu);
|
||||
}
|
||||
|
||||
m_effectTextures[p.first] = effectTexture;
|
||||
}
|
||||
}
|
||||
|
||||
if (DebugEnabled)
|
||||
logGlErrorSummary("OpenGL errors setting effect config");
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::setEffectParameter(String const& parameterName, RenderEffectParameter const& value) {
|
||||
auto ptr = m_effectParameters.ptr(parameterName);
|
||||
if (!ptr || (ptr->parameterValue && *ptr->parameterValue == value))
|
||||
return;
|
||||
|
||||
if (ptr->parameterType != value.typeIndex())
|
||||
throw RendererException::format("OpenGL20Renderer::setEffectParameter '%s' parameter type mismatch", parameterName);
|
||||
|
||||
flushImmediatePrimitives();
|
||||
|
||||
if (auto v = value.ptr<bool>())
|
||||
glUniform1i(ptr->parameterUniform, *v);
|
||||
else if (auto v = value.ptr<int>())
|
||||
glUniform1i(ptr->parameterUniform, *v);
|
||||
else if (auto v = value.ptr<float>())
|
||||
glUniform1f(ptr->parameterUniform, *v);
|
||||
else if (auto v = value.ptr<Vec2F>())
|
||||
glUniform2f(ptr->parameterUniform, (*v)[0], (*v)[1]);
|
||||
else if (auto v = value.ptr<Vec3F>())
|
||||
glUniform3f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2]);
|
||||
else if (auto v = value.ptr<Vec4F>())
|
||||
glUniform4f(ptr->parameterUniform, (*v)[0], (*v)[1], (*v)[2], (*v)[3]);
|
||||
|
||||
ptr->parameterValue = value;
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::setEffectTexture(String const& textureName, Image const& image) {
|
||||
auto ptr = m_effectTextures.ptr(textureName);
|
||||
if (!ptr)
|
||||
return;
|
||||
|
||||
flushImmediatePrimitives();
|
||||
|
||||
if (!ptr->textureValue || ptr->textureValue->textureId == 0) {
|
||||
ptr->textureValue = createGlTexture(image, ptr->textureAddressing, ptr->textureFiltering);
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, ptr->textureValue->textureId);
|
||||
ptr->textureValue->textureSize = image.size();
|
||||
uploadTextureImage(image.pixelFormat(), image.size(), image.data());
|
||||
}
|
||||
|
||||
if (ptr->textureSizeUniform != -1) {
|
||||
auto textureSize = ptr->textureValue->glTextureSize();
|
||||
glUniform2f(ptr->textureSizeUniform, textureSize[0], textureSize[1]);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::setScissorRect(Maybe<RectI> const& scissorRect) {
|
||||
if (scissorRect == m_scissorRect)
|
||||
return;
|
||||
|
||||
flushImmediatePrimitives();
|
||||
|
||||
m_scissorRect = scissorRect;
|
||||
if (m_scissorRect) {
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(m_scissorRect->xMin(), m_scissorRect->yMin(), m_scissorRect->width(), m_scissorRect->height());
|
||||
} else {
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
TexturePtr OpenGl20Renderer::createTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering) {
|
||||
return createGlTexture(texture, addressing, filtering);
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::setSizeLimitEnabled(bool enabled) {
|
||||
m_limitTextureGroupSize = enabled;
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::setMultiTexturingEnabled(bool enabled) {
|
||||
m_useMultiTexturing = enabled;
|
||||
}
|
||||
|
||||
TextureGroupPtr OpenGl20Renderer::createTextureGroup(TextureGroupSize textureSize, TextureFiltering filtering) {
|
||||
int maxTextureSize;
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
|
||||
|
||||
// Large texture sizes are not always supported
|
||||
if (textureSize == TextureGroupSize::Large && (m_limitTextureGroupSize || maxTextureSize < 4096))
|
||||
textureSize = TextureGroupSize::Medium;
|
||||
|
||||
unsigned atlasNumCells;
|
||||
if (textureSize == TextureGroupSize::Large)
|
||||
atlasNumCells = 256;
|
||||
else if (textureSize == TextureGroupSize::Medium)
|
||||
atlasNumCells = 128;
|
||||
else // TextureGroupSize::Small
|
||||
atlasNumCells = 64;
|
||||
|
||||
Logger::info("detected supported OpenGL texture size %s, using atlasNumCells %s", maxTextureSize, atlasNumCells);
|
||||
|
||||
auto glTextureGroup = make_shared<GlTextureGroup>(atlasNumCells);
|
||||
glTextureGroup->textureAtlasSet.textureFiltering = filtering;
|
||||
m_liveTextureGroups.append(glTextureGroup);
|
||||
return glTextureGroup;
|
||||
}
|
||||
|
||||
RenderBufferPtr OpenGl20Renderer::createRenderBuffer() {
|
||||
return createGlRenderBuffer();
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::render(RenderPrimitive primitive) {
|
||||
m_immediatePrimitives.append(move(primitive));
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation) {
|
||||
flushImmediatePrimitives();
|
||||
renderGlBuffer(*convert<GlRenderBuffer>(renderBuffer.get()), transformation);
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::flush() {
|
||||
flushImmediatePrimitives();
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::setScreenSize(Vec2U screenSize) {
|
||||
m_screenSize = screenSize;
|
||||
glViewport(0, 0, m_screenSize[0], m_screenSize[1]);
|
||||
glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]);
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::startFrame() {
|
||||
if (m_scissorRect)
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (m_scissorRect)
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::finishFrame() {
|
||||
flushImmediatePrimitives();
|
||||
// Make sure that the immediate render buffer doesn't needlessly lock texutres
|
||||
// from being compressed.
|
||||
m_immediateRenderBuffer->set({});
|
||||
|
||||
filter(m_liveTextureGroups, [](auto const& p) {
|
||||
unsigned const CompressionsPerFrame = 1;
|
||||
|
||||
if (!p.unique() || p->textureAtlasSet.totalTextures() > 0) {
|
||||
p->textureAtlasSet.compressionPass(CompressionsPerFrame);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (DebugEnabled)
|
||||
logGlErrorSummary("OpenGL errors this frame");
|
||||
}
|
||||
|
||||
OpenGl20Renderer::GlTextureAtlasSet::GlTextureAtlasSet(unsigned atlasNumCells)
|
||||
: TextureAtlasSet(16, atlasNumCells) {}
|
||||
|
||||
GLuint OpenGl20Renderer::GlTextureAtlasSet::createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) {
|
||||
GLuint glTextureId;
|
||||
glGenTextures(1, &glTextureId);
|
||||
if (glTextureId == 0)
|
||||
throw RendererException("Could not generate texture in OpenGL20Renderer::TextureGroup::createAtlasTexture()");
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, glTextureId);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
if (textureFiltering == TextureFiltering::Nearest) {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
} else {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
}
|
||||
|
||||
uploadTextureImage(pixelFormat, size, nullptr);
|
||||
return glTextureId;
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::GlTextureAtlasSet::destroyAtlasTexture(GLuint const& glTexture) {
|
||||
glDeleteTextures(1, &glTexture);
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::GlTextureAtlasSet::copyAtlasPixels(
|
||||
GLuint const& glTexture, Vec2U const& bottomLeft, Image const& image) {
|
||||
glBindTexture(GL_TEXTURE_2D, glTexture);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
if (image.pixelFormat() == PixelFormat::RGB24) {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_RGB, GL_UNSIGNED_BYTE, image.data());
|
||||
} else if (image.pixelFormat() == PixelFormat::RGBA32) {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.data());
|
||||
} else if (image.pixelFormat() == PixelFormat::BGR24) {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_BGR, GL_UNSIGNED_BYTE, image.data());
|
||||
} else if (image.pixelFormat() == PixelFormat::BGRA32) {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, bottomLeft[0], bottomLeft[1], image.width(), image.height(), GL_BGRA, GL_UNSIGNED_BYTE, image.data());
|
||||
} else {
|
||||
throw RendererException("Unsupported texture format in OpenGL20Renderer::TextureGroup::copyAtlasPixels");
|
||||
}
|
||||
}
|
||||
|
||||
OpenGl20Renderer::GlTextureGroup::GlTextureGroup(unsigned atlasNumCells)
|
||||
: textureAtlasSet(atlasNumCells) {}
|
||||
|
||||
OpenGl20Renderer::GlTextureGroup::~GlTextureGroup() {
|
||||
textureAtlasSet.reset();
|
||||
}
|
||||
|
||||
TextureFiltering OpenGl20Renderer::GlTextureGroup::filtering() const {
|
||||
return textureAtlasSet.textureFiltering;
|
||||
}
|
||||
|
||||
TexturePtr OpenGl20Renderer::GlTextureGroup::create(Image const& texture) {
|
||||
// If the image is empty, or would not fit in the texture atlas with border
|
||||
// pixels, just create a regular texture
|
||||
Vec2U atlasTextureSize = textureAtlasSet.atlasTextureSize();
|
||||
if (texture.empty() || texture.width() + 2 > atlasTextureSize[0] || texture.height() + 2 > atlasTextureSize[1])
|
||||
return createGlTexture(texture, TextureAddressing::Clamp, textureAtlasSet.textureFiltering);
|
||||
|
||||
auto glGroupedTexture = make_ref<GlGroupedTexture>();
|
||||
glGroupedTexture->parentGroup = shared_from_this();
|
||||
glGroupedTexture->parentAtlasTexture = textureAtlasSet.addTexture(texture);
|
||||
|
||||
return glGroupedTexture;
|
||||
}
|
||||
|
||||
OpenGl20Renderer::GlGroupedTexture::~GlGroupedTexture() {
|
||||
if (parentAtlasTexture)
|
||||
parentGroup->textureAtlasSet.freeTexture(parentAtlasTexture);
|
||||
}
|
||||
|
||||
Vec2U OpenGl20Renderer::GlGroupedTexture::size() const {
|
||||
return parentAtlasTexture->imageSize();
|
||||
}
|
||||
|
||||
TextureFiltering OpenGl20Renderer::GlGroupedTexture::filtering() const {
|
||||
return parentGroup->filtering();
|
||||
}
|
||||
|
||||
TextureAddressing OpenGl20Renderer::GlGroupedTexture::addressing() const {
|
||||
return TextureAddressing::Clamp;
|
||||
}
|
||||
|
||||
GLuint OpenGl20Renderer::GlGroupedTexture::glTextureId() const {
|
||||
return parentAtlasTexture->atlasTexture();
|
||||
}
|
||||
|
||||
Vec2U OpenGl20Renderer::GlGroupedTexture::glTextureSize() const {
|
||||
return parentGroup->textureAtlasSet.atlasTextureSize();
|
||||
}
|
||||
|
||||
Vec2U OpenGl20Renderer::GlGroupedTexture::glTextureCoordinateOffset() const {
|
||||
return parentAtlasTexture->atlasTextureCoordinates().min();
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::GlGroupedTexture::incrementBufferUseCount() {
|
||||
if (bufferUseCount == 0)
|
||||
parentAtlasTexture->setLocked(true);
|
||||
++bufferUseCount;
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::GlGroupedTexture::decrementBufferUseCount() {
|
||||
starAssert(bufferUseCount != 0);
|
||||
if (bufferUseCount == 1)
|
||||
parentAtlasTexture->setLocked(false);
|
||||
--bufferUseCount;
|
||||
}
|
||||
|
||||
OpenGl20Renderer::GlLoneTexture::~GlLoneTexture() {
|
||||
if (textureId != 0)
|
||||
glDeleteTextures(1, &textureId);
|
||||
}
|
||||
|
||||
Vec2U OpenGl20Renderer::GlLoneTexture::size() const {
|
||||
return textureSize;
|
||||
}
|
||||
|
||||
TextureFiltering OpenGl20Renderer::GlLoneTexture::filtering() const {
|
||||
return textureFiltering;
|
||||
}
|
||||
|
||||
TextureAddressing OpenGl20Renderer::GlLoneTexture::addressing() const {
|
||||
return textureAddressing;
|
||||
}
|
||||
|
||||
GLuint OpenGl20Renderer::GlLoneTexture::glTextureId() const {
|
||||
return textureId;
|
||||
}
|
||||
|
||||
Vec2U OpenGl20Renderer::GlLoneTexture::glTextureSize() const {
|
||||
return textureSize;
|
||||
}
|
||||
|
||||
Vec2U OpenGl20Renderer::GlLoneTexture::glTextureCoordinateOffset() const {
|
||||
return Vec2U();
|
||||
}
|
||||
|
||||
OpenGl20Renderer::GlRenderBuffer::~GlRenderBuffer() {
|
||||
for (auto const& texture : usedTextures) {
|
||||
if (auto gt = as<GlGroupedTexture>(texture.get()))
|
||||
gt->decrementBufferUseCount();
|
||||
}
|
||||
for (auto const& vb : vertexBuffers)
|
||||
glDeleteBuffers(1, &vb.vertexBuffer);
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::GlRenderBuffer::set(List<RenderPrimitive> primitives) {
|
||||
for (auto const& texture : usedTextures) {
|
||||
if (auto gt = as<GlGroupedTexture>(texture.get()))
|
||||
gt->decrementBufferUseCount();
|
||||
}
|
||||
usedTextures.clear();
|
||||
|
||||
auto oldVertexBuffers = take(vertexBuffers);
|
||||
|
||||
List<GLuint> currentTextures;
|
||||
List<Vec2U> currentTextureSizes;
|
||||
size_t currentVertexCount = 0;
|
||||
|
||||
auto finishCurrentBuffer = [&]() {
|
||||
if (currentVertexCount > 0) {
|
||||
GlVertexBuffer vb;
|
||||
for (size_t i = 0; i < currentTextures.size(); ++i) {
|
||||
vb.textures.append(GlVertexBufferTexture{currentTextures[i], currentTextureSizes[i]});
|
||||
}
|
||||
vb.vertexCount = currentVertexCount;
|
||||
if (!oldVertexBuffers.empty()) {
|
||||
auto oldVb = oldVertexBuffers.takeLast();
|
||||
vb.vertexBuffer = oldVb.vertexBuffer;
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vb.vertexBuffer);
|
||||
if (oldVb.vertexCount >= vb.vertexCount)
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, accumulationBuffer.size(), accumulationBuffer.ptr());
|
||||
else
|
||||
glBufferData(GL_ARRAY_BUFFER, accumulationBuffer.size(), accumulationBuffer.ptr(), GL_STREAM_DRAW);
|
||||
} else {
|
||||
glGenBuffers(1, &vb.vertexBuffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vb.vertexBuffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, accumulationBuffer.size(), accumulationBuffer.ptr(), GL_STREAM_DRAW);
|
||||
}
|
||||
|
||||
vertexBuffers.append(vb);
|
||||
|
||||
currentTextures.clear();
|
||||
currentTextureSizes.clear();
|
||||
accumulationBuffer.clear();
|
||||
currentVertexCount = 0;
|
||||
}
|
||||
};
|
||||
|
||||
auto textureCount = useMultiTexturing ? MultiTextureCount : 1;
|
||||
auto addCurrentTexture = [&](TexturePtr texture) -> pair<uint8_t, Vec2F> {
|
||||
if (!texture)
|
||||
texture = whiteTexture;
|
||||
|
||||
auto glTexture = as<GlTexture>(texture.get());
|
||||
GLuint glTextureId = glTexture->glTextureId();
|
||||
|
||||
auto textureIndex = currentTextures.indexOf(glTextureId);
|
||||
if (textureIndex == NPos) {
|
||||
if (currentTextures.size() >= textureCount)
|
||||
finishCurrentBuffer();
|
||||
|
||||
textureIndex = currentTextures.size();
|
||||
currentTextures.append(glTextureId);
|
||||
currentTextureSizes.append(glTexture->glTextureSize());
|
||||
}
|
||||
|
||||
if (auto gt = as<GlGroupedTexture>(texture.get()))
|
||||
gt->incrementBufferUseCount();
|
||||
usedTextures.add(move(texture));
|
||||
|
||||
return {float(textureIndex), Vec2F(glTexture->glTextureCoordinateOffset())};
|
||||
};
|
||||
|
||||
auto appendBufferVertex = [&](RenderVertex v, float textureIndex, Vec2F textureCoordinateOffset) {
|
||||
GlRenderVertex glv {
|
||||
v.screenCoordinate,
|
||||
v.textureCoordinate + textureCoordinateOffset,
|
||||
textureIndex,
|
||||
v.color,
|
||||
v.param1
|
||||
};
|
||||
accumulationBuffer.append((char const*)&glv, sizeof(GlRenderVertex));
|
||||
++currentVertexCount;
|
||||
};
|
||||
|
||||
for (auto& primitive : primitives) {
|
||||
float textureIndex;
|
||||
Vec2F textureOffset;
|
||||
if (auto tri = primitive.ptr<RenderTriangle>()) {
|
||||
tie(textureIndex, textureOffset) = addCurrentTexture(move(tri->texture));
|
||||
|
||||
appendBufferVertex(tri->a, textureIndex, textureOffset);
|
||||
appendBufferVertex(tri->b, textureIndex, textureOffset);
|
||||
appendBufferVertex(tri->c, textureIndex, textureOffset);
|
||||
|
||||
} else if (auto quad = primitive.ptr<RenderQuad>()) {
|
||||
tie(textureIndex, textureOffset) = addCurrentTexture(move(quad->texture));
|
||||
|
||||
appendBufferVertex(quad->a, textureIndex, textureOffset);
|
||||
appendBufferVertex(quad->b, textureIndex, textureOffset);
|
||||
appendBufferVertex(quad->c, textureIndex, textureOffset);
|
||||
|
||||
appendBufferVertex(quad->a, textureIndex, textureOffset);
|
||||
appendBufferVertex(quad->c, textureIndex, textureOffset);
|
||||
appendBufferVertex(quad->d, textureIndex, textureOffset);
|
||||
|
||||
} else if (auto poly = primitive.ptr<RenderPoly>()) {
|
||||
if (poly->vertexes.size() > 2) {
|
||||
tie(textureIndex, textureOffset) = addCurrentTexture(move(poly->texture));
|
||||
for (size_t i = 1; i < poly->vertexes.size() - 1; ++i) {
|
||||
appendBufferVertex(poly->vertexes[0], textureIndex, textureOffset);
|
||||
appendBufferVertex(poly->vertexes[i], textureIndex, textureOffset);
|
||||
appendBufferVertex(poly->vertexes[i + 1], textureIndex, textureOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
finishCurrentBuffer();
|
||||
|
||||
for (auto const& vb : oldVertexBuffers)
|
||||
glDeleteBuffers(1, &vb.vertexBuffer);
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::logGlErrorSummary(String prefix) {
|
||||
List<GLenum> errors;
|
||||
while (GLenum error = glGetError())
|
||||
errors.append(error);
|
||||
|
||||
if (!errors.empty()) {
|
||||
String errorMessage = move(prefix);
|
||||
errorMessage.append(": ");
|
||||
for (auto const& error : errors) {
|
||||
if (error == GL_INVALID_ENUM) {
|
||||
errorMessage += " GL_INVALID_ENUM";
|
||||
} else if (error == GL_INVALID_VALUE) {
|
||||
errorMessage += " GL_INVALID_VALUE";
|
||||
} else if (error == GL_INVALID_OPERATION) {
|
||||
errorMessage += " GL_INVALID_OPERATION";
|
||||
} else if (error == GL_INVALID_FRAMEBUFFER_OPERATION) {
|
||||
errorMessage += " GL_INVALID_FRAMEBUFFER_OPERATION";
|
||||
} else if (error == GL_OUT_OF_MEMORY) {
|
||||
errorMessage += " GL_OUT_OF_MEMORY";
|
||||
} else if (error == GL_STACK_UNDERFLOW) {
|
||||
errorMessage += " GL_STACK_UNDERFLOW";
|
||||
} else if (error == GL_STACK_OVERFLOW) {
|
||||
errorMessage += " GL_STACK_OVERFLOW";
|
||||
} else {
|
||||
errorMessage += " <UNRECOGNIZED GL ERROR>";
|
||||
}
|
||||
}
|
||||
Logger::error(errorMessage.utf8Ptr());
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data) {
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
if (pixelFormat == PixelFormat::RGB24) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size[0], size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, data);
|
||||
} else if (pixelFormat == PixelFormat::RGBA32) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size[0], size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||
} else if (pixelFormat == PixelFormat::BGR24) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_BGR, size[0], size[1], 0, GL_BGR, GL_UNSIGNED_BYTE, data);
|
||||
} else if (pixelFormat == PixelFormat::BGRA32) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA, size[0], size[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||
} else {
|
||||
starAssert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::flushImmediatePrimitives() {
|
||||
if (m_immediatePrimitives.empty())
|
||||
return;
|
||||
|
||||
m_immediateRenderBuffer->set(take(m_immediatePrimitives));
|
||||
renderGlBuffer(*m_immediateRenderBuffer, Mat3F::identity());
|
||||
}
|
||||
|
||||
auto OpenGl20Renderer::createGlTexture(Image const& image, TextureAddressing addressing, TextureFiltering filtering)
|
||||
-> RefPtr<GlLoneTexture> {
|
||||
auto glLoneTexture = make_ref<GlLoneTexture>();
|
||||
glLoneTexture->textureFiltering = filtering;
|
||||
glLoneTexture->textureAddressing = addressing;
|
||||
glLoneTexture->textureSize = image.size();
|
||||
|
||||
if (image.empty())
|
||||
return glLoneTexture;
|
||||
|
||||
glGenTextures(1, &glLoneTexture->textureId);
|
||||
if (glLoneTexture->textureId == 0)
|
||||
throw RendererException("Could not generate texture in OpenGL20Renderer::createGlTexture");
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, glLoneTexture->textureId);
|
||||
|
||||
if (addressing == TextureAddressing::Clamp) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
} else {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
}
|
||||
|
||||
if (filtering == TextureFiltering::Nearest) {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
} else {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
}
|
||||
|
||||
uploadTextureImage(image.pixelFormat(), image.size(), image.data());
|
||||
|
||||
return glLoneTexture;
|
||||
}
|
||||
|
||||
auto OpenGl20Renderer::createGlRenderBuffer() -> shared_ptr<GlRenderBuffer> {
|
||||
auto glrb = make_shared<GlRenderBuffer>();
|
||||
glrb->whiteTexture = m_whiteTexture;
|
||||
glrb->useMultiTexturing = m_useMultiTexturing;
|
||||
return glrb;
|
||||
}
|
||||
|
||||
void OpenGl20Renderer::renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F const& transformation) {
|
||||
for (auto const& vb : renderBuffer.vertexBuffers) {
|
||||
glUniformMatrix3fv(m_vertexTransformUniform, 1, GL_TRUE, transformation.ptr());
|
||||
|
||||
for (size_t i = 0; i < vb.textures.size(); ++i) {
|
||||
glUniform2f(m_textureSizeUniforms[i], vb.textures[i].size[0], vb.textures[i].size[1]);
|
||||
glActiveTexture(GL_TEXTURE0 + i);
|
||||
glBindTexture(GL_TEXTURE_2D, vb.textures[i].texture);
|
||||
}
|
||||
|
||||
for (auto const& p : m_effectTextures) {
|
||||
if (p.second.textureValue) {
|
||||
glActiveTexture(GL_TEXTURE0 + p.second.textureUnit);
|
||||
glBindTexture(GL_TEXTURE_2D, p.second.textureValue->textureId);
|
||||
}
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vb.vertexBuffer);
|
||||
|
||||
glEnableVertexAttribArray(m_positionAttribute);
|
||||
glEnableVertexAttribArray(m_texCoordAttribute);
|
||||
glEnableVertexAttribArray(m_texIndexAttribute);
|
||||
glEnableVertexAttribArray(m_colorAttribute);
|
||||
glEnableVertexAttribArray(m_param1Attribute);
|
||||
|
||||
glVertexAttribPointer(m_positionAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, screenCoordinate));
|
||||
glVertexAttribPointer(m_texCoordAttribute, 2, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, textureCoordinate));
|
||||
glVertexAttribPointer(m_texIndexAttribute, 1, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, textureIndex));
|
||||
glVertexAttribPointer(m_colorAttribute, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, color));
|
||||
glVertexAttribPointer(m_param1Attribute, 1, GL_FLOAT, GL_FALSE, sizeof(GlRenderVertex), (GLvoid*)offsetof(GlRenderVertex, param1));
|
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, vb.vertexCount);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
199
source/application/StarRenderer_opengl20.hpp
Normal file
199
source/application/StarRenderer_opengl20.hpp
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
#ifndef STAR_RENDERER_OPENGL_HPP
|
||||
#define STAR_RENDERER_OPENGL_HPP
|
||||
|
||||
#include "StarTextureAtlas.hpp"
|
||||
#include "StarRenderer.hpp"
|
||||
|
||||
#include "GL/glew.h"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(OpenGl20Renderer);
|
||||
|
||||
// OpenGL 2.0 implementation of Renderer. OpenGL context must be created and
|
||||
// active during construction, destruction, and all method calls.
|
||||
class OpenGl20Renderer : public Renderer {
|
||||
public:
|
||||
OpenGl20Renderer();
|
||||
~OpenGl20Renderer();
|
||||
|
||||
String rendererId() const override;
|
||||
Vec2U screenSize() const override;
|
||||
|
||||
void setEffectConfig(Json const& effectConfig) override;
|
||||
void setEffectParameter(String const& parameterName, RenderEffectParameter const& parameter) override;
|
||||
void setEffectTexture(String const& textureName, Image const& image) override;
|
||||
|
||||
void setScissorRect(Maybe<RectI> const& scissorRect) override;
|
||||
|
||||
TexturePtr createTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering) override;
|
||||
void setSizeLimitEnabled(bool enabled) override;
|
||||
void setMultiTexturingEnabled(bool enabled) override;
|
||||
TextureGroupPtr createTextureGroup(TextureGroupSize size, TextureFiltering filtering) override;
|
||||
RenderBufferPtr createRenderBuffer() override;
|
||||
|
||||
void render(RenderPrimitive primitive) override;
|
||||
void renderBuffer(RenderBufferPtr const& renderBuffer, Mat3F const& transformation) override;
|
||||
|
||||
void flush() override;
|
||||
|
||||
void setScreenSize(Vec2U screenSize);
|
||||
|
||||
void startFrame();
|
||||
void finishFrame();
|
||||
|
||||
private:
|
||||
struct GlTextureAtlasSet : public TextureAtlasSet<GLuint> {
|
||||
public:
|
||||
GlTextureAtlasSet(unsigned atlasNumCells);
|
||||
|
||||
GLuint createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) override;
|
||||
void destroyAtlasTexture(GLuint const& glTexture) override;
|
||||
void copyAtlasPixels(GLuint const& glTexture, Vec2U const& bottomLeft, Image const& image) override;
|
||||
|
||||
TextureFiltering textureFiltering;
|
||||
};
|
||||
|
||||
struct GlTextureGroup : enable_shared_from_this<GlTextureGroup>, public TextureGroup {
|
||||
GlTextureGroup(unsigned atlasNumCells);
|
||||
~GlTextureGroup();
|
||||
|
||||
TextureFiltering filtering() const override;
|
||||
TexturePtr create(Image const& texture) override;
|
||||
|
||||
GlTextureAtlasSet textureAtlasSet;
|
||||
};
|
||||
|
||||
struct GlTexture : public Texture {
|
||||
virtual GLuint glTextureId() const = 0;
|
||||
virtual Vec2U glTextureSize() const = 0;
|
||||
virtual Vec2U glTextureCoordinateOffset() const = 0;
|
||||
};
|
||||
|
||||
struct GlGroupedTexture : public GlTexture {
|
||||
~GlGroupedTexture();
|
||||
|
||||
Vec2U size() const override;
|
||||
TextureFiltering filtering() const override;
|
||||
TextureAddressing addressing() const override;
|
||||
|
||||
GLuint glTextureId() const override;
|
||||
Vec2U glTextureSize() const override;
|
||||
Vec2U glTextureCoordinateOffset() const override;
|
||||
|
||||
void incrementBufferUseCount();
|
||||
void decrementBufferUseCount();
|
||||
|
||||
unsigned bufferUseCount = 0;
|
||||
shared_ptr<GlTextureGroup> parentGroup;
|
||||
GlTextureAtlasSet::TextureHandle parentAtlasTexture = nullptr;
|
||||
};
|
||||
|
||||
struct GlLoneTexture : public GlTexture {
|
||||
~GlLoneTexture();
|
||||
|
||||
Vec2U size() const override;
|
||||
TextureFiltering filtering() const override;
|
||||
TextureAddressing addressing() const override;
|
||||
|
||||
GLuint glTextureId() const override;
|
||||
Vec2U glTextureSize() const override;
|
||||
Vec2U glTextureCoordinateOffset() const override;
|
||||
|
||||
GLuint textureId = 0;
|
||||
Vec2U textureSize;
|
||||
TextureAddressing textureAddressing = TextureAddressing::Clamp;
|
||||
TextureFiltering textureFiltering = TextureFiltering::Nearest;
|
||||
};
|
||||
|
||||
struct GlRenderVertex {
|
||||
Vec2F screenCoordinate;
|
||||
Vec2F textureCoordinate;
|
||||
float textureIndex;
|
||||
Vec4B color;
|
||||
float param1;
|
||||
};
|
||||
|
||||
struct GlRenderBuffer : public RenderBuffer {
|
||||
struct GlVertexBufferTexture {
|
||||
GLuint texture;
|
||||
Vec2U size;
|
||||
};
|
||||
|
||||
struct GlVertexBuffer {
|
||||
List<GlVertexBufferTexture> textures;
|
||||
GLuint vertexBuffer = 0;
|
||||
size_t vertexCount = 0;
|
||||
};
|
||||
|
||||
~GlRenderBuffer();
|
||||
|
||||
void set(List<RenderPrimitive> primitives) override;
|
||||
|
||||
RefPtr<GlTexture> whiteTexture;
|
||||
ByteArray accumulationBuffer;
|
||||
|
||||
HashSet<TexturePtr> usedTextures;
|
||||
List<GlVertexBuffer> vertexBuffers;
|
||||
|
||||
bool useMultiTexturing;
|
||||
};
|
||||
|
||||
struct EffectParameter {
|
||||
GLint parameterUniform = -1;
|
||||
VariantTypeIndex parameterType;
|
||||
Maybe<RenderEffectParameter> parameterValue;
|
||||
};
|
||||
|
||||
struct EffectTexture {
|
||||
GLint textureUniform = -1;
|
||||
unsigned textureUnit;
|
||||
TextureAddressing textureAddressing = TextureAddressing::Clamp;
|
||||
TextureFiltering textureFiltering = TextureFiltering::Linear;
|
||||
GLint textureSizeUniform = -1;
|
||||
RefPtr<GlLoneTexture> textureValue;
|
||||
};
|
||||
|
||||
static void logGlErrorSummary(String prefix);
|
||||
static void uploadTextureImage(PixelFormat pixelFormat, Vec2U size, uint8_t const* data);
|
||||
|
||||
static RefPtr<GlLoneTexture> createGlTexture(Image const& texture, TextureAddressing addressing, TextureFiltering filtering);
|
||||
|
||||
shared_ptr<GlRenderBuffer> createGlRenderBuffer();
|
||||
|
||||
void flushImmediatePrimitives();
|
||||
|
||||
void renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F const& transformation);
|
||||
|
||||
Vec2U m_screenSize;
|
||||
|
||||
GLuint m_program = 0;
|
||||
|
||||
GLint m_positionAttribute = -1;
|
||||
GLint m_texCoordAttribute = -1;
|
||||
GLint m_texIndexAttribute = -1;
|
||||
GLint m_colorAttribute = -1;
|
||||
GLint m_param1Attribute = -1;
|
||||
|
||||
List<GLint> m_textureUniforms = {};
|
||||
List<GLint> m_textureSizeUniforms = {};
|
||||
GLint m_screenSizeUniform = -1;
|
||||
GLint m_vertexTransformUniform = -1;
|
||||
|
||||
RefPtr<GlTexture> m_whiteTexture;
|
||||
|
||||
StringMap<EffectParameter> m_effectParameters;
|
||||
StringMap<EffectTexture> m_effectTextures;
|
||||
Maybe<RectI> m_scissorRect;
|
||||
|
||||
bool m_limitTextureGroupSize;
|
||||
bool m_useMultiTexturing;
|
||||
List<shared_ptr<GlTextureGroup>> m_liveTextureGroups;
|
||||
|
||||
List<RenderPrimitive> m_immediatePrimitives;
|
||||
shared_ptr<GlRenderBuffer> m_immediateRenderBuffer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
130
source/application/StarStatisticsService_pc_steam.cpp
Normal file
130
source/application/StarStatisticsService_pc_steam.cpp
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#include "StarStatisticsService_pc_steam.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
SteamStatisticsService::SteamStatisticsService(PcPlatformServicesStatePtr)
|
||||
: m_callbackUserStatsReceived(this, &SteamStatisticsService::onUserStatsReceived),
|
||||
m_callbackUserStatsStored(this, &SteamStatisticsService::onUserStatsStored),
|
||||
m_callbackAchievementStored(this, &SteamStatisticsService::onAchievementStored) {
|
||||
m_appId = SteamUtils()->GetAppID();
|
||||
refresh();
|
||||
}
|
||||
|
||||
bool SteamStatisticsService::initialized() const {
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
Maybe<String> SteamStatisticsService::error() const {
|
||||
return m_error;
|
||||
}
|
||||
|
||||
bool SteamStatisticsService::setStat(String const& name, String const& type, Json const& value) {
|
||||
if (type == "int")
|
||||
return SteamUserStats()->SetStat(name.utf8Ptr(), (int32_t)value.toInt());
|
||||
|
||||
if (type == "float")
|
||||
return SteamUserStats()->SetStat(name.utf8Ptr(), value.toFloat());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Json SteamStatisticsService::getStat(String const& name, String const& type, Json def) const {
|
||||
if (type == "int") {
|
||||
int32_t intValue = 0;
|
||||
if (SteamUserStats()->GetStat(name.utf8Ptr(), &intValue))
|
||||
return Json(intValue);
|
||||
}
|
||||
|
||||
if (type == "float") {
|
||||
float floatValue = 0.0f;
|
||||
if (SteamUserStats()->GetStat(name.utf8Ptr(), &floatValue))
|
||||
return Json(floatValue);
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
bool SteamStatisticsService::reportEvent(String const&, Json const&) {
|
||||
// Steam doesn't support events
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SteamStatisticsService::unlockAchievement(String const& name) {
|
||||
if (!SteamUserStats()->SetAchievement(name.utf8Ptr())) {
|
||||
Logger::error("Cannot set Steam achievement %s", name);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
StringSet SteamStatisticsService::achievementsUnlocked() const {
|
||||
StringSet achievements;
|
||||
for (uint32_t i = 0; i < SteamUserStats()->GetNumAchievements(); ++i) {
|
||||
String achievement = SteamUserStats()->GetAchievementName(i);
|
||||
|
||||
bool unlocked = false;
|
||||
if (SteamUserStats()->GetAchievement(achievement.utf8Ptr(), &unlocked) && unlocked) {
|
||||
achievements.add(achievement);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void SteamStatisticsService::refresh() {
|
||||
if (!SteamUser()->BLoggedOn()) {
|
||||
m_error = {"Not logged in"};
|
||||
return;
|
||||
}
|
||||
|
||||
SteamUserStats()->RequestCurrentStats();
|
||||
}
|
||||
|
||||
void SteamStatisticsService::flush() {
|
||||
SteamUserStats()->StoreStats();
|
||||
}
|
||||
|
||||
bool SteamStatisticsService::reset() {
|
||||
SteamUserStats()->ResetAllStats(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SteamStatisticsService::onUserStatsReceived(UserStatsReceived_t* callback) {
|
||||
if (callback->m_nGameID != m_appId)
|
||||
return;
|
||||
|
||||
if (callback->m_eResult != k_EResultOK) {
|
||||
m_error = {strf("Steam RequestCurrentStats failed with code %d", callback->m_eResult)};
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::debug("Steam RequestCurrentStats successful");
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
void SteamStatisticsService::onUserStatsStored(UserStatsStored_t* callback) {
|
||||
if (callback->m_nGameID != m_appId)
|
||||
return;
|
||||
|
||||
if (callback->m_eResult == k_EResultOK) {
|
||||
Logger::debug("Steam StoreStats successful");
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback->m_eResult == k_EResultInvalidParam) {
|
||||
// A stat we set broke a constraint and was reverted on the service.
|
||||
Logger::info("Steam StoreStats: Some stats failed validation");
|
||||
return;
|
||||
}
|
||||
|
||||
m_error = {strf("Steam StoreStats failed with code %d", callback->m_eResult)};
|
||||
}
|
||||
|
||||
void SteamStatisticsService::onAchievementStored(UserAchievementStored_t* callback) {
|
||||
if (callback->m_nGameID != m_appId)
|
||||
return;
|
||||
|
||||
Logger::debug("Steam achievement %s stored successfully", callback->m_rgchAchievementName);
|
||||
}
|
||||
|
||||
}
|
||||
41
source/application/StarStatisticsService_pc_steam.hpp
Normal file
41
source/application/StarStatisticsService_pc_steam.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef STAR_STATISTICS_SERVICE_PC_STEAM_HPP
|
||||
#define STAR_STATISTICS_SERVICE_PC_STEAM_HPP
|
||||
|
||||
#include "StarPlatformServices_pc.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(SteamStatisticsService);
|
||||
|
||||
class SteamStatisticsService : public StatisticsService {
|
||||
public:
|
||||
SteamStatisticsService(PcPlatformServicesStatePtr state);
|
||||
|
||||
bool initialized() const override;
|
||||
Maybe<String> error() const override;
|
||||
|
||||
bool setStat(String const& name, String const& type, Json const& value) override;
|
||||
Json getStat(String const& name, String const& type, Json def = {}) const override;
|
||||
|
||||
bool reportEvent(String const& name, Json const& fields) override;
|
||||
|
||||
bool unlockAchievement(String const& name) override;
|
||||
StringSet achievementsUnlocked() const override;
|
||||
|
||||
void refresh() override;
|
||||
void flush() override;
|
||||
bool reset() override;
|
||||
|
||||
private:
|
||||
STEAM_CALLBACK(SteamStatisticsService, onUserStatsReceived, UserStatsReceived_t, m_callbackUserStatsReceived);
|
||||
STEAM_CALLBACK(SteamStatisticsService, onUserStatsStored, UserStatsStored_t, m_callbackUserStatsStored);
|
||||
STEAM_CALLBACK(SteamStatisticsService, onAchievementStored, UserAchievementStored_t, m_callbackAchievementStored);
|
||||
|
||||
uint64_t m_appId;
|
||||
bool m_initialized;
|
||||
Maybe<String> m_error;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
417
source/application/StarTextureAtlas.hpp
Normal file
417
source/application/StarTextureAtlas.hpp
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
#ifndef STAR_TEXTURE_ATLAS_HPP
|
||||
#define STAR_TEXTURE_ATLAS_HPP
|
||||
|
||||
#include "StarRect.hpp"
|
||||
#include "StarImage.hpp"
|
||||
#include "StarCasting.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_EXCEPTION(TextureAtlasException, StarException);
|
||||
|
||||
// Implements a set of "texture atlases" or, sets of smaller textures grouped
|
||||
// as a larger texture.
|
||||
template <typename AtlasTextureHandle>
|
||||
class TextureAtlasSet {
|
||||
public:
|
||||
struct Texture {
|
||||
virtual Vec2U imageSize() const = 0;
|
||||
|
||||
virtual AtlasTextureHandle const& atlasTexture() const = 0;
|
||||
virtual RectU atlasTextureCoordinates() const = 0;
|
||||
|
||||
// A locked texture will never be moved during compression, so its
|
||||
// atlasTexture and textureCoordinates will not change.
|
||||
virtual void setLocked(bool locked) = 0;
|
||||
|
||||
// Returns true if this texture has been freed or the parent
|
||||
// TextureAtlasSet has been destructed.
|
||||
virtual bool expired() const = 0;
|
||||
};
|
||||
|
||||
typedef shared_ptr<Texture> TextureHandle;
|
||||
|
||||
TextureAtlasSet(unsigned cellSize, unsigned atlasNumCells);
|
||||
|
||||
// The constant square size of all atlas textures
|
||||
Vec2U atlasTextureSize() const;
|
||||
|
||||
// Removes all existing textures and destroys all texture atlases.
|
||||
void reset();
|
||||
|
||||
// Adds texture to some TextureAtlas. Texture must fit in a single atlas
|
||||
// texture, otherwise an exception is thrown. Returns a pointer to the new
|
||||
// texture. If borderPixels is true, then fills a 1px border around the
|
||||
// given image in the atlas with the nearest color value, to prevent
|
||||
// bleeding.
|
||||
TextureHandle addTexture(Image const& image, bool borderPixels = true);
|
||||
|
||||
// Removes the given texture from the TextureAtlasSet and invalidates the
|
||||
// pointer.
|
||||
void freeTexture(TextureHandle const& texture);
|
||||
|
||||
unsigned totalAtlases() const;
|
||||
unsigned totalTextures() const;
|
||||
float averageFillLevel() const;
|
||||
|
||||
// Takes images from sparsely filled atlases and moves them to less sparsely
|
||||
// filled atlases in an effort to free up room. This method tages the atlas
|
||||
// with the lowest fill level and picks a texture from it, removes it, and
|
||||
// re-adds it to the AtlasSet. It does this up to textureCount textures,
|
||||
// until it finds a texture where re-adding it to the texture atlas simply
|
||||
// moves the texture into the same atlas, at which point it stops.
|
||||
void compressionPass(size_t textureCount = NPos);
|
||||
|
||||
// The number of atlases that the AtlasSet will attempt to fit a texture in
|
||||
// before giving up and creating a new atlas. Tries in order of least full
|
||||
// to most full. Defaults to 3.
|
||||
unsigned textureFitTries() const;
|
||||
void setTextureFitTries(unsigned textureFitTries);
|
||||
|
||||
protected:
|
||||
virtual AtlasTextureHandle createAtlasTexture(Vec2U const& size, PixelFormat pixelFormat) = 0;
|
||||
virtual void destroyAtlasTexture(AtlasTextureHandle const& atlasTexture) = 0;
|
||||
virtual void copyAtlasPixels(AtlasTextureHandle const& atlasTexture, Vec2U const& bottomLeft, Image const& image) = 0;
|
||||
|
||||
private:
|
||||
struct TextureAtlas {
|
||||
AtlasTextureHandle atlasTexture;
|
||||
unique_ptr<bool[]> usedCells;
|
||||
unsigned usedCellCount;
|
||||
};
|
||||
|
||||
struct AtlasPlacement {
|
||||
TextureAtlas* atlas;
|
||||
bool borderPixels = false;
|
||||
RectU occupiedCells;
|
||||
RectU textureCoords;
|
||||
};
|
||||
|
||||
struct TextureEntry : Texture {
|
||||
Vec2U imageSize() const override;
|
||||
|
||||
AtlasTextureHandle const& atlasTexture() const override;
|
||||
RectU atlasTextureCoordinates() const override;
|
||||
|
||||
// A locked texture will never be moved during compression, so its
|
||||
// atlasTexture and textureCoordinates will not change.
|
||||
void setLocked(bool locked) override;
|
||||
|
||||
bool expired() const override;
|
||||
|
||||
Image textureImage;
|
||||
AtlasPlacement atlasPlacement;
|
||||
bool placementLocked = false;
|
||||
bool textureExpired = false;
|
||||
};
|
||||
|
||||
void setAtlasRegionUsed(TextureAtlas* extureAtlas, RectU const& region, bool used) const;
|
||||
|
||||
Maybe<AtlasPlacement> addTextureToAtlas(TextureAtlas* atlas, Image const& image, bool borderPixels);
|
||||
void sortAtlases();
|
||||
|
||||
unsigned m_atlasCellSize;
|
||||
unsigned m_atlasNumCells;
|
||||
unsigned m_textureFitTries;
|
||||
|
||||
List<shared_ptr<TextureAtlas>> m_atlases;
|
||||
HashSet<shared_ptr<TextureEntry>> m_textures;
|
||||
};
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
TextureAtlasSet<AtlasTextureHandle>::TextureAtlasSet(unsigned cellSize, unsigned atlasNumCells)
|
||||
: m_atlasCellSize(cellSize), m_atlasNumCells(atlasNumCells), m_textureFitTries(3) {}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
Vec2U TextureAtlasSet<AtlasTextureHandle>::atlasTextureSize() const {
|
||||
return Vec2U::filled(m_atlasCellSize * m_atlasNumCells);
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
void TextureAtlasSet<AtlasTextureHandle>::reset() {
|
||||
for (auto const& texture : m_textures)
|
||||
texture->textureExpired = true;
|
||||
|
||||
for (auto const& atlas : m_atlases)
|
||||
destroyAtlasTexture(atlas->atlasTexture);
|
||||
|
||||
m_atlases.clear();
|
||||
m_textures.clear();
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
auto TextureAtlasSet<AtlasTextureHandle>::addTexture(Image const& image, bool borderPixels) -> TextureHandle {
|
||||
if (image.empty())
|
||||
throw TextureAtlasException("Empty image given in TextureAtlasSet::addTexture");
|
||||
|
||||
Image finalImage;
|
||||
if (borderPixels) {
|
||||
Vec2U imageSize = image.size();
|
||||
Vec2U finalImageSize = imageSize + Vec2U(2, 2);
|
||||
finalImage = Image(finalImageSize, PixelFormat::RGBA32);
|
||||
|
||||
// Fill 1px border on all sides of the image
|
||||
for (unsigned y = 0; y < finalImageSize[1]; ++y) {
|
||||
for (unsigned x = 0; x < finalImageSize[0]; ++x) {
|
||||
unsigned xind = clamp<unsigned>(x, 1, imageSize[0]) - 1;
|
||||
unsigned yind = clamp<unsigned>(y, 1, imageSize[1]) - 1;
|
||||
finalImage.set32(x, y, image.getrgb(xind, yind));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finalImage = image;
|
||||
}
|
||||
|
||||
auto tryAtlas = [&](TextureAtlas* atlas) -> TextureHandle {
|
||||
auto placement = addTextureToAtlas(atlas, finalImage, borderPixels);
|
||||
if (!placement)
|
||||
return nullptr;
|
||||
|
||||
auto textureEntry = make_shared<TextureEntry>();
|
||||
textureEntry->textureImage = move(finalImage);
|
||||
textureEntry->atlasPlacement = *placement;
|
||||
|
||||
m_textures.add(textureEntry);
|
||||
sortAtlases();
|
||||
return textureEntry;
|
||||
};
|
||||
|
||||
// Try the first 'm_textureFitTries' atlases to see if we can fit a given
|
||||
// texture in an existing atlas. Do this from the most full to the least
|
||||
// full atlas to maximize compression.
|
||||
size_t startAtlas = m_atlases.size() - min<size_t>(m_atlases.size(), m_textureFitTries);
|
||||
for (size_t i = startAtlas; i < m_atlases.size(); ++i) {
|
||||
if (auto texturePtr = tryAtlas(m_atlases[i].get()))
|
||||
return texturePtr;
|
||||
}
|
||||
|
||||
// If we have not found an existing atlas to put the texture, need to create
|
||||
// a new atlas
|
||||
m_atlases.append(make_shared<TextureAtlas>(TextureAtlas{
|
||||
createAtlasTexture(Vec2U::filled(m_atlasCellSize * m_atlasNumCells), PixelFormat::RGBA32),
|
||||
unique_ptr<bool[]>(new bool[m_atlasNumCells * m_atlasNumCells]()), 0
|
||||
}));
|
||||
|
||||
if (auto texturePtr = tryAtlas(m_atlases.last().get()))
|
||||
return texturePtr;
|
||||
|
||||
// If it can't fit in a brand new empty atlas, it will not fit in any atlas
|
||||
destroyAtlasTexture(m_atlases.last()->atlasTexture);
|
||||
m_atlases.removeLast();
|
||||
throw TextureAtlasException("Could not add texture to new atlas in TextureAtlasSet::addTexture, too large");
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
void TextureAtlasSet<AtlasTextureHandle>::freeTexture(TextureHandle const& texture) {
|
||||
auto textureEntry = convert<TextureEntry>(texture);
|
||||
|
||||
setAtlasRegionUsed(textureEntry->atlasPlacement.atlas, textureEntry->atlasPlacement.occupiedCells, false);
|
||||
sortAtlases();
|
||||
|
||||
textureEntry->textureExpired = true;
|
||||
m_textures.remove(textureEntry);
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
unsigned TextureAtlasSet<AtlasTextureHandle>::totalAtlases() const {
|
||||
return m_atlases.size();
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
unsigned TextureAtlasSet<AtlasTextureHandle>::totalTextures() const {
|
||||
return m_textures.size();
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
float TextureAtlasSet<AtlasTextureHandle>::averageFillLevel() const {
|
||||
if (m_atlases.empty())
|
||||
return 0.0f;
|
||||
|
||||
float atlasFillLevelSum = 0.0f;
|
||||
for (auto const& atlas : m_atlases)
|
||||
atlasFillLevelSum += atlas->usedCellCount / (float)square(m_atlasNumCells);
|
||||
return atlasFillLevelSum / m_atlases.size();
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
void TextureAtlasSet<AtlasTextureHandle>::compressionPass(size_t textureCount) {
|
||||
while (m_atlases.size() > 1 && textureCount > 0) {
|
||||
// Find the least full atlas, If it is empty, remove it and start at the
|
||||
// next atlas.
|
||||
auto const& smallestAtlas = m_atlases.last();
|
||||
if (smallestAtlas->usedCellCount == 0) {
|
||||
destroyAtlasTexture(smallestAtlas->atlasTexture);
|
||||
m_atlases.removeLast();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Loop over the currently loaded textures to find the smallest texture in
|
||||
// the smallest atlas that is not locked.
|
||||
TextureEntry* smallestTexture = nullptr;
|
||||
for (auto const& texture : m_textures) {
|
||||
if (texture->atlasPlacement.atlas == m_atlases.last().get()) {
|
||||
if (!texture->placementLocked) {
|
||||
if (!smallestTexture || texture->atlasPlacement.occupiedCells.volume() < smallestTexture->atlasPlacement.occupiedCells.volume())
|
||||
smallestTexture = texture.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we were not able to find a smallest texture because the texture is
|
||||
// locked, then simply stop. TODO: This could be done better, this will
|
||||
// prevent compressing textures that are not from the smallest atlas if the
|
||||
// smallest atlas has only locked textures.
|
||||
if (!smallestTexture)
|
||||
break;
|
||||
|
||||
// Try to add the texture to any atlas that isn't the last (most empty) one
|
||||
size_t startAtlas = m_atlases.size() - 1 - min<size_t>(m_atlases.size() - 1, m_textureFitTries);
|
||||
for (size_t i = startAtlas; i < m_atlases.size() - 1; ++i) {
|
||||
if (auto placement = addTextureToAtlas(m_atlases[i].get(), smallestTexture->textureImage, smallestTexture->atlasPlacement.borderPixels)) {
|
||||
setAtlasRegionUsed(smallestTexture->atlasPlacement.atlas, smallestTexture->atlasPlacement.occupiedCells, false);
|
||||
smallestTexture->atlasPlacement = *placement;
|
||||
smallestTexture = nullptr;
|
||||
sortAtlases();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have not managed to move the smallest texture into any other
|
||||
// atlas, assume the atlas set is compressed enough and quit.
|
||||
if (smallestTexture)
|
||||
break;
|
||||
|
||||
--textureCount;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
unsigned TextureAtlasSet<AtlasTextureHandle>::textureFitTries() const {
|
||||
return m_textureFitTries;
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
void TextureAtlasSet<AtlasTextureHandle>::setTextureFitTries(unsigned textureFitTries) {
|
||||
m_textureFitTries = textureFitTries;
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
Vec2U TextureAtlasSet<AtlasTextureHandle>::TextureEntry::imageSize() const {
|
||||
if (atlasPlacement.borderPixels)
|
||||
return textureImage.size() - Vec2U(2, 2);
|
||||
else
|
||||
return textureImage.size();
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
AtlasTextureHandle const& TextureAtlasSet<AtlasTextureHandle>::TextureEntry::atlasTexture() const {
|
||||
return atlasPlacement.atlas->atlasTexture;
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
RectU TextureAtlasSet<AtlasTextureHandle>::TextureEntry::atlasTextureCoordinates() const {
|
||||
return atlasPlacement.textureCoords;
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
void TextureAtlasSet<AtlasTextureHandle>::TextureEntry::setLocked(bool locked) {
|
||||
placementLocked = locked;
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
bool TextureAtlasSet<AtlasTextureHandle>::TextureEntry::expired() const {
|
||||
return textureExpired;
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
void TextureAtlasSet<AtlasTextureHandle>::setAtlasRegionUsed(TextureAtlas* textureAtlas, RectU const& region, bool used) const {
|
||||
for (unsigned y = region.yMin(); y < region.yMax(); ++y) {
|
||||
for (unsigned x = region.xMin(); x < region.xMax(); ++x) {
|
||||
auto& val = textureAtlas->usedCells[y * m_atlasNumCells + x];
|
||||
bool oldVal = val;
|
||||
val = used;
|
||||
if (oldVal && !val) {
|
||||
starAssert(textureAtlas->usedCellCount != 0);
|
||||
textureAtlas->usedCellCount -= 1;
|
||||
} else if (!oldVal && used) {
|
||||
textureAtlas->usedCellCount += 1;
|
||||
starAssert(textureAtlas->usedCellCount <= square(m_atlasNumCells));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
void TextureAtlasSet<AtlasTextureHandle>::sortAtlases() {
|
||||
sort(m_atlases, [](auto const& a1, auto const& a2) {
|
||||
return a1->usedCellCount > a2->usedCellCount;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename AtlasTextureHandle>
|
||||
auto TextureAtlasSet<AtlasTextureHandle>::addTextureToAtlas(TextureAtlas* atlas, Image const& image, bool borderPixels) -> Maybe<AtlasPlacement> {
|
||||
bool found = false;
|
||||
// Minimum cell indexes where this texture fits in this atlas.
|
||||
unsigned fitCellX = 0;
|
||||
unsigned fitCellY = 0;
|
||||
|
||||
Vec2U imageSize = image.size();
|
||||
|
||||
// Number of cells this image will take.
|
||||
size_t numCellsX = (imageSize[0] + m_atlasCellSize - 1) / m_atlasCellSize;
|
||||
size_t numCellsY = (imageSize[1] + m_atlasCellSize - 1) / m_atlasCellSize;
|
||||
|
||||
if (numCellsX > m_atlasNumCells || numCellsY > m_atlasNumCells)
|
||||
return {};
|
||||
|
||||
for (size_t cellY = 0; cellY <= m_atlasNumCells - numCellsY; ++cellY) {
|
||||
for (size_t cellX = 0; cellX <= m_atlasNumCells - numCellsX; ++cellX) {
|
||||
// Check this box of numCellsX x numCellsY for fit.
|
||||
found = true;
|
||||
size_t fx;
|
||||
size_t fy;
|
||||
for (fy = cellY; fy < cellY + numCellsY; ++fy) {
|
||||
for (fx = cellX; fx < cellX + numCellsX; ++fx) {
|
||||
if (atlas->usedCells[fy * m_atlasNumCells + fx]) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
break;
|
||||
}
|
||||
if (!found) {
|
||||
// If it does not fit, then we can skip to the block past the first
|
||||
// horizontal used block;
|
||||
cellX = fx;
|
||||
} else {
|
||||
fitCellX = cellX;
|
||||
fitCellY = cellY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return {};
|
||||
|
||||
setAtlasRegionUsed(atlas, RectU::withSize({fitCellX, fitCellY}, {(unsigned)numCellsX, (unsigned)numCellsY}), true);
|
||||
|
||||
copyAtlasPixels(atlas->atlasTexture, Vec2U(fitCellX * m_atlasCellSize, fitCellY * m_atlasCellSize), image);
|
||||
|
||||
AtlasPlacement atlasPlacement;
|
||||
atlasPlacement.atlas = atlas;
|
||||
atlasPlacement.borderPixels = borderPixels;
|
||||
atlasPlacement.occupiedCells = RectU::withSize(Vec2U(fitCellX, fitCellY), Vec2U(numCellsX, numCellsY));
|
||||
if (borderPixels)
|
||||
atlasPlacement.textureCoords = RectU::withSize(Vec2U(fitCellX * m_atlasCellSize + 1, fitCellY * m_atlasCellSize + 1), imageSize - Vec2U(2, 2));
|
||||
else
|
||||
atlasPlacement.textureCoords = RectU::withSize(Vec2U(fitCellX * m_atlasCellSize, fitCellY * m_atlasCellSize), imageSize);
|
||||
return atlasPlacement;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
#include "StarUserGeneratedContentService_pc_steam.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarLexicalCast.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
SteamUserGeneratedContentService::SteamUserGeneratedContentService(PcPlatformServicesStatePtr)
|
||||
: m_callbackDownloadResult(this, &SteamUserGeneratedContentService::onDownloadResult) {};
|
||||
|
||||
StringList SteamUserGeneratedContentService::subscribedContentIds() const {
|
||||
List<PublishedFileId_t> contentIds(SteamUGC()->GetNumSubscribedItems(), {});
|
||||
SteamUGC()->GetSubscribedItems(contentIds.ptr(), contentIds.size());
|
||||
return contentIds.transformed([](PublishedFileId_t id) {
|
||||
return String(toString(id));
|
||||
});
|
||||
}
|
||||
|
||||
Maybe<String> SteamUserGeneratedContentService::contentDownloadDirectory(String const& contentId) const {
|
||||
PublishedFileId_t id = lexicalCast<PublishedFileId_t>(contentId);
|
||||
uint32 itemState = SteamUGC()->GetItemState(id);
|
||||
if (itemState & k_EItemStateInstalled) {
|
||||
char path[4096];
|
||||
if (SteamUGC()->GetItemInstallInfo(id, nullptr, path, sizeof(path), nullptr))
|
||||
return String(path);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool SteamUserGeneratedContentService::triggerContentDownload() {
|
||||
List<PublishedFileId_t> contentIds(SteamUGC()->GetNumSubscribedItems(), {});
|
||||
SteamUGC()->GetSubscribedItems(contentIds.ptr(), contentIds.size());
|
||||
|
||||
for (uint64 contentId : contentIds) {
|
||||
if (!m_currentDownloadState.contains(contentId)) {
|
||||
uint32 itemState = SteamUGC()->GetItemState(contentId);
|
||||
if (!(itemState & k_EItemStateInstalled) || itemState & k_EItemStateNeedsUpdate) {
|
||||
SteamUGC()->DownloadItem(contentId, true);
|
||||
itemState = SteamUGC()->GetItemState(contentId);
|
||||
m_currentDownloadState[contentId] = !(itemState & k_EItemStateDownloading);
|
||||
} else {
|
||||
m_currentDownloadState[contentId] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool allDownloaded = true;
|
||||
for (auto const& p : m_currentDownloadState) {
|
||||
if (!p.second)
|
||||
allDownloaded = false;
|
||||
}
|
||||
|
||||
return allDownloaded;
|
||||
}
|
||||
|
||||
void SteamUserGeneratedContentService::onDownloadResult(DownloadItemResult_t* result) {
|
||||
m_currentDownloadState[result->m_nPublishedFileId] = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef STAR_USER_GENERATED_CONTENT_SERVICE_PC_STEAM_HPP
|
||||
#define STAR_USER_GENERATED_CONTENT_SERVICE_PC_STEAM_HPP
|
||||
|
||||
#include "StarPlatformServices_pc.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(SteamUserGeneratedContentService);
|
||||
|
||||
class SteamUserGeneratedContentService : public UserGeneratedContentService {
|
||||
public:
|
||||
SteamUserGeneratedContentService(PcPlatformServicesStatePtr state);
|
||||
|
||||
StringList subscribedContentIds() const override;
|
||||
Maybe<String> contentDownloadDirectory(String const& contentId) const override;
|
||||
bool triggerContentDownload() override;
|
||||
|
||||
private:
|
||||
STEAM_CALLBACK(SteamUserGeneratedContentService, onDownloadResult, DownloadItemResult_t, m_callbackDownloadResult);
|
||||
|
||||
HashMap<PublishedFileId_t, bool> m_currentDownloadState;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
177
source/application/discord/activity_manager.cpp
Normal file
177
source/application/discord/activity_manager.cpp
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "activity_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class ActivityEvents final {
|
||||
public:
|
||||
static void OnActivityJoin(void* callbackData, char const* secret)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->ActivityManager();
|
||||
module.OnActivityJoin(static_cast<const char*>(secret));
|
||||
}
|
||||
|
||||
static void OnActivitySpectate(void* callbackData, char const* secret)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->ActivityManager();
|
||||
module.OnActivitySpectate(static_cast<const char*>(secret));
|
||||
}
|
||||
|
||||
static void OnActivityJoinRequest(void* callbackData, DiscordUser* user)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->ActivityManager();
|
||||
module.OnActivityJoinRequest(*reinterpret_cast<User const*>(user));
|
||||
}
|
||||
|
||||
static void OnActivityInvite(void* callbackData,
|
||||
EDiscordActivityActionType type,
|
||||
DiscordUser* user,
|
||||
DiscordActivity* activity)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->ActivityManager();
|
||||
module.OnActivityInvite(static_cast<ActivityActionType>(type),
|
||||
*reinterpret_cast<User const*>(user),
|
||||
*reinterpret_cast<Activity const*>(activity));
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordActivityEvents ActivityManager::events_{
|
||||
&ActivityEvents::OnActivityJoin,
|
||||
&ActivityEvents::OnActivitySpectate,
|
||||
&ActivityEvents::OnActivityJoinRequest,
|
||||
&ActivityEvents::OnActivityInvite,
|
||||
};
|
||||
|
||||
Result ActivityManager::RegisterCommand(char const* command)
|
||||
{
|
||||
auto result = internal_->register_command(internal_, const_cast<char*>(command));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result ActivityManager::RegisterSteam(std::uint32_t steamId)
|
||||
{
|
||||
auto result = internal_->register_steam(internal_, steamId);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void ActivityManager::UpdateActivity(Activity const& activity, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->update_activity(internal_,
|
||||
reinterpret_cast<DiscordActivity*>(const_cast<Activity*>(&activity)),
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
void ActivityManager::ClearActivity(std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->clear_activity(internal_, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void ActivityManager::SendRequestReply(UserId userId,
|
||||
ActivityJoinRequestReply reply,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->send_request_reply(internal_,
|
||||
userId,
|
||||
static_cast<EDiscordActivityJoinRequestReply>(reply),
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
void ActivityManager::SendInvite(UserId userId,
|
||||
ActivityActionType type,
|
||||
char const* content,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->send_invite(internal_,
|
||||
userId,
|
||||
static_cast<EDiscordActivityActionType>(type),
|
||||
const_cast<char*>(content),
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
void ActivityManager::AcceptInvite(UserId userId, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->accept_invite(internal_, userId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
42
source/application/discord/activity_manager.h
Normal file
42
source/application/discord/activity_manager.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class ActivityManager final {
|
||||
public:
|
||||
~ActivityManager() = default;
|
||||
|
||||
Result RegisterCommand(char const* command);
|
||||
Result RegisterSteam(std::uint32_t steamId);
|
||||
void UpdateActivity(Activity const& activity, std::function<void(Result)> callback);
|
||||
void ClearActivity(std::function<void(Result)> callback);
|
||||
void SendRequestReply(UserId userId,
|
||||
ActivityJoinRequestReply reply,
|
||||
std::function<void(Result)> callback);
|
||||
void SendInvite(UserId userId,
|
||||
ActivityActionType type,
|
||||
char const* content,
|
||||
std::function<void(Result)> callback);
|
||||
void AcceptInvite(UserId userId, std::function<void(Result)> callback);
|
||||
|
||||
Event<char const*> OnActivityJoin;
|
||||
Event<char const*> OnActivitySpectate;
|
||||
Event<User const&> OnActivityJoinRequest;
|
||||
Event<ActivityActionType, User const&, Activity const&> OnActivityInvite;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
ActivityManager() = default;
|
||||
ActivityManager(ActivityManager const& rhs) = delete;
|
||||
ActivityManager& operator=(ActivityManager const& rhs) = delete;
|
||||
ActivityManager(ActivityManager&& rhs) = delete;
|
||||
ActivityManager& operator=(ActivityManager&& rhs) = delete;
|
||||
|
||||
IDiscordActivityManager* internal_;
|
||||
static IDiscordActivityEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
63
source/application/discord/application_manager.cpp
Normal file
63
source/application/discord/application_manager.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "application_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
void ApplicationManager::ValidateOrExit(std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->validate_or_exit(internal_, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void ApplicationManager::GetCurrentLocale(char locale[128])
|
||||
{
|
||||
if (!locale) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->get_current_locale(internal_, reinterpret_cast<DiscordLocale*>(locale));
|
||||
}
|
||||
|
||||
void ApplicationManager::GetCurrentBranch(char branch[4096])
|
||||
{
|
||||
if (!branch) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->get_current_branch(internal_, reinterpret_cast<DiscordBranch*>(branch));
|
||||
}
|
||||
|
||||
void ApplicationManager::GetOAuth2Token(std::function<void(Result, OAuth2Token const&)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, DiscordOAuth2Token* oauth2Token) -> void {
|
||||
std::unique_ptr<std::function<void(Result, OAuth2Token const&)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, OAuth2Token const&)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), *reinterpret_cast<OAuth2Token const*>(oauth2Token));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, OAuth2Token const&)>> cb{};
|
||||
cb.reset(new std::function<void(Result, OAuth2Token const&)>(std::move(callback)));
|
||||
internal_->get_oauth2_token(internal_, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
29
source/application/discord/application_manager.h
Normal file
29
source/application/discord/application_manager.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class ApplicationManager final {
|
||||
public:
|
||||
~ApplicationManager() = default;
|
||||
|
||||
void ValidateOrExit(std::function<void(Result)> callback);
|
||||
void GetCurrentLocale(char locale[128]);
|
||||
void GetCurrentBranch(char branch[4096]);
|
||||
void GetOAuth2Token(std::function<void(Result, OAuth2Token const&)> callback);
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
ApplicationManager() = default;
|
||||
ApplicationManager(ApplicationManager const& rhs) = delete;
|
||||
ApplicationManager& operator=(ApplicationManager const& rhs) = delete;
|
||||
ApplicationManager(ApplicationManager&& rhs) = delete;
|
||||
ApplicationManager& operator=(ApplicationManager&& rhs) = delete;
|
||||
|
||||
IDiscordApplicationManager* internal_;
|
||||
static IDiscordApplicationEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
162
source/application/discord/core.cpp
Normal file
162
source/application/discord/core.cpp
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
Result Core::Create(ClientId clientId, std::uint64_t flags, Core** instance)
|
||||
{
|
||||
if (!instance) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
(*instance) = new Core();
|
||||
DiscordCreateParams params{};
|
||||
DiscordCreateParamsSetDefault(¶ms);
|
||||
params.client_id = clientId;
|
||||
params.flags = flags;
|
||||
params.events = nullptr;
|
||||
params.event_data = *instance;
|
||||
params.user_events = &UserManager::events_;
|
||||
params.activity_events = &ActivityManager::events_;
|
||||
params.relationship_events = &RelationshipManager::events_;
|
||||
params.lobby_events = &LobbyManager::events_;
|
||||
params.network_events = &NetworkManager::events_;
|
||||
params.overlay_events = &OverlayManager::events_;
|
||||
params.store_events = &StoreManager::events_;
|
||||
auto result = DiscordCreate(DISCORD_VERSION, ¶ms, &((*instance)->internal_));
|
||||
if (result != DiscordResult_Ok || !(*instance)->internal_) {
|
||||
delete (*instance);
|
||||
(*instance) = nullptr;
|
||||
}
|
||||
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Core::~Core()
|
||||
{
|
||||
if (internal_) {
|
||||
internal_->destroy(internal_);
|
||||
internal_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Result Core::RunCallbacks()
|
||||
{
|
||||
auto result = internal_->run_callbacks(internal_);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void Core::SetLogHook(LogLevel minLevel, std::function<void(LogLevel, char const*)> hook)
|
||||
{
|
||||
setLogHook_.DisconnectAll();
|
||||
setLogHook_.Connect(std::move(hook));
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordLogLevel level, char const* message) -> void {
|
||||
auto cb(reinterpret_cast<decltype(setLogHook_)*>(callbackData));
|
||||
if (!cb) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<LogLevel>(level), static_cast<const char*>(message));
|
||||
};
|
||||
|
||||
internal_->set_log_hook(
|
||||
internal_, static_cast<EDiscordLogLevel>(minLevel), &setLogHook_, wrapper);
|
||||
}
|
||||
|
||||
discord::ApplicationManager& Core::ApplicationManager()
|
||||
{
|
||||
if (!applicationManager_.internal_) {
|
||||
applicationManager_.internal_ = internal_->get_application_manager(internal_);
|
||||
}
|
||||
|
||||
return applicationManager_;
|
||||
}
|
||||
|
||||
discord::UserManager& Core::UserManager()
|
||||
{
|
||||
if (!userManager_.internal_) {
|
||||
userManager_.internal_ = internal_->get_user_manager(internal_);
|
||||
}
|
||||
|
||||
return userManager_;
|
||||
}
|
||||
|
||||
discord::ImageManager& Core::ImageManager()
|
||||
{
|
||||
if (!imageManager_.internal_) {
|
||||
imageManager_.internal_ = internal_->get_image_manager(internal_);
|
||||
}
|
||||
|
||||
return imageManager_;
|
||||
}
|
||||
|
||||
discord::ActivityManager& Core::ActivityManager()
|
||||
{
|
||||
if (!activityManager_.internal_) {
|
||||
activityManager_.internal_ = internal_->get_activity_manager(internal_);
|
||||
}
|
||||
|
||||
return activityManager_;
|
||||
}
|
||||
|
||||
discord::RelationshipManager& Core::RelationshipManager()
|
||||
{
|
||||
if (!relationshipManager_.internal_) {
|
||||
relationshipManager_.internal_ = internal_->get_relationship_manager(internal_);
|
||||
}
|
||||
|
||||
return relationshipManager_;
|
||||
}
|
||||
|
||||
discord::LobbyManager& Core::LobbyManager()
|
||||
{
|
||||
if (!lobbyManager_.internal_) {
|
||||
lobbyManager_.internal_ = internal_->get_lobby_manager(internal_);
|
||||
}
|
||||
|
||||
return lobbyManager_;
|
||||
}
|
||||
|
||||
discord::NetworkManager& Core::NetworkManager()
|
||||
{
|
||||
if (!networkManager_.internal_) {
|
||||
networkManager_.internal_ = internal_->get_network_manager(internal_);
|
||||
}
|
||||
|
||||
return networkManager_;
|
||||
}
|
||||
|
||||
discord::OverlayManager& Core::OverlayManager()
|
||||
{
|
||||
if (!overlayManager_.internal_) {
|
||||
overlayManager_.internal_ = internal_->get_overlay_manager(internal_);
|
||||
}
|
||||
|
||||
return overlayManager_;
|
||||
}
|
||||
|
||||
discord::StorageManager& Core::StorageManager()
|
||||
{
|
||||
if (!storageManager_.internal_) {
|
||||
storageManager_.internal_ = internal_->get_storage_manager(internal_);
|
||||
}
|
||||
|
||||
return storageManager_;
|
||||
}
|
||||
|
||||
discord::StoreManager& Core::StoreManager()
|
||||
{
|
||||
if (!storeManager_.internal_) {
|
||||
storeManager_.internal_ = internal_->get_store_manager(internal_);
|
||||
}
|
||||
|
||||
return storeManager_;
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
58
source/application/discord/core.h
Normal file
58
source/application/discord/core.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "application_manager.h"
|
||||
#include "user_manager.h"
|
||||
#include "image_manager.h"
|
||||
#include "activity_manager.h"
|
||||
#include "relationship_manager.h"
|
||||
#include "lobby_manager.h"
|
||||
#include "network_manager.h"
|
||||
#include "overlay_manager.h"
|
||||
#include "storage_manager.h"
|
||||
#include "store_manager.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class Core final {
|
||||
public:
|
||||
static Result Create(ClientId clientId, std::uint64_t flags, Core** instance);
|
||||
|
||||
~Core();
|
||||
|
||||
Result RunCallbacks();
|
||||
void SetLogHook(LogLevel minLevel, std::function<void(LogLevel, char const*)> hook);
|
||||
|
||||
discord::ApplicationManager& ApplicationManager();
|
||||
discord::UserManager& UserManager();
|
||||
discord::ImageManager& ImageManager();
|
||||
discord::ActivityManager& ActivityManager();
|
||||
discord::RelationshipManager& RelationshipManager();
|
||||
discord::LobbyManager& LobbyManager();
|
||||
discord::NetworkManager& NetworkManager();
|
||||
discord::OverlayManager& OverlayManager();
|
||||
discord::StorageManager& StorageManager();
|
||||
discord::StoreManager& StoreManager();
|
||||
|
||||
private:
|
||||
Core() = default;
|
||||
Core(Core const& rhs) = delete;
|
||||
Core& operator=(Core const& rhs) = delete;
|
||||
Core(Core&& rhs) = delete;
|
||||
Core& operator=(Core&& rhs) = delete;
|
||||
|
||||
IDiscordCore* internal_;
|
||||
Event<LogLevel, char const*> setLogHook_;
|
||||
discord::ApplicationManager applicationManager_;
|
||||
discord::UserManager userManager_;
|
||||
discord::ImageManager imageManager_;
|
||||
discord::ActivityManager activityManager_;
|
||||
discord::RelationshipManager relationshipManager_;
|
||||
discord::LobbyManager lobbyManager_;
|
||||
discord::NetworkManager networkManager_;
|
||||
discord::OverlayManager overlayManager_;
|
||||
discord::StorageManager storageManager_;
|
||||
discord::StoreManager storeManager_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
14
source/application/discord/discord.h
Normal file
14
source/application/discord/discord.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "core.h"
|
||||
#include "application_manager.h"
|
||||
#include "user_manager.h"
|
||||
#include "image_manager.h"
|
||||
#include "activity_manager.h"
|
||||
#include "relationship_manager.h"
|
||||
#include "lobby_manager.h"
|
||||
#include "network_manager.h"
|
||||
#include "overlay_manager.h"
|
||||
#include "storage_manager.h"
|
||||
#include "store_manager.h"
|
||||
59
source/application/discord/event.h
Normal file
59
source/application/discord/event.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace discord {
|
||||
|
||||
template <typename... Args>
|
||||
class Event final {
|
||||
public:
|
||||
using Token = int;
|
||||
|
||||
Event() { slots_.reserve(4); }
|
||||
|
||||
Event(Event const&) = default;
|
||||
Event(Event&&) = default;
|
||||
~Event() = default;
|
||||
|
||||
Event& operator=(Event const&) = default;
|
||||
Event& operator=(Event&&) = default;
|
||||
|
||||
template <typename EventHandler>
|
||||
Token Connect(EventHandler slot)
|
||||
{
|
||||
slots_.emplace_back(Slot{nextToken_, std::move(slot)});
|
||||
return nextToken_++;
|
||||
}
|
||||
|
||||
void Disconnect(Token token)
|
||||
{
|
||||
for (auto& slot : slots_) {
|
||||
if (slot.token == token) {
|
||||
slot = slots_.back();
|
||||
slots_.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisconnectAll() { slots_ = {}; }
|
||||
|
||||
void operator()(Args... args)
|
||||
{
|
||||
for (auto const& slot : slots_) {
|
||||
slot.fn(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct Slot {
|
||||
Token token;
|
||||
std::function<void(Args...)> fn;
|
||||
};
|
||||
|
||||
Token nextToken_{};
|
||||
std::vector<Slot> slots_{};
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
790
source/application/discord/ffi.h
Normal file
790
source/application/discord/ffi.h
Normal file
|
|
@ -0,0 +1,790 @@
|
|||
#ifndef _DISCORD_GAME_SDK_H_
|
||||
#define _DISCORD_GAME_SDK_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#define DISCORD_VERSION 2
|
||||
#define DISCORD_APPLICATION_MANAGER_VERSION 1
|
||||
#define DISCORD_USER_MANAGER_VERSION 1
|
||||
#define DISCORD_IMAGE_MANAGER_VERSION 1
|
||||
#define DISCORD_ACTIVITY_MANAGER_VERSION 1
|
||||
#define DISCORD_RELATIONSHIP_MANAGER_VERSION 1
|
||||
#define DISCORD_LOBBY_MANAGER_VERSION 1
|
||||
#define DISCORD_NETWORK_MANAGER_VERSION 1
|
||||
#define DISCORD_OVERLAY_MANAGER_VERSION 1
|
||||
#define DISCORD_STORAGE_MANAGER_VERSION 1
|
||||
#define DISCORD_STORE_MANAGER_VERSION 1
|
||||
|
||||
enum EDiscordResult {
|
||||
DiscordResult_Ok,
|
||||
DiscordResult_ServiceUnavailable,
|
||||
DiscordResult_InvalidVersion,
|
||||
DiscordResult_LockFailed,
|
||||
DiscordResult_InternalError,
|
||||
DiscordResult_InvalidPayload,
|
||||
DiscordResult_InvalidCommand,
|
||||
DiscordResult_InvalidPermissions,
|
||||
DiscordResult_NotFetched,
|
||||
DiscordResult_NotFound,
|
||||
DiscordResult_Conflict,
|
||||
DiscordResult_InvalidSecret,
|
||||
DiscordResult_InvalidJoinSecret,
|
||||
DiscordResult_NoEligibleActivity,
|
||||
DiscordResult_InvalidInvite,
|
||||
DiscordResult_NotAuthenticated,
|
||||
DiscordResult_InvalidAccessToken,
|
||||
DiscordResult_ApplicationMismatch,
|
||||
DiscordResult_InvalidDataUrl,
|
||||
DiscordResult_InvalidBase64,
|
||||
DiscordResult_NotFiltered,
|
||||
DiscordResult_LobbyFull,
|
||||
DiscordResult_InvalidLobbySecret,
|
||||
DiscordResult_InvalidFilename,
|
||||
DiscordResult_InvalidFileSize,
|
||||
DiscordResult_InvalidEntitlement,
|
||||
DiscordResult_NotInstalled,
|
||||
DiscordResult_NotRunning,
|
||||
DiscordResult_InsufficientBuffer,
|
||||
DiscordResult_PurchaseCanceled,
|
||||
};
|
||||
|
||||
enum EDiscordCreateFlags {
|
||||
DiscordCreateFlags_Default = 0,
|
||||
DiscordCreateFlags_NoRequireDiscord = 1,
|
||||
};
|
||||
|
||||
enum EDiscordLogLevel {
|
||||
DiscordLogLevel_Error = 1,
|
||||
DiscordLogLevel_Warn,
|
||||
DiscordLogLevel_Info,
|
||||
DiscordLogLevel_Debug,
|
||||
};
|
||||
|
||||
enum EDiscordImageType {
|
||||
DiscordImageType_User,
|
||||
};
|
||||
|
||||
enum EDiscordActivityType {
|
||||
DiscordActivityType_Playing,
|
||||
DiscordActivityType_Streaming,
|
||||
DiscordActivityType_Listening,
|
||||
DiscordActivityType_Watching,
|
||||
};
|
||||
|
||||
enum EDiscordActivityActionType {
|
||||
DiscordActivityActionType_Join = 1,
|
||||
DiscordActivityActionType_Spectate,
|
||||
};
|
||||
|
||||
enum EDiscordActivityJoinRequestReply {
|
||||
DiscordActivityJoinRequestReply_No,
|
||||
DiscordActivityJoinRequestReply_Yes,
|
||||
DiscordActivityJoinRequestReply_Ignore,
|
||||
};
|
||||
|
||||
enum EDiscordStatus {
|
||||
DiscordStatus_Offline = 0,
|
||||
DiscordStatus_Online = 1,
|
||||
DiscordStatus_Idle = 2,
|
||||
DiscordStatus_DoNotDisturb = 3,
|
||||
};
|
||||
|
||||
enum EDiscordRelationshipType {
|
||||
DiscordRelationshipType_None,
|
||||
DiscordRelationshipType_Friend,
|
||||
DiscordRelationshipType_Blocked,
|
||||
DiscordRelationshipType_PendingIncoming,
|
||||
DiscordRelationshipType_PendingOutgoing,
|
||||
DiscordRelationshipType_Implicit,
|
||||
};
|
||||
|
||||
enum EDiscordLobbyType {
|
||||
DiscordLobbyType_Private = 1,
|
||||
DiscordLobbyType_Public,
|
||||
};
|
||||
|
||||
enum EDiscordLobbySearchComparison {
|
||||
DiscordLobbySearchComparison_LessThanOrEqual = -2,
|
||||
DiscordLobbySearchComparison_LessThan,
|
||||
DiscordLobbySearchComparison_Equal,
|
||||
DiscordLobbySearchComparison_GreaterThan,
|
||||
DiscordLobbySearchComparison_GreaterThanOrEqual,
|
||||
DiscordLobbySearchComparison_NotEqual,
|
||||
};
|
||||
|
||||
enum EDiscordLobbySearchCast {
|
||||
DiscordLobbySearchCast_String = 1,
|
||||
DiscordLobbySearchCast_Number,
|
||||
};
|
||||
|
||||
enum EDiscordLobbySearchDistance {
|
||||
DiscordLobbySearchDistance_Local,
|
||||
DiscordLobbySearchDistance_Default,
|
||||
DiscordLobbySearchDistance_Extended,
|
||||
DiscordLobbySearchDistance_Global,
|
||||
};
|
||||
|
||||
enum EDiscordEntitlementType {
|
||||
DiscordEntitlementType_Purchase = 1,
|
||||
DiscordEntitlementType_PremiumSubscription,
|
||||
DiscordEntitlementType_DeveloperGift,
|
||||
};
|
||||
|
||||
enum EDiscordSkuType {
|
||||
DiscordSkuType_Application = 1,
|
||||
DiscordSkuType_DLC,
|
||||
DiscordSkuType_Consumable,
|
||||
DiscordSkuType_Bundle,
|
||||
};
|
||||
|
||||
typedef int64_t DiscordClientId;
|
||||
typedef int32_t DiscordVersion;
|
||||
typedef int64_t DiscordSnowflake;
|
||||
typedef int64_t DiscordTimestamp;
|
||||
typedef DiscordSnowflake DiscordUserId;
|
||||
typedef char DiscordLocale[128];
|
||||
typedef char DiscordBranch[4096];
|
||||
typedef DiscordSnowflake DiscordLobbyId;
|
||||
typedef char DiscordLobbySecret[128];
|
||||
typedef char DiscordMetadataKey[256];
|
||||
typedef char DiscordMetadataValue[4096];
|
||||
typedef uint64_t DiscordNetworkPeerId;
|
||||
typedef uint8_t DiscordNetworkChannelId;
|
||||
|
||||
struct DiscordUser {
|
||||
DiscordUserId id;
|
||||
char username[256];
|
||||
char discriminator[8];
|
||||
char avatar[128];
|
||||
bool bot;
|
||||
};
|
||||
|
||||
struct DiscordOAuth2Token {
|
||||
char access_token[128];
|
||||
char scopes[1024];
|
||||
DiscordTimestamp expires;
|
||||
};
|
||||
|
||||
struct DiscordImageHandle {
|
||||
enum EDiscordImageType type;
|
||||
int64_t id;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
struct DiscordImageDimensions {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
struct DiscordActivityTimestamps {
|
||||
DiscordTimestamp start;
|
||||
DiscordTimestamp end;
|
||||
};
|
||||
|
||||
struct DiscordActivityAssets {
|
||||
char large_image[128];
|
||||
char large_text[128];
|
||||
char small_image[128];
|
||||
char small_text[128];
|
||||
};
|
||||
|
||||
struct DiscordPartySize {
|
||||
int32_t current_size;
|
||||
int32_t max_size;
|
||||
};
|
||||
|
||||
struct DiscordActivityParty {
|
||||
char id[128];
|
||||
struct DiscordPartySize size;
|
||||
};
|
||||
|
||||
struct DiscordActivitySecrets {
|
||||
char match[128];
|
||||
char join[128];
|
||||
char spectate[128];
|
||||
};
|
||||
|
||||
struct DiscordActivity {
|
||||
enum EDiscordActivityType type;
|
||||
int64_t application_id;
|
||||
char name[128];
|
||||
char state[128];
|
||||
char details[128];
|
||||
struct DiscordActivityTimestamps timestamps;
|
||||
struct DiscordActivityAssets assets;
|
||||
struct DiscordActivityParty party;
|
||||
struct DiscordActivitySecrets secrets;
|
||||
bool instance;
|
||||
};
|
||||
|
||||
struct DiscordPresence {
|
||||
enum EDiscordStatus status;
|
||||
struct DiscordActivity activity;
|
||||
};
|
||||
|
||||
struct DiscordRelationship {
|
||||
enum EDiscordRelationshipType type;
|
||||
struct DiscordUser user;
|
||||
struct DiscordPresence presence;
|
||||
};
|
||||
|
||||
struct DiscordLobby {
|
||||
DiscordLobbyId id;
|
||||
enum EDiscordLobbyType type;
|
||||
DiscordUserId owner_id;
|
||||
DiscordLobbySecret secret;
|
||||
uint32_t capacity;
|
||||
bool locked;
|
||||
};
|
||||
|
||||
struct DiscordFileStat {
|
||||
char filename[260];
|
||||
uint64_t size;
|
||||
uint64_t last_modified;
|
||||
};
|
||||
|
||||
struct DiscordEntitlement {
|
||||
DiscordSnowflake id;
|
||||
enum EDiscordEntitlementType type;
|
||||
DiscordSnowflake sku_id;
|
||||
};
|
||||
|
||||
struct DiscordSkuPrice {
|
||||
uint32_t amount;
|
||||
char currency[16];
|
||||
};
|
||||
|
||||
struct DiscordSku {
|
||||
DiscordSnowflake id;
|
||||
enum EDiscordSkuType type;
|
||||
char name[256];
|
||||
struct DiscordSkuPrice price;
|
||||
};
|
||||
|
||||
struct IDiscordLobbyTransaction {
|
||||
enum EDiscordResult (*set_type)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||
enum EDiscordLobbyType type);
|
||||
enum EDiscordResult (*set_owner)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||
DiscordUserId owner_id);
|
||||
enum EDiscordResult (*set_capacity)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||
uint32_t capacity);
|
||||
enum EDiscordResult (*set_metadata)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||
DiscordMetadataKey key,
|
||||
DiscordMetadataValue value);
|
||||
enum EDiscordResult (*delete_metadata)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||
DiscordMetadataKey key);
|
||||
enum EDiscordResult (*set_locked)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||
bool locked);
|
||||
};
|
||||
|
||||
struct IDiscordLobbyMemberTransaction {
|
||||
enum EDiscordResult (*set_metadata)(
|
||||
struct IDiscordLobbyMemberTransaction* lobby_member_transaction,
|
||||
DiscordMetadataKey key,
|
||||
DiscordMetadataValue value);
|
||||
enum EDiscordResult (*delete_metadata)(
|
||||
struct IDiscordLobbyMemberTransaction* lobby_member_transaction,
|
||||
DiscordMetadataKey key);
|
||||
};
|
||||
|
||||
struct IDiscordLobbySearchQuery {
|
||||
enum EDiscordResult (*filter)(struct IDiscordLobbySearchQuery* lobby_search_query,
|
||||
DiscordMetadataKey key,
|
||||
enum EDiscordLobbySearchComparison comparison,
|
||||
enum EDiscordLobbySearchCast cast,
|
||||
DiscordMetadataValue value);
|
||||
enum EDiscordResult (*sort)(struct IDiscordLobbySearchQuery* lobby_search_query,
|
||||
DiscordMetadataKey key,
|
||||
enum EDiscordLobbySearchCast cast,
|
||||
DiscordMetadataValue value);
|
||||
enum EDiscordResult (*limit)(struct IDiscordLobbySearchQuery* lobby_search_query,
|
||||
uint32_t limit);
|
||||
enum EDiscordResult (*distance)(struct IDiscordLobbySearchQuery* lobby_search_query,
|
||||
enum EDiscordLobbySearchDistance distance);
|
||||
};
|
||||
|
||||
typedef void* IDiscordApplicationEvents;
|
||||
|
||||
struct IDiscordApplicationManager {
|
||||
void (*validate_or_exit)(struct IDiscordApplicationManager* manager,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*get_current_locale)(struct IDiscordApplicationManager* manager, DiscordLocale* locale);
|
||||
void (*get_current_branch)(struct IDiscordApplicationManager* manager, DiscordBranch* branch);
|
||||
void (*get_oauth2_token)(struct IDiscordApplicationManager* manager,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
struct DiscordOAuth2Token* oauth2_token));
|
||||
};
|
||||
|
||||
struct IDiscordUserEvents {
|
||||
void (*on_current_user_update)(void* event_data);
|
||||
};
|
||||
|
||||
struct IDiscordUserManager {
|
||||
enum EDiscordResult (*get_current_user)(struct IDiscordUserManager* manager,
|
||||
struct DiscordUser* current_user);
|
||||
void (*get_user)(struct IDiscordUserManager* manager,
|
||||
DiscordUserId user_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
struct DiscordUser* user));
|
||||
};
|
||||
|
||||
typedef void* IDiscordImageEvents;
|
||||
|
||||
struct IDiscordImageManager {
|
||||
void (*fetch)(struct IDiscordImageManager* manager,
|
||||
struct DiscordImageHandle handle,
|
||||
bool refresh,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
struct DiscordImageHandle handle_result));
|
||||
enum EDiscordResult (*get_dimensions)(struct IDiscordImageManager* manager,
|
||||
struct DiscordImageHandle handle,
|
||||
struct DiscordImageDimensions* dimensions);
|
||||
enum EDiscordResult (*get_data)(struct IDiscordImageManager* manager,
|
||||
struct DiscordImageHandle handle,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
};
|
||||
|
||||
struct IDiscordActivityEvents {
|
||||
void (*on_activity_join)(void* event_data, const char* secret);
|
||||
void (*on_activity_spectate)(void* event_data, const char* secret);
|
||||
void (*on_activity_join_request)(void* event_data, struct DiscordUser* user);
|
||||
void (*on_activity_invite)(void* event_data,
|
||||
enum EDiscordActivityActionType type,
|
||||
struct DiscordUser* user,
|
||||
struct DiscordActivity* activity);
|
||||
};
|
||||
|
||||
struct IDiscordActivityManager {
|
||||
enum EDiscordResult (*register_command)(struct IDiscordActivityManager* manager,
|
||||
const char* command);
|
||||
enum EDiscordResult (*register_steam)(struct IDiscordActivityManager* manager,
|
||||
uint32_t steam_id);
|
||||
void (*update_activity)(struct IDiscordActivityManager* manager,
|
||||
struct DiscordActivity* activity,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*clear_activity)(struct IDiscordActivityManager* manager,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*send_request_reply)(struct IDiscordActivityManager* manager,
|
||||
DiscordUserId user_id,
|
||||
enum EDiscordActivityJoinRequestReply reply,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*send_invite)(struct IDiscordActivityManager* manager,
|
||||
DiscordUserId user_id,
|
||||
enum EDiscordActivityActionType type,
|
||||
const char* content,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*accept_invite)(struct IDiscordActivityManager* manager,
|
||||
DiscordUserId user_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
};
|
||||
|
||||
struct IDiscordRelationshipEvents {
|
||||
void (*on_refresh)(void* event_data);
|
||||
void (*on_relationship_update)(void* event_data, struct DiscordRelationship* relationship);
|
||||
};
|
||||
|
||||
struct IDiscordRelationshipManager {
|
||||
void (*filter)(struct IDiscordRelationshipManager* manager,
|
||||
void* filter_data,
|
||||
bool (*filter)(void* filter_data, struct DiscordRelationship* relationship));
|
||||
enum EDiscordResult (*count)(struct IDiscordRelationshipManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get)(struct IDiscordRelationshipManager* manager,
|
||||
DiscordUserId user_id,
|
||||
struct DiscordRelationship* relationship);
|
||||
enum EDiscordResult (*get_at)(struct IDiscordRelationshipManager* manager,
|
||||
uint32_t index,
|
||||
struct DiscordRelationship* relationship);
|
||||
};
|
||||
|
||||
struct IDiscordLobbyEvents {
|
||||
void (*on_lobby_update)(void* event_data, int64_t lobby_id);
|
||||
void (*on_lobby_delete)(void* event_data, int64_t lobby_id, uint32_t reason);
|
||||
void (*on_member_connect)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||
void (*on_member_update)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||
void (*on_member_disconnect)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||
void (*on_lobby_message)(void* event_data,
|
||||
int64_t lobby_id,
|
||||
int64_t user_id,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
void (*on_speaking)(void* event_data, int64_t lobby_id, int64_t user_id, bool speaking);
|
||||
void (*on_network_message)(void* event_data,
|
||||
int64_t lobby_id,
|
||||
int64_t user_id,
|
||||
uint8_t channel_id,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
};
|
||||
|
||||
struct IDiscordLobbyManager {
|
||||
enum EDiscordResult (*get_lobby_create_transaction)(
|
||||
struct IDiscordLobbyManager* manager,
|
||||
struct IDiscordLobbyTransaction** transaction);
|
||||
enum EDiscordResult (*get_lobby_update_transaction)(
|
||||
struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
struct IDiscordLobbyTransaction** transaction);
|
||||
enum EDiscordResult (*get_member_update_transaction)(
|
||||
struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
struct IDiscordLobbyMemberTransaction** transaction);
|
||||
void (*create_lobby)(struct IDiscordLobbyManager* manager,
|
||||
struct IDiscordLobbyTransaction* transaction,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
struct DiscordLobby* lobby));
|
||||
void (*update_lobby)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
struct IDiscordLobbyTransaction* transaction,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*delete_lobby)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*connect_lobby)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordLobbySecret secret,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
struct DiscordLobby* lobby));
|
||||
void (*connect_lobby_with_activity_secret)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbySecret activity_secret,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
struct DiscordLobby* lobby));
|
||||
void (*disconnect_lobby)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*get_lobby)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
struct DiscordLobby* lobby);
|
||||
enum EDiscordResult (*get_lobby_activity_secret)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordLobbySecret* secret);
|
||||
enum EDiscordResult (*get_lobby_metadata_value)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordMetadataKey key,
|
||||
DiscordMetadataValue* value);
|
||||
enum EDiscordResult (*get_lobby_metadata_key)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
int32_t index,
|
||||
DiscordMetadataKey* key);
|
||||
enum EDiscordResult (*lobby_metadata_count)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
int32_t* count);
|
||||
enum EDiscordResult (*member_count)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
int32_t* count);
|
||||
enum EDiscordResult (*get_member_user_id)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
int32_t index,
|
||||
DiscordUserId* user_id);
|
||||
enum EDiscordResult (*get_member_user)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
struct DiscordUser* user);
|
||||
enum EDiscordResult (*get_member_metadata_value)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
DiscordMetadataKey key,
|
||||
DiscordMetadataValue* value);
|
||||
enum EDiscordResult (*get_member_metadata_key)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
int32_t index,
|
||||
DiscordMetadataKey* key);
|
||||
enum EDiscordResult (*member_metadata_count)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
int32_t* count);
|
||||
void (*update_member)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
struct IDiscordLobbyMemberTransaction* transaction,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*send_lobby_message)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
uint8_t* data,
|
||||
uint32_t data_length,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*get_search_query)(struct IDiscordLobbyManager* manager,
|
||||
struct IDiscordLobbySearchQuery** query);
|
||||
void (*search)(struct IDiscordLobbyManager* manager,
|
||||
struct IDiscordLobbySearchQuery* query,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*lobby_count)(struct IDiscordLobbyManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get_lobby_id)(struct IDiscordLobbyManager* manager,
|
||||
int32_t index,
|
||||
DiscordLobbyId* lobby_id);
|
||||
void (*connect_voice)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*disconnect_voice)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*connect_network)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id);
|
||||
enum EDiscordResult (*disconnect_network)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id);
|
||||
enum EDiscordResult (*flush_network)(struct IDiscordLobbyManager* manager);
|
||||
enum EDiscordResult (*open_network_channel)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
uint8_t channel_id,
|
||||
bool reliable);
|
||||
enum EDiscordResult (*send_network_message)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
uint8_t channel_id,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
};
|
||||
|
||||
struct IDiscordNetworkEvents {
|
||||
void (*on_message)(void* event_data,
|
||||
DiscordNetworkPeerId peer_id,
|
||||
DiscordNetworkChannelId channel_id,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
void (*on_route_update)(void* event_data, const char* route_data);
|
||||
};
|
||||
|
||||
struct IDiscordNetworkManager {
|
||||
void (*get_peer_id)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId* peer_id);
|
||||
enum EDiscordResult (*flush)(struct IDiscordNetworkManager* manager);
|
||||
enum EDiscordResult (*open_peer)(struct IDiscordNetworkManager* manager,
|
||||
DiscordNetworkPeerId peer_id,
|
||||
const char* route_data);
|
||||
enum EDiscordResult (*update_peer)(struct IDiscordNetworkManager* manager,
|
||||
DiscordNetworkPeerId peer_id,
|
||||
const char* route_data);
|
||||
enum EDiscordResult (*close_peer)(struct IDiscordNetworkManager* manager,
|
||||
DiscordNetworkPeerId peer_id);
|
||||
enum EDiscordResult (*open_channel)(struct IDiscordNetworkManager* manager,
|
||||
DiscordNetworkPeerId peer_id,
|
||||
DiscordNetworkChannelId channel_id,
|
||||
bool reliable);
|
||||
enum EDiscordResult (*close_channel)(struct IDiscordNetworkManager* manager,
|
||||
DiscordNetworkPeerId peer_id,
|
||||
DiscordNetworkChannelId channel_id);
|
||||
enum EDiscordResult (*send_message)(struct IDiscordNetworkManager* manager,
|
||||
DiscordNetworkPeerId peer_id,
|
||||
DiscordNetworkChannelId channel_id,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
};
|
||||
|
||||
struct IDiscordOverlayEvents {
|
||||
void (*on_toggle)(void* event_data, bool locked);
|
||||
};
|
||||
|
||||
struct IDiscordOverlayManager {
|
||||
void (*is_enabled)(struct IDiscordOverlayManager* manager, bool* enabled);
|
||||
void (*is_locked)(struct IDiscordOverlayManager* manager, bool* locked);
|
||||
void (*set_locked)(struct IDiscordOverlayManager* manager,
|
||||
bool locked,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*open_activity_invite)(struct IDiscordOverlayManager* manager,
|
||||
enum EDiscordActivityActionType type,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*open_guild_invite)(struct IDiscordOverlayManager* manager,
|
||||
const char* code,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
};
|
||||
|
||||
typedef void* IDiscordStorageEvents;
|
||||
|
||||
struct IDiscordStorageManager {
|
||||
enum EDiscordResult (*read)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
uint8_t* data,
|
||||
uint32_t data_length,
|
||||
uint32_t* read);
|
||||
void (*read_async)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
uint8_t* data,
|
||||
uint32_t data_length));
|
||||
void (*read_async_partial)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
uint64_t offset,
|
||||
uint64_t length,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
uint8_t* data,
|
||||
uint32_t data_length));
|
||||
enum EDiscordResult (*write)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
void (*write_async)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
uint8_t* data,
|
||||
uint32_t data_length,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*delete_)(struct IDiscordStorageManager* manager, const char* name);
|
||||
enum EDiscordResult (*exists)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
bool* exists);
|
||||
void (*count)(struct IDiscordStorageManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*stat)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
struct DiscordFileStat* stat);
|
||||
enum EDiscordResult (*stat_at)(struct IDiscordStorageManager* manager,
|
||||
int32_t index,
|
||||
struct DiscordFileStat* stat);
|
||||
};
|
||||
|
||||
struct IDiscordStoreEvents {
|
||||
void (*on_entitlement_create)(void* event_data, struct DiscordEntitlement* entitlement);
|
||||
void (*on_entitlement_delete)(void* event_data, struct DiscordEntitlement* entitlement);
|
||||
};
|
||||
|
||||
struct IDiscordStoreManager {
|
||||
void (*fetch_skus)(struct IDiscordStoreManager* manager,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*count_skus)(struct IDiscordStoreManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get_sku)(struct IDiscordStoreManager* manager,
|
||||
DiscordSnowflake sku_id,
|
||||
struct DiscordSku* sku);
|
||||
enum EDiscordResult (*get_sku_at)(struct IDiscordStoreManager* manager,
|
||||
int32_t index,
|
||||
struct DiscordSku* sku);
|
||||
void (*fetch_entitlements)(struct IDiscordStoreManager* manager,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*count_entitlements)(struct IDiscordStoreManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get_entitlement)(struct IDiscordStoreManager* manager,
|
||||
DiscordSnowflake entitlement_id,
|
||||
struct DiscordEntitlement* entitlement);
|
||||
enum EDiscordResult (*get_entitlement_at)(struct IDiscordStoreManager* manager,
|
||||
int32_t index,
|
||||
struct DiscordEntitlement* entitlement);
|
||||
enum EDiscordResult (*has_sku_entitlement)(struct IDiscordStoreManager* manager,
|
||||
DiscordSnowflake sku_id,
|
||||
bool* has_entitlement);
|
||||
void (*start_purchase)(struct IDiscordStoreManager* manager,
|
||||
DiscordSnowflake sku_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
};
|
||||
|
||||
typedef void* IDiscordCoreEvents;
|
||||
|
||||
struct IDiscordCore {
|
||||
void (*destroy)(struct IDiscordCore* core);
|
||||
enum EDiscordResult (*run_callbacks)(struct IDiscordCore* core);
|
||||
void (*set_log_hook)(struct IDiscordCore* core,
|
||||
enum EDiscordLogLevel min_level,
|
||||
void* hook_data,
|
||||
void (*hook)(void* hook_data,
|
||||
enum EDiscordLogLevel level,
|
||||
const char* message));
|
||||
struct IDiscordApplicationManager* (*get_application_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordUserManager* (*get_user_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordImageManager* (*get_image_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordActivityManager* (*get_activity_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordRelationshipManager* (*get_relationship_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordLobbyManager* (*get_lobby_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordNetworkManager* (*get_network_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordOverlayManager* (*get_overlay_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordStorageManager* (*get_storage_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordStoreManager* (*get_store_manager)(struct IDiscordCore* core);
|
||||
};
|
||||
|
||||
struct DiscordCreateParams {
|
||||
DiscordClientId client_id;
|
||||
uint64_t flags;
|
||||
IDiscordCoreEvents* events;
|
||||
void* event_data;
|
||||
IDiscordApplicationEvents* application_events;
|
||||
DiscordVersion application_version;
|
||||
struct IDiscordUserEvents* user_events;
|
||||
DiscordVersion user_version;
|
||||
IDiscordImageEvents* image_events;
|
||||
DiscordVersion image_version;
|
||||
struct IDiscordActivityEvents* activity_events;
|
||||
DiscordVersion activity_version;
|
||||
struct IDiscordRelationshipEvents* relationship_events;
|
||||
DiscordVersion relationship_version;
|
||||
struct IDiscordLobbyEvents* lobby_events;
|
||||
DiscordVersion lobby_version;
|
||||
struct IDiscordNetworkEvents* network_events;
|
||||
DiscordVersion network_version;
|
||||
struct IDiscordOverlayEvents* overlay_events;
|
||||
DiscordVersion overlay_version;
|
||||
IDiscordStorageEvents* storage_events;
|
||||
DiscordVersion storage_version;
|
||||
struct IDiscordStoreEvents* store_events;
|
||||
DiscordVersion store_version;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
inline
|
||||
#else
|
||||
static
|
||||
#endif
|
||||
void
|
||||
DiscordCreateParamsSetDefault(struct DiscordCreateParams* params)
|
||||
{
|
||||
memset(params, 0, sizeof(struct DiscordCreateParams));
|
||||
params->application_version = DISCORD_APPLICATION_MANAGER_VERSION;
|
||||
params->user_version = DISCORD_USER_MANAGER_VERSION;
|
||||
params->image_version = DISCORD_IMAGE_MANAGER_VERSION;
|
||||
params->activity_version = DISCORD_ACTIVITY_MANAGER_VERSION;
|
||||
params->relationship_version = DISCORD_RELATIONSHIP_MANAGER_VERSION;
|
||||
params->lobby_version = DISCORD_LOBBY_MANAGER_VERSION;
|
||||
params->network_version = DISCORD_NETWORK_MANAGER_VERSION;
|
||||
params->overlay_version = DISCORD_OVERLAY_MANAGER_VERSION;
|
||||
params->storage_version = DISCORD_STORAGE_MANAGER_VERSION;
|
||||
params->store_version = DISCORD_STORE_MANAGER_VERSION;
|
||||
}
|
||||
|
||||
enum EDiscordResult DiscordCreate(DiscordVersion version,
|
||||
struct DiscordCreateParams* params,
|
||||
struct IDiscordCore** result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
57
source/application/discord/image_manager.cpp
Normal file
57
source/application/discord/image_manager.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "image_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
void ImageManager::Fetch(ImageHandle handle,
|
||||
bool refresh,
|
||||
std::function<void(Result, ImageHandle)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, DiscordImageHandle handleResult) -> void {
|
||||
std::unique_ptr<std::function<void(Result, ImageHandle)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, ImageHandle)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), *reinterpret_cast<ImageHandle const*>(&handleResult));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, ImageHandle)>> cb{};
|
||||
cb.reset(new std::function<void(Result, ImageHandle)>(std::move(callback)));
|
||||
internal_->fetch(internal_,
|
||||
*reinterpret_cast<DiscordImageHandle const*>(&handle),
|
||||
(refresh ? 1 : 0),
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
Result ImageManager::GetDimensions(ImageHandle handle, ImageDimensions* dimensions)
|
||||
{
|
||||
if (!dimensions) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_dimensions(internal_,
|
||||
*reinterpret_cast<DiscordImageHandle const*>(&handle),
|
||||
reinterpret_cast<DiscordImageDimensions*>(dimensions));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result ImageManager::GetData(ImageHandle handle, std::uint8_t* data, std::uint32_t dataLength)
|
||||
{
|
||||
auto result = internal_->get_data(internal_,
|
||||
*reinterpret_cast<DiscordImageHandle const*>(&handle),
|
||||
reinterpret_cast<uint8_t*>(data),
|
||||
dataLength);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
28
source/application/discord/image_manager.h
Normal file
28
source/application/discord/image_manager.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class ImageManager final {
|
||||
public:
|
||||
~ImageManager() = default;
|
||||
|
||||
void Fetch(ImageHandle handle, bool refresh, std::function<void(Result, ImageHandle)> callback);
|
||||
Result GetDimensions(ImageHandle handle, ImageDimensions* dimensions);
|
||||
Result GetData(ImageHandle handle, std::uint8_t* data, std::uint32_t dataLength);
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
ImageManager() = default;
|
||||
ImageManager(ImageManager const& rhs) = delete;
|
||||
ImageManager& operator=(ImageManager const& rhs) = delete;
|
||||
ImageManager(ImageManager&& rhs) = delete;
|
||||
ImageManager& operator=(ImageManager&& rhs) = delete;
|
||||
|
||||
IDiscordImageManager* internal_;
|
||||
static IDiscordImageEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
547
source/application/discord/lobby_manager.cpp
Normal file
547
source/application/discord/lobby_manager.cpp
Normal file
|
|
@ -0,0 +1,547 @@
|
|||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "lobby_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class LobbyEvents final {
|
||||
public:
|
||||
static void OnLobbyUpdate(void* callbackData, int64_t lobbyId)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnLobbyUpdate(lobbyId);
|
||||
}
|
||||
|
||||
static void OnLobbyDelete(void* callbackData, int64_t lobbyId, uint32_t reason)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnLobbyDelete(lobbyId, reason);
|
||||
}
|
||||
|
||||
static void OnMemberConnect(void* callbackData, int64_t lobbyId, int64_t userId)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnMemberConnect(lobbyId, userId);
|
||||
}
|
||||
|
||||
static void OnMemberUpdate(void* callbackData, int64_t lobbyId, int64_t userId)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnMemberUpdate(lobbyId, userId);
|
||||
}
|
||||
|
||||
static void OnMemberDisconnect(void* callbackData, int64_t lobbyId, int64_t userId)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnMemberDisconnect(lobbyId, userId);
|
||||
}
|
||||
|
||||
static void OnLobbyMessage(void* callbackData,
|
||||
int64_t lobbyId,
|
||||
int64_t userId,
|
||||
uint8_t* data,
|
||||
uint32_t dataLength)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnLobbyMessage(lobbyId, userId, data, dataLength);
|
||||
}
|
||||
|
||||
static void OnSpeaking(void* callbackData, int64_t lobbyId, int64_t userId, bool speaking)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnSpeaking(lobbyId, userId, (speaking != 0));
|
||||
}
|
||||
|
||||
static void OnNetworkMessage(void* callbackData,
|
||||
int64_t lobbyId,
|
||||
int64_t userId,
|
||||
uint8_t channelId,
|
||||
uint8_t* data,
|
||||
uint32_t dataLength)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnNetworkMessage(lobbyId, userId, channelId, data, dataLength);
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordLobbyEvents LobbyManager::events_{
|
||||
&LobbyEvents::OnLobbyUpdate,
|
||||
&LobbyEvents::OnLobbyDelete,
|
||||
&LobbyEvents::OnMemberConnect,
|
||||
&LobbyEvents::OnMemberUpdate,
|
||||
&LobbyEvents::OnMemberDisconnect,
|
||||
&LobbyEvents::OnLobbyMessage,
|
||||
&LobbyEvents::OnSpeaking,
|
||||
&LobbyEvents::OnNetworkMessage,
|
||||
};
|
||||
|
||||
Result LobbyManager::GetLobbyCreateTransaction(LobbyTransaction* transaction)
|
||||
{
|
||||
if (!transaction) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_lobby_create_transaction(internal_, transaction->Receive());
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetLobbyUpdateTransaction(LobbyId lobbyId, LobbyTransaction* transaction)
|
||||
{
|
||||
if (!transaction) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_lobby_update_transaction(internal_, lobbyId, transaction->Receive());
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetMemberUpdateTransaction(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
LobbyMemberTransaction* transaction)
|
||||
{
|
||||
if (!transaction) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_member_update_transaction(internal_, lobbyId, userId, transaction->Receive());
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void LobbyManager::CreateLobby(LobbyTransaction const& transaction,
|
||||
std::function<void(Result, Lobby const&)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void {
|
||||
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, Lobby const&)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), *reinterpret_cast<Lobby const*>(lobby));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb{};
|
||||
cb.reset(new std::function<void(Result, Lobby const&)>(std::move(callback)));
|
||||
internal_->create_lobby(
|
||||
internal_, const_cast<LobbyTransaction&>(transaction).Internal(), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::UpdateLobby(LobbyId lobbyId,
|
||||
LobbyTransaction const& transaction,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->update_lobby(internal_,
|
||||
lobbyId,
|
||||
const_cast<LobbyTransaction&>(transaction).Internal(),
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::DeleteLobby(LobbyId lobbyId, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->delete_lobby(internal_, lobbyId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::ConnectLobby(LobbyId lobbyId,
|
||||
LobbySecret secret,
|
||||
std::function<void(Result, Lobby const&)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void {
|
||||
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, Lobby const&)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), *reinterpret_cast<Lobby const*>(lobby));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb{};
|
||||
cb.reset(new std::function<void(Result, Lobby const&)>(std::move(callback)));
|
||||
internal_->connect_lobby(internal_, lobbyId, const_cast<char*>(secret), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::ConnectLobbyWithActivitySecret(
|
||||
LobbySecret activitySecret,
|
||||
std::function<void(Result, Lobby const&)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void {
|
||||
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, Lobby const&)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), *reinterpret_cast<Lobby const*>(lobby));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb{};
|
||||
cb.reset(new std::function<void(Result, Lobby const&)>(std::move(callback)));
|
||||
internal_->connect_lobby_with_activity_secret(
|
||||
internal_, const_cast<char*>(activitySecret), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::DisconnectLobby(LobbyId lobbyId, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->disconnect_lobby(internal_, lobbyId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetLobby(LobbyId lobbyId, Lobby* lobby)
|
||||
{
|
||||
if (!lobby) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_lobby(internal_, lobbyId, reinterpret_cast<DiscordLobby*>(lobby));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetLobbyActivitySecret(LobbyId lobbyId, char secret[128])
|
||||
{
|
||||
if (!secret) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_lobby_activity_secret(
|
||||
internal_, lobbyId, reinterpret_cast<DiscordLobbySecret*>(secret));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetLobbyMetadataValue(LobbyId lobbyId, MetadataKey key, char value[4096])
|
||||
{
|
||||
if (!value) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_lobby_metadata_value(
|
||||
internal_, lobbyId, const_cast<char*>(key), reinterpret_cast<DiscordMetadataValue*>(value));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetLobbyMetadataKey(LobbyId lobbyId, std::int32_t index, char key[256])
|
||||
{
|
||||
if (!key) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_lobby_metadata_key(
|
||||
internal_, lobbyId, index, reinterpret_cast<DiscordMetadataKey*>(key));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::LobbyMetadataCount(LobbyId lobbyId, std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->lobby_metadata_count(internal_, lobbyId, reinterpret_cast<int32_t*>(count));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::MemberCount(LobbyId lobbyId, std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->member_count(internal_, lobbyId, reinterpret_cast<int32_t*>(count));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetMemberUserId(LobbyId lobbyId, std::int32_t index, UserId* userId)
|
||||
{
|
||||
if (!userId) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_member_user_id(internal_, lobbyId, index, reinterpret_cast<int64_t*>(userId));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetMemberUser(LobbyId lobbyId, UserId userId, User* user)
|
||||
{
|
||||
if (!user) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_member_user(internal_, lobbyId, userId, reinterpret_cast<DiscordUser*>(user));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetMemberMetadataValue(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
MetadataKey key,
|
||||
char value[4096])
|
||||
{
|
||||
if (!value) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_member_metadata_value(internal_,
|
||||
lobbyId,
|
||||
userId,
|
||||
const_cast<char*>(key),
|
||||
reinterpret_cast<DiscordMetadataValue*>(value));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetMemberMetadataKey(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
std::int32_t index,
|
||||
char key[256])
|
||||
{
|
||||
if (!key) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_member_metadata_key(
|
||||
internal_, lobbyId, userId, index, reinterpret_cast<DiscordMetadataKey*>(key));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::MemberMetadataCount(LobbyId lobbyId, UserId userId, std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->member_metadata_count(
|
||||
internal_, lobbyId, userId, reinterpret_cast<int32_t*>(count));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void LobbyManager::UpdateMember(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
LobbyMemberTransaction const& transaction,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->update_member(internal_,
|
||||
lobbyId,
|
||||
userId,
|
||||
const_cast<LobbyMemberTransaction&>(transaction).Internal(),
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::SendLobbyMessage(LobbyId lobbyId,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->send_lobby_message(
|
||||
internal_, lobbyId, reinterpret_cast<uint8_t*>(data), dataLength, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetSearchQuery(LobbySearchQuery* query)
|
||||
{
|
||||
if (!query) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_search_query(internal_, query->Receive());
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void LobbyManager::Search(LobbySearchQuery const& query, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->search(
|
||||
internal_, const_cast<LobbySearchQuery&>(query).Internal(), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::LobbyCount(std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->lobby_count(internal_, reinterpret_cast<int32_t*>(count));
|
||||
}
|
||||
|
||||
Result LobbyManager::GetLobbyId(std::int32_t index, LobbyId* lobbyId)
|
||||
{
|
||||
if (!lobbyId) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_lobby_id(internal_, index, reinterpret_cast<int64_t*>(lobbyId));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void LobbyManager::ConnectVoice(LobbyId lobbyId, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->connect_voice(internal_, lobbyId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::DisconnectVoice(LobbyId lobbyId, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->disconnect_voice(internal_, lobbyId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
Result LobbyManager::ConnectNetwork(LobbyId lobbyId)
|
||||
{
|
||||
auto result = internal_->connect_network(internal_, lobbyId);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::DisconnectNetwork(LobbyId lobbyId)
|
||||
{
|
||||
auto result = internal_->disconnect_network(internal_, lobbyId);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::FlushNetwork()
|
||||
{
|
||||
auto result = internal_->flush_network(internal_);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::OpenNetworkChannel(LobbyId lobbyId, std::uint8_t channelId, bool reliable)
|
||||
{
|
||||
auto result =
|
||||
internal_->open_network_channel(internal_, lobbyId, channelId, (reliable ? 1 : 0));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::SendNetworkMessage(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
std::uint8_t channelId,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength)
|
||||
{
|
||||
auto result = internal_->send_network_message(
|
||||
internal_, lobbyId, userId, channelId, reinterpret_cast<uint8_t*>(data), dataLength);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
88
source/application/discord/lobby_manager.h
Normal file
88
source/application/discord/lobby_manager.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class LobbyManager final {
|
||||
public:
|
||||
~LobbyManager() = default;
|
||||
|
||||
Result GetLobbyCreateTransaction(LobbyTransaction* transaction);
|
||||
Result GetLobbyUpdateTransaction(LobbyId lobbyId, LobbyTransaction* transaction);
|
||||
Result GetMemberUpdateTransaction(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
LobbyMemberTransaction* transaction);
|
||||
void CreateLobby(LobbyTransaction const& transaction,
|
||||
std::function<void(Result, Lobby const&)> callback);
|
||||
void UpdateLobby(LobbyId lobbyId,
|
||||
LobbyTransaction const& transaction,
|
||||
std::function<void(Result)> callback);
|
||||
void DeleteLobby(LobbyId lobbyId, std::function<void(Result)> callback);
|
||||
void ConnectLobby(LobbyId lobbyId,
|
||||
LobbySecret secret,
|
||||
std::function<void(Result, Lobby const&)> callback);
|
||||
void ConnectLobbyWithActivitySecret(LobbySecret activitySecret,
|
||||
std::function<void(Result, Lobby const&)> callback);
|
||||
void DisconnectLobby(LobbyId lobbyId, std::function<void(Result)> callback);
|
||||
Result GetLobby(LobbyId lobbyId, Lobby* lobby);
|
||||
Result GetLobbyActivitySecret(LobbyId lobbyId, char secret[128]);
|
||||
Result GetLobbyMetadataValue(LobbyId lobbyId, MetadataKey key, char value[4096]);
|
||||
Result GetLobbyMetadataKey(LobbyId lobbyId, std::int32_t index, char key[256]);
|
||||
Result LobbyMetadataCount(LobbyId lobbyId, std::int32_t* count);
|
||||
Result MemberCount(LobbyId lobbyId, std::int32_t* count);
|
||||
Result GetMemberUserId(LobbyId lobbyId, std::int32_t index, UserId* userId);
|
||||
Result GetMemberUser(LobbyId lobbyId, UserId userId, User* user);
|
||||
Result GetMemberMetadataValue(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
MetadataKey key,
|
||||
char value[4096]);
|
||||
Result GetMemberMetadataKey(LobbyId lobbyId, UserId userId, std::int32_t index, char key[256]);
|
||||
Result MemberMetadataCount(LobbyId lobbyId, UserId userId, std::int32_t* count);
|
||||
void UpdateMember(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
LobbyMemberTransaction const& transaction,
|
||||
std::function<void(Result)> callback);
|
||||
void SendLobbyMessage(LobbyId lobbyId,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength,
|
||||
std::function<void(Result)> callback);
|
||||
Result GetSearchQuery(LobbySearchQuery* query);
|
||||
void Search(LobbySearchQuery const& query, std::function<void(Result)> callback);
|
||||
void LobbyCount(std::int32_t* count);
|
||||
Result GetLobbyId(std::int32_t index, LobbyId* lobbyId);
|
||||
void ConnectVoice(LobbyId lobbyId, std::function<void(Result)> callback);
|
||||
void DisconnectVoice(LobbyId lobbyId, std::function<void(Result)> callback);
|
||||
Result ConnectNetwork(LobbyId lobbyId);
|
||||
Result DisconnectNetwork(LobbyId lobbyId);
|
||||
Result FlushNetwork();
|
||||
Result OpenNetworkChannel(LobbyId lobbyId, std::uint8_t channelId, bool reliable);
|
||||
Result SendNetworkMessage(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
std::uint8_t channelId,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength);
|
||||
|
||||
Event<std::int64_t> OnLobbyUpdate;
|
||||
Event<std::int64_t, std::uint32_t> OnLobbyDelete;
|
||||
Event<std::int64_t, std::int64_t> OnMemberConnect;
|
||||
Event<std::int64_t, std::int64_t> OnMemberUpdate;
|
||||
Event<std::int64_t, std::int64_t> OnMemberDisconnect;
|
||||
Event<std::int64_t, std::int64_t, std::uint8_t*, std::uint32_t> OnLobbyMessage;
|
||||
Event<std::int64_t, std::int64_t, bool> OnSpeaking;
|
||||
Event<std::int64_t, std::int64_t, std::uint8_t, std::uint8_t*, std::uint32_t> OnNetworkMessage;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
LobbyManager() = default;
|
||||
LobbyManager(LobbyManager const& rhs) = delete;
|
||||
LobbyManager& operator=(LobbyManager const& rhs) = delete;
|
||||
LobbyManager(LobbyManager&& rhs) = delete;
|
||||
LobbyManager& operator=(LobbyManager&& rhs) = delete;
|
||||
|
||||
IDiscordLobbyManager* internal_;
|
||||
static IDiscordLobbyEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
103
source/application/discord/network_manager.cpp
Normal file
103
source/application/discord/network_manager.cpp
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "network_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class NetworkEvents final {
|
||||
public:
|
||||
static void OnMessage(void* callbackData,
|
||||
DiscordNetworkPeerId peerId,
|
||||
DiscordNetworkChannelId channelId,
|
||||
uint8_t* data,
|
||||
uint32_t dataLength)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->NetworkManager();
|
||||
module.OnMessage(peerId, channelId, data, dataLength);
|
||||
}
|
||||
|
||||
static void OnRouteUpdate(void* callbackData, char const* routeData)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->NetworkManager();
|
||||
module.OnRouteUpdate(static_cast<const char*>(routeData));
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordNetworkEvents NetworkManager::events_{
|
||||
&NetworkEvents::OnMessage,
|
||||
&NetworkEvents::OnRouteUpdate,
|
||||
};
|
||||
|
||||
void NetworkManager::GetPeerId(NetworkPeerId* peerId)
|
||||
{
|
||||
if (!peerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->get_peer_id(internal_, reinterpret_cast<uint64_t*>(peerId));
|
||||
}
|
||||
|
||||
Result NetworkManager::Flush()
|
||||
{
|
||||
auto result = internal_->flush(internal_);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result NetworkManager::OpenPeer(NetworkPeerId peerId, char const* routeData)
|
||||
{
|
||||
auto result = internal_->open_peer(internal_, peerId, const_cast<char*>(routeData));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result NetworkManager::UpdatePeer(NetworkPeerId peerId, char const* routeData)
|
||||
{
|
||||
auto result = internal_->update_peer(internal_, peerId, const_cast<char*>(routeData));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result NetworkManager::ClosePeer(NetworkPeerId peerId)
|
||||
{
|
||||
auto result = internal_->close_peer(internal_, peerId);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result NetworkManager::OpenChannel(NetworkPeerId peerId, NetworkChannelId channelId, bool reliable)
|
||||
{
|
||||
auto result = internal_->open_channel(internal_, peerId, channelId, (reliable ? 1 : 0));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result NetworkManager::CloseChannel(NetworkPeerId peerId, NetworkChannelId channelId)
|
||||
{
|
||||
auto result = internal_->close_channel(internal_, peerId, channelId);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result NetworkManager::SendMessage(NetworkPeerId peerId,
|
||||
NetworkChannelId channelId,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength)
|
||||
{
|
||||
auto result = internal_->send_message(
|
||||
internal_, peerId, channelId, reinterpret_cast<uint8_t*>(data), dataLength);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
39
source/application/discord/network_manager.h
Normal file
39
source/application/discord/network_manager.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class NetworkManager final {
|
||||
public:
|
||||
~NetworkManager() = default;
|
||||
|
||||
void GetPeerId(NetworkPeerId* peerId);
|
||||
Result Flush();
|
||||
Result OpenPeer(NetworkPeerId peerId, char const* routeData);
|
||||
Result UpdatePeer(NetworkPeerId peerId, char const* routeData);
|
||||
Result ClosePeer(NetworkPeerId peerId);
|
||||
Result OpenChannel(NetworkPeerId peerId, NetworkChannelId channelId, bool reliable);
|
||||
Result CloseChannel(NetworkPeerId peerId, NetworkChannelId channelId);
|
||||
Result SendMessage(NetworkPeerId peerId,
|
||||
NetworkChannelId channelId,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength);
|
||||
|
||||
Event<NetworkPeerId, NetworkChannelId, std::uint8_t*, std::uint32_t> OnMessage;
|
||||
Event<char const*> OnRouteUpdate;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
NetworkManager() = default;
|
||||
NetworkManager(NetworkManager const& rhs) = delete;
|
||||
NetworkManager& operator=(NetworkManager const& rhs) = delete;
|
||||
NetworkManager(NetworkManager&& rhs) = delete;
|
||||
NetworkManager& operator=(NetworkManager&& rhs) = delete;
|
||||
|
||||
IDiscordNetworkManager* internal_;
|
||||
static IDiscordNetworkEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
97
source/application/discord/overlay_manager.cpp
Normal file
97
source/application/discord/overlay_manager.cpp
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "overlay_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class OverlayEvents final {
|
||||
public:
|
||||
static void OnToggle(void* callbackData, bool locked)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->OverlayManager();
|
||||
module.OnToggle((locked != 0));
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordOverlayEvents OverlayManager::events_{
|
||||
&OverlayEvents::OnToggle,
|
||||
};
|
||||
|
||||
void OverlayManager::IsEnabled(bool* enabled)
|
||||
{
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->is_enabled(internal_, reinterpret_cast<bool*>(enabled));
|
||||
}
|
||||
|
||||
void OverlayManager::IsLocked(bool* locked)
|
||||
{
|
||||
if (!locked) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->is_locked(internal_, reinterpret_cast<bool*>(locked));
|
||||
}
|
||||
|
||||
void OverlayManager::SetLocked(bool locked, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->set_locked(internal_, (locked ? 1 : 0), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void OverlayManager::OpenActivityInvite(ActivityActionType type,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->open_activity_invite(
|
||||
internal_, static_cast<EDiscordActivityActionType>(type), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void OverlayManager::OpenGuildInvite(char const* code, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->open_guild_invite(internal_, const_cast<char*>(code), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
32
source/application/discord/overlay_manager.h
Normal file
32
source/application/discord/overlay_manager.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class OverlayManager final {
|
||||
public:
|
||||
~OverlayManager() = default;
|
||||
|
||||
void IsEnabled(bool* enabled);
|
||||
void IsLocked(bool* locked);
|
||||
void SetLocked(bool locked, std::function<void(Result)> callback);
|
||||
void OpenActivityInvite(ActivityActionType type, std::function<void(Result)> callback);
|
||||
void OpenGuildInvite(char const* code, std::function<void(Result)> callback);
|
||||
|
||||
Event<bool> OnToggle;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
OverlayManager() = default;
|
||||
OverlayManager(OverlayManager const& rhs) = delete;
|
||||
OverlayManager& operator=(OverlayManager const& rhs) = delete;
|
||||
OverlayManager(OverlayManager&& rhs) = delete;
|
||||
OverlayManager& operator=(OverlayManager&& rhs) = delete;
|
||||
|
||||
IDiscordOverlayManager* internal_;
|
||||
static IDiscordOverlayEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
90
source/application/discord/relationship_manager.cpp
Normal file
90
source/application/discord/relationship_manager.cpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "relationship_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class RelationshipEvents final {
|
||||
public:
|
||||
static void OnRefresh(void* callbackData)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->RelationshipManager();
|
||||
module.OnRefresh();
|
||||
}
|
||||
|
||||
static void OnRelationshipUpdate(void* callbackData, DiscordRelationship* relationship)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->RelationshipManager();
|
||||
module.OnRelationshipUpdate(*reinterpret_cast<Relationship const*>(relationship));
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordRelationshipEvents RelationshipManager::events_{
|
||||
&RelationshipEvents::OnRefresh,
|
||||
&RelationshipEvents::OnRelationshipUpdate,
|
||||
};
|
||||
|
||||
void RelationshipManager::Filter(std::function<bool(Relationship const&)> filter)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, DiscordRelationship* relationship) -> bool {
|
||||
auto cb(reinterpret_cast<std::function<bool(Relationship const&)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return {};
|
||||
}
|
||||
return (*cb)(*reinterpret_cast<Relationship const*>(relationship));
|
||||
};
|
||||
std::unique_ptr<std::function<bool(Relationship const&)>> cb{};
|
||||
cb.reset(new std::function<bool(Relationship const&)>(std::move(filter)));
|
||||
internal_->filter(internal_, cb.get(), wrapper);
|
||||
}
|
||||
|
||||
Result RelationshipManager::Count(std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->count(internal_, reinterpret_cast<int32_t*>(count));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result RelationshipManager::Get(UserId userId, Relationship* relationship)
|
||||
{
|
||||
if (!relationship) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get(internal_, userId, reinterpret_cast<DiscordRelationship*>(relationship));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result RelationshipManager::GetAt(std::uint32_t index, Relationship* relationship)
|
||||
{
|
||||
if (!relationship) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_at(internal_, index, reinterpret_cast<DiscordRelationship*>(relationship));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
32
source/application/discord/relationship_manager.h
Normal file
32
source/application/discord/relationship_manager.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class RelationshipManager final {
|
||||
public:
|
||||
~RelationshipManager() = default;
|
||||
|
||||
void Filter(std::function<bool(Relationship const&)> filter);
|
||||
Result Count(std::int32_t* count);
|
||||
Result Get(UserId userId, Relationship* relationship);
|
||||
Result GetAt(std::uint32_t index, Relationship* relationship);
|
||||
|
||||
Event<> OnRefresh;
|
||||
Event<Relationship const&> OnRelationshipUpdate;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
RelationshipManager() = default;
|
||||
RelationshipManager(RelationshipManager const& rhs) = delete;
|
||||
RelationshipManager& operator=(RelationshipManager const& rhs) = delete;
|
||||
RelationshipManager(RelationshipManager&& rhs) = delete;
|
||||
RelationshipManager& operator=(RelationshipManager&& rhs) = delete;
|
||||
|
||||
IDiscordRelationshipManager* internal_;
|
||||
static IDiscordRelationshipEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
148
source/application/discord/storage_manager.cpp
Normal file
148
source/application/discord/storage_manager.cpp
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "storage_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
Result StorageManager::Read(char const* name,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength,
|
||||
std::uint32_t* read)
|
||||
{
|
||||
if (!read) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->read(internal_,
|
||||
const_cast<char*>(name),
|
||||
reinterpret_cast<uint8_t*>(data),
|
||||
dataLength,
|
||||
reinterpret_cast<uint32_t*>(read));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void StorageManager::ReadAsync(char const* name,
|
||||
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, uint8_t* data, uint32_t dataLength) -> void {
|
||||
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, std::uint8_t*, std::uint32_t)>*>(
|
||||
callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), data, dataLength);
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb{};
|
||||
cb.reset(new std::function<void(Result, std::uint8_t*, std::uint32_t)>(std::move(callback)));
|
||||
internal_->read_async(internal_, const_cast<char*>(name), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void StorageManager::ReadAsyncPartial(
|
||||
char const* name,
|
||||
std::uint64_t offset,
|
||||
std::uint64_t length,
|
||||
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, uint8_t* data, uint32_t dataLength) -> void {
|
||||
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, std::uint8_t*, std::uint32_t)>*>(
|
||||
callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), data, dataLength);
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb{};
|
||||
cb.reset(new std::function<void(Result, std::uint8_t*, std::uint32_t)>(std::move(callback)));
|
||||
internal_->read_async_partial(
|
||||
internal_, const_cast<char*>(name), offset, length, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
Result StorageManager::Write(char const* name, std::uint8_t* data, std::uint32_t dataLength)
|
||||
{
|
||||
auto result = internal_->write(
|
||||
internal_, const_cast<char*>(name), reinterpret_cast<uint8_t*>(data), dataLength);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void StorageManager::WriteAsync(char const* name,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->write_async(internal_,
|
||||
const_cast<char*>(name),
|
||||
reinterpret_cast<uint8_t*>(data),
|
||||
dataLength,
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
Result StorageManager::Delete(char const* name)
|
||||
{
|
||||
auto result = internal_->delete_(internal_, const_cast<char*>(name));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result StorageManager::Exists(char const* name, bool* exists)
|
||||
{
|
||||
if (!exists) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->exists(internal_, const_cast<char*>(name), reinterpret_cast<bool*>(exists));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void StorageManager::Count(std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->count(internal_, reinterpret_cast<int32_t*>(count));
|
||||
}
|
||||
|
||||
Result StorageManager::Stat(char const* name, FileStat* stat)
|
||||
{
|
||||
if (!stat) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->stat(internal_, const_cast<char*>(name), reinterpret_cast<DiscordFileStat*>(stat));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result StorageManager::StatAt(std::int32_t index, FileStat* stat)
|
||||
{
|
||||
if (!stat) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->stat_at(internal_, index, reinterpret_cast<DiscordFileStat*>(stat));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
45
source/application/discord/storage_manager.h
Normal file
45
source/application/discord/storage_manager.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class StorageManager final {
|
||||
public:
|
||||
~StorageManager() = default;
|
||||
|
||||
Result Read(char const* name,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength,
|
||||
std::uint32_t* read);
|
||||
void ReadAsync(char const* name,
|
||||
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback);
|
||||
void ReadAsyncPartial(char const* name,
|
||||
std::uint64_t offset,
|
||||
std::uint64_t length,
|
||||
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback);
|
||||
Result Write(char const* name, std::uint8_t* data, std::uint32_t dataLength);
|
||||
void WriteAsync(char const* name,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength,
|
||||
std::function<void(Result)> callback);
|
||||
Result Delete(char const* name);
|
||||
Result Exists(char const* name, bool* exists);
|
||||
void Count(std::int32_t* count);
|
||||
Result Stat(char const* name, FileStat* stat);
|
||||
Result StatAt(std::int32_t index, FileStat* stat);
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
StorageManager() = default;
|
||||
StorageManager(StorageManager const& rhs) = delete;
|
||||
StorageManager& operator=(StorageManager const& rhs) = delete;
|
||||
StorageManager(StorageManager&& rhs) = delete;
|
||||
StorageManager& operator=(StorageManager&& rhs) = delete;
|
||||
|
||||
IDiscordStorageManager* internal_;
|
||||
static IDiscordStorageEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
160
source/application/discord/store_manager.cpp
Normal file
160
source/application/discord/store_manager.cpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "store_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class StoreEvents final {
|
||||
public:
|
||||
static void OnEntitlementCreate(void* callbackData, DiscordEntitlement* entitlement)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->StoreManager();
|
||||
module.OnEntitlementCreate(*reinterpret_cast<Entitlement const*>(entitlement));
|
||||
}
|
||||
|
||||
static void OnEntitlementDelete(void* callbackData, DiscordEntitlement* entitlement)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->StoreManager();
|
||||
module.OnEntitlementDelete(*reinterpret_cast<Entitlement const*>(entitlement));
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordStoreEvents StoreManager::events_{
|
||||
&StoreEvents::OnEntitlementCreate,
|
||||
&StoreEvents::OnEntitlementDelete,
|
||||
};
|
||||
|
||||
void StoreManager::FetchSkus(std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->fetch_skus(internal_, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void StoreManager::CountSkus(std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->count_skus(internal_, reinterpret_cast<int32_t*>(count));
|
||||
}
|
||||
|
||||
Result StoreManager::GetSku(Snowflake skuId, Sku* sku)
|
||||
{
|
||||
if (!sku) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_sku(internal_, skuId, reinterpret_cast<DiscordSku*>(sku));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result StoreManager::GetSkuAt(std::int32_t index, Sku* sku)
|
||||
{
|
||||
if (!sku) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_sku_at(internal_, index, reinterpret_cast<DiscordSku*>(sku));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void StoreManager::FetchEntitlements(std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->fetch_entitlements(internal_, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void StoreManager::CountEntitlements(std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->count_entitlements(internal_, reinterpret_cast<int32_t*>(count));
|
||||
}
|
||||
|
||||
Result StoreManager::GetEntitlement(Snowflake entitlementId, Entitlement* entitlement)
|
||||
{
|
||||
if (!entitlement) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_entitlement(
|
||||
internal_, entitlementId, reinterpret_cast<DiscordEntitlement*>(entitlement));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result StoreManager::GetEntitlementAt(std::int32_t index, Entitlement* entitlement)
|
||||
{
|
||||
if (!entitlement) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_entitlement_at(
|
||||
internal_, index, reinterpret_cast<DiscordEntitlement*>(entitlement));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result StoreManager::HasSkuEntitlement(Snowflake skuId, bool* hasEntitlement)
|
||||
{
|
||||
if (!hasEntitlement) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->has_sku_entitlement(internal_, skuId, reinterpret_cast<bool*>(hasEntitlement));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void StoreManager::StartPurchase(Snowflake skuId, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->start_purchase(internal_, skuId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
38
source/application/discord/store_manager.h
Normal file
38
source/application/discord/store_manager.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class StoreManager final {
|
||||
public:
|
||||
~StoreManager() = default;
|
||||
|
||||
void FetchSkus(std::function<void(Result)> callback);
|
||||
void CountSkus(std::int32_t* count);
|
||||
Result GetSku(Snowflake skuId, Sku* sku);
|
||||
Result GetSkuAt(std::int32_t index, Sku* sku);
|
||||
void FetchEntitlements(std::function<void(Result)> callback);
|
||||
void CountEntitlements(std::int32_t* count);
|
||||
Result GetEntitlement(Snowflake entitlementId, Entitlement* entitlement);
|
||||
Result GetEntitlementAt(std::int32_t index, Entitlement* entitlement);
|
||||
Result HasSkuEntitlement(Snowflake skuId, bool* hasEntitlement);
|
||||
void StartPurchase(Snowflake skuId, std::function<void(Result)> callback);
|
||||
|
||||
Event<Entitlement const&> OnEntitlementCreate;
|
||||
Event<Entitlement const&> OnEntitlementDelete;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
StoreManager() = default;
|
||||
StoreManager(StoreManager const& rhs) = delete;
|
||||
StoreManager& operator=(StoreManager const& rhs) = delete;
|
||||
StoreManager(StoreManager&& rhs) = delete;
|
||||
StoreManager& operator=(StoreManager&& rhs) = delete;
|
||||
|
||||
IDiscordStoreManager* internal_;
|
||||
static IDiscordStoreEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
707
source/application/discord/types.cpp
Normal file
707
source/application/discord/types.cpp
Normal file
|
|
@ -0,0 +1,707 @@
|
|||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
void User::SetId(UserId id)
|
||||
{
|
||||
internal_.id = id;
|
||||
}
|
||||
|
||||
UserId User::GetId() const
|
||||
{
|
||||
return internal_.id;
|
||||
}
|
||||
|
||||
void User::SetUsername(char const* username)
|
||||
{
|
||||
strncpy(internal_.username, username, 256);
|
||||
internal_.username[256 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* User::GetUsername() const
|
||||
{
|
||||
return internal_.username;
|
||||
}
|
||||
|
||||
void User::SetDiscriminator(char const* discriminator)
|
||||
{
|
||||
strncpy(internal_.discriminator, discriminator, 8);
|
||||
internal_.discriminator[8 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* User::GetDiscriminator() const
|
||||
{
|
||||
return internal_.discriminator;
|
||||
}
|
||||
|
||||
void User::SetAvatar(char const* avatar)
|
||||
{
|
||||
strncpy(internal_.avatar, avatar, 128);
|
||||
internal_.avatar[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* User::GetAvatar() const
|
||||
{
|
||||
return internal_.avatar;
|
||||
}
|
||||
|
||||
void User::SetBot(bool bot)
|
||||
{
|
||||
internal_.bot = bot;
|
||||
}
|
||||
|
||||
bool User::GetBot() const
|
||||
{
|
||||
return internal_.bot != 0;
|
||||
}
|
||||
|
||||
void OAuth2Token::SetAccessToken(char const* accessToken)
|
||||
{
|
||||
strncpy(internal_.access_token, accessToken, 128);
|
||||
internal_.access_token[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* OAuth2Token::GetAccessToken() const
|
||||
{
|
||||
return internal_.access_token;
|
||||
}
|
||||
|
||||
void OAuth2Token::SetScopes(char const* scopes)
|
||||
{
|
||||
strncpy(internal_.scopes, scopes, 1024);
|
||||
internal_.scopes[1024 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* OAuth2Token::GetScopes() const
|
||||
{
|
||||
return internal_.scopes;
|
||||
}
|
||||
|
||||
void OAuth2Token::SetExpires(Timestamp expires)
|
||||
{
|
||||
internal_.expires = expires;
|
||||
}
|
||||
|
||||
Timestamp OAuth2Token::GetExpires() const
|
||||
{
|
||||
return internal_.expires;
|
||||
}
|
||||
|
||||
void ImageHandle::SetType(ImageType type)
|
||||
{
|
||||
internal_.type = static_cast<EDiscordImageType>(type);
|
||||
}
|
||||
|
||||
ImageType ImageHandle::GetType() const
|
||||
{
|
||||
return static_cast<ImageType>(internal_.type);
|
||||
}
|
||||
|
||||
void ImageHandle::SetId(std::int64_t id)
|
||||
{
|
||||
internal_.id = id;
|
||||
}
|
||||
|
||||
std::int64_t ImageHandle::GetId() const
|
||||
{
|
||||
return internal_.id;
|
||||
}
|
||||
|
||||
void ImageHandle::SetSize(std::uint32_t size)
|
||||
{
|
||||
internal_.size = size;
|
||||
}
|
||||
|
||||
std::uint32_t ImageHandle::GetSize() const
|
||||
{
|
||||
return internal_.size;
|
||||
}
|
||||
|
||||
void ImageDimensions::SetWidth(std::uint32_t width)
|
||||
{
|
||||
internal_.width = width;
|
||||
}
|
||||
|
||||
std::uint32_t ImageDimensions::GetWidth() const
|
||||
{
|
||||
return internal_.width;
|
||||
}
|
||||
|
||||
void ImageDimensions::SetHeight(std::uint32_t height)
|
||||
{
|
||||
internal_.height = height;
|
||||
}
|
||||
|
||||
std::uint32_t ImageDimensions::GetHeight() const
|
||||
{
|
||||
return internal_.height;
|
||||
}
|
||||
|
||||
void ActivityTimestamps::SetStart(Timestamp start)
|
||||
{
|
||||
internal_.start = start;
|
||||
}
|
||||
|
||||
Timestamp ActivityTimestamps::GetStart() const
|
||||
{
|
||||
return internal_.start;
|
||||
}
|
||||
|
||||
void ActivityTimestamps::SetEnd(Timestamp end)
|
||||
{
|
||||
internal_.end = end;
|
||||
}
|
||||
|
||||
Timestamp ActivityTimestamps::GetEnd() const
|
||||
{
|
||||
return internal_.end;
|
||||
}
|
||||
|
||||
void ActivityAssets::SetLargeImage(char const* largeImage)
|
||||
{
|
||||
strncpy(internal_.large_image, largeImage, 128);
|
||||
internal_.large_image[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivityAssets::GetLargeImage() const
|
||||
{
|
||||
return internal_.large_image;
|
||||
}
|
||||
|
||||
void ActivityAssets::SetLargeText(char const* largeText)
|
||||
{
|
||||
strncpy(internal_.large_text, largeText, 128);
|
||||
internal_.large_text[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivityAssets::GetLargeText() const
|
||||
{
|
||||
return internal_.large_text;
|
||||
}
|
||||
|
||||
void ActivityAssets::SetSmallImage(char const* smallImage)
|
||||
{
|
||||
strncpy(internal_.small_image, smallImage, 128);
|
||||
internal_.small_image[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivityAssets::GetSmallImage() const
|
||||
{
|
||||
return internal_.small_image;
|
||||
}
|
||||
|
||||
void ActivityAssets::SetSmallText(char const* smallText)
|
||||
{
|
||||
strncpy(internal_.small_text, smallText, 128);
|
||||
internal_.small_text[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivityAssets::GetSmallText() const
|
||||
{
|
||||
return internal_.small_text;
|
||||
}
|
||||
|
||||
void PartySize::SetCurrentSize(std::int32_t currentSize)
|
||||
{
|
||||
internal_.current_size = currentSize;
|
||||
}
|
||||
|
||||
std::int32_t PartySize::GetCurrentSize() const
|
||||
{
|
||||
return internal_.current_size;
|
||||
}
|
||||
|
||||
void PartySize::SetMaxSize(std::int32_t maxSize)
|
||||
{
|
||||
internal_.max_size = maxSize;
|
||||
}
|
||||
|
||||
std::int32_t PartySize::GetMaxSize() const
|
||||
{
|
||||
return internal_.max_size;
|
||||
}
|
||||
|
||||
void ActivityParty::SetId(char const* id)
|
||||
{
|
||||
strncpy(internal_.id, id, 128);
|
||||
internal_.id[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivityParty::GetId() const
|
||||
{
|
||||
return internal_.id;
|
||||
}
|
||||
|
||||
PartySize& ActivityParty::GetSize()
|
||||
{
|
||||
return reinterpret_cast<PartySize&>(internal_.size);
|
||||
}
|
||||
|
||||
PartySize const& ActivityParty::GetSize() const
|
||||
{
|
||||
return reinterpret_cast<PartySize const&>(internal_.size);
|
||||
}
|
||||
|
||||
void ActivitySecrets::SetMatch(char const* match)
|
||||
{
|
||||
strncpy(internal_.match, match, 128);
|
||||
internal_.match[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivitySecrets::GetMatch() const
|
||||
{
|
||||
return internal_.match;
|
||||
}
|
||||
|
||||
void ActivitySecrets::SetJoin(char const* join)
|
||||
{
|
||||
strncpy(internal_.join, join, 128);
|
||||
internal_.join[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivitySecrets::GetJoin() const
|
||||
{
|
||||
return internal_.join;
|
||||
}
|
||||
|
||||
void ActivitySecrets::SetSpectate(char const* spectate)
|
||||
{
|
||||
strncpy(internal_.spectate, spectate, 128);
|
||||
internal_.spectate[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivitySecrets::GetSpectate() const
|
||||
{
|
||||
return internal_.spectate;
|
||||
}
|
||||
|
||||
void Activity::SetType(ActivityType type)
|
||||
{
|
||||
internal_.type = static_cast<EDiscordActivityType>(type);
|
||||
}
|
||||
|
||||
ActivityType Activity::GetType() const
|
||||
{
|
||||
return static_cast<ActivityType>(internal_.type);
|
||||
}
|
||||
|
||||
void Activity::SetApplicationId(std::int64_t applicationId)
|
||||
{
|
||||
internal_.application_id = applicationId;
|
||||
}
|
||||
|
||||
std::int64_t Activity::GetApplicationId() const
|
||||
{
|
||||
return internal_.application_id;
|
||||
}
|
||||
|
||||
void Activity::SetName(char const* name)
|
||||
{
|
||||
strncpy(internal_.name, name, 128);
|
||||
internal_.name[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* Activity::GetName() const
|
||||
{
|
||||
return internal_.name;
|
||||
}
|
||||
|
||||
void Activity::SetState(char const* state)
|
||||
{
|
||||
strncpy(internal_.state, state, 128);
|
||||
internal_.state[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* Activity::GetState() const
|
||||
{
|
||||
return internal_.state;
|
||||
}
|
||||
|
||||
void Activity::SetDetails(char const* details)
|
||||
{
|
||||
strncpy(internal_.details, details, 128);
|
||||
internal_.details[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* Activity::GetDetails() const
|
||||
{
|
||||
return internal_.details;
|
||||
}
|
||||
|
||||
ActivityTimestamps& Activity::GetTimestamps()
|
||||
{
|
||||
return reinterpret_cast<ActivityTimestamps&>(internal_.timestamps);
|
||||
}
|
||||
|
||||
ActivityTimestamps const& Activity::GetTimestamps() const
|
||||
{
|
||||
return reinterpret_cast<ActivityTimestamps const&>(internal_.timestamps);
|
||||
}
|
||||
|
||||
ActivityAssets& Activity::GetAssets()
|
||||
{
|
||||
return reinterpret_cast<ActivityAssets&>(internal_.assets);
|
||||
}
|
||||
|
||||
ActivityAssets const& Activity::GetAssets() const
|
||||
{
|
||||
return reinterpret_cast<ActivityAssets const&>(internal_.assets);
|
||||
}
|
||||
|
||||
ActivityParty& Activity::GetParty()
|
||||
{
|
||||
return reinterpret_cast<ActivityParty&>(internal_.party);
|
||||
}
|
||||
|
||||
ActivityParty const& Activity::GetParty() const
|
||||
{
|
||||
return reinterpret_cast<ActivityParty const&>(internal_.party);
|
||||
}
|
||||
|
||||
ActivitySecrets& Activity::GetSecrets()
|
||||
{
|
||||
return reinterpret_cast<ActivitySecrets&>(internal_.secrets);
|
||||
}
|
||||
|
||||
ActivitySecrets const& Activity::GetSecrets() const
|
||||
{
|
||||
return reinterpret_cast<ActivitySecrets const&>(internal_.secrets);
|
||||
}
|
||||
|
||||
void Activity::SetInstance(bool instance)
|
||||
{
|
||||
internal_.instance = instance;
|
||||
}
|
||||
|
||||
bool Activity::GetInstance() const
|
||||
{
|
||||
return internal_.instance != 0;
|
||||
}
|
||||
|
||||
void Presence::SetStatus(Status status)
|
||||
{
|
||||
internal_.status = static_cast<EDiscordStatus>(status);
|
||||
}
|
||||
|
||||
Status Presence::GetStatus() const
|
||||
{
|
||||
return static_cast<Status>(internal_.status);
|
||||
}
|
||||
|
||||
Activity& Presence::GetActivity()
|
||||
{
|
||||
return reinterpret_cast<Activity&>(internal_.activity);
|
||||
}
|
||||
|
||||
Activity const& Presence::GetActivity() const
|
||||
{
|
||||
return reinterpret_cast<Activity const&>(internal_.activity);
|
||||
}
|
||||
|
||||
void Relationship::SetType(RelationshipType type)
|
||||
{
|
||||
internal_.type = static_cast<EDiscordRelationshipType>(type);
|
||||
}
|
||||
|
||||
RelationshipType Relationship::GetType() const
|
||||
{
|
||||
return static_cast<RelationshipType>(internal_.type);
|
||||
}
|
||||
|
||||
User& Relationship::GetUser()
|
||||
{
|
||||
return reinterpret_cast<User&>(internal_.user);
|
||||
}
|
||||
|
||||
User const& Relationship::GetUser() const
|
||||
{
|
||||
return reinterpret_cast<User const&>(internal_.user);
|
||||
}
|
||||
|
||||
Presence& Relationship::GetPresence()
|
||||
{
|
||||
return reinterpret_cast<Presence&>(internal_.presence);
|
||||
}
|
||||
|
||||
Presence const& Relationship::GetPresence() const
|
||||
{
|
||||
return reinterpret_cast<Presence const&>(internal_.presence);
|
||||
}
|
||||
|
||||
void Lobby::SetId(LobbyId id)
|
||||
{
|
||||
internal_.id = id;
|
||||
}
|
||||
|
||||
LobbyId Lobby::GetId() const
|
||||
{
|
||||
return internal_.id;
|
||||
}
|
||||
|
||||
void Lobby::SetType(LobbyType type)
|
||||
{
|
||||
internal_.type = static_cast<EDiscordLobbyType>(type);
|
||||
}
|
||||
|
||||
LobbyType Lobby::GetType() const
|
||||
{
|
||||
return static_cast<LobbyType>(internal_.type);
|
||||
}
|
||||
|
||||
void Lobby::SetOwnerId(UserId ownerId)
|
||||
{
|
||||
internal_.owner_id = ownerId;
|
||||
}
|
||||
|
||||
UserId Lobby::GetOwnerId() const
|
||||
{
|
||||
return internal_.owner_id;
|
||||
}
|
||||
|
||||
void Lobby::SetSecret(LobbySecret secret)
|
||||
{
|
||||
strncpy(internal_.secret, secret, 128);
|
||||
internal_.secret[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
LobbySecret Lobby::GetSecret() const
|
||||
{
|
||||
return internal_.secret;
|
||||
}
|
||||
|
||||
void Lobby::SetCapacity(std::uint32_t capacity)
|
||||
{
|
||||
internal_.capacity = capacity;
|
||||
}
|
||||
|
||||
std::uint32_t Lobby::GetCapacity() const
|
||||
{
|
||||
return internal_.capacity;
|
||||
}
|
||||
|
||||
void Lobby::SetLocked(bool locked)
|
||||
{
|
||||
internal_.locked = locked;
|
||||
}
|
||||
|
||||
bool Lobby::GetLocked() const
|
||||
{
|
||||
return internal_.locked != 0;
|
||||
}
|
||||
|
||||
void FileStat::SetFilename(char const* filename)
|
||||
{
|
||||
strncpy(internal_.filename, filename, 260);
|
||||
internal_.filename[260 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* FileStat::GetFilename() const
|
||||
{
|
||||
return internal_.filename;
|
||||
}
|
||||
|
||||
void FileStat::SetSize(std::uint64_t size)
|
||||
{
|
||||
internal_.size = size;
|
||||
}
|
||||
|
||||
std::uint64_t FileStat::GetSize() const
|
||||
{
|
||||
return internal_.size;
|
||||
}
|
||||
|
||||
void FileStat::SetLastModified(std::uint64_t lastModified)
|
||||
{
|
||||
internal_.last_modified = lastModified;
|
||||
}
|
||||
|
||||
std::uint64_t FileStat::GetLastModified() const
|
||||
{
|
||||
return internal_.last_modified;
|
||||
}
|
||||
|
||||
void Entitlement::SetId(Snowflake id)
|
||||
{
|
||||
internal_.id = id;
|
||||
}
|
||||
|
||||
Snowflake Entitlement::GetId() const
|
||||
{
|
||||
return internal_.id;
|
||||
}
|
||||
|
||||
void Entitlement::SetType(EntitlementType type)
|
||||
{
|
||||
internal_.type = static_cast<EDiscordEntitlementType>(type);
|
||||
}
|
||||
|
||||
EntitlementType Entitlement::GetType() const
|
||||
{
|
||||
return static_cast<EntitlementType>(internal_.type);
|
||||
}
|
||||
|
||||
void Entitlement::SetSkuId(Snowflake skuId)
|
||||
{
|
||||
internal_.sku_id = skuId;
|
||||
}
|
||||
|
||||
Snowflake Entitlement::GetSkuId() const
|
||||
{
|
||||
return internal_.sku_id;
|
||||
}
|
||||
|
||||
void SkuPrice::SetAmount(std::uint32_t amount)
|
||||
{
|
||||
internal_.amount = amount;
|
||||
}
|
||||
|
||||
std::uint32_t SkuPrice::GetAmount() const
|
||||
{
|
||||
return internal_.amount;
|
||||
}
|
||||
|
||||
void SkuPrice::SetCurrency(char const* currency)
|
||||
{
|
||||
strncpy(internal_.currency, currency, 16);
|
||||
internal_.currency[16 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* SkuPrice::GetCurrency() const
|
||||
{
|
||||
return internal_.currency;
|
||||
}
|
||||
|
||||
void Sku::SetId(Snowflake id)
|
||||
{
|
||||
internal_.id = id;
|
||||
}
|
||||
|
||||
Snowflake Sku::GetId() const
|
||||
{
|
||||
return internal_.id;
|
||||
}
|
||||
|
||||
void Sku::SetType(SkuType type)
|
||||
{
|
||||
internal_.type = static_cast<EDiscordSkuType>(type);
|
||||
}
|
||||
|
||||
SkuType Sku::GetType() const
|
||||
{
|
||||
return static_cast<SkuType>(internal_.type);
|
||||
}
|
||||
|
||||
void Sku::SetName(char const* name)
|
||||
{
|
||||
strncpy(internal_.name, name, 256);
|
||||
internal_.name[256 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* Sku::GetName() const
|
||||
{
|
||||
return internal_.name;
|
||||
}
|
||||
|
||||
SkuPrice& Sku::GetPrice()
|
||||
{
|
||||
return reinterpret_cast<SkuPrice&>(internal_.price);
|
||||
}
|
||||
|
||||
SkuPrice const& Sku::GetPrice() const
|
||||
{
|
||||
return reinterpret_cast<SkuPrice const&>(internal_.price);
|
||||
}
|
||||
|
||||
Result LobbyTransaction::SetType(LobbyType type)
|
||||
{
|
||||
auto result = internal_->set_type(internal_, static_cast<EDiscordLobbyType>(type));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyTransaction::SetOwner(UserId ownerId)
|
||||
{
|
||||
auto result = internal_->set_owner(internal_, ownerId);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyTransaction::SetCapacity(std::uint32_t capacity)
|
||||
{
|
||||
auto result = internal_->set_capacity(internal_, capacity);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyTransaction::SetMetadata(MetadataKey key, MetadataValue value)
|
||||
{
|
||||
auto result =
|
||||
internal_->set_metadata(internal_, const_cast<char*>(key), const_cast<char*>(value));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyTransaction::DeleteMetadata(MetadataKey key)
|
||||
{
|
||||
auto result = internal_->delete_metadata(internal_, const_cast<char*>(key));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyTransaction::SetLocked(bool locked)
|
||||
{
|
||||
auto result = internal_->set_locked(internal_, (locked ? 1 : 0));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyMemberTransaction::SetMetadata(MetadataKey key, MetadataValue value)
|
||||
{
|
||||
auto result =
|
||||
internal_->set_metadata(internal_, const_cast<char*>(key), const_cast<char*>(value));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyMemberTransaction::DeleteMetadata(MetadataKey key)
|
||||
{
|
||||
auto result = internal_->delete_metadata(internal_, const_cast<char*>(key));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbySearchQuery::Filter(MetadataKey key,
|
||||
LobbySearchComparison comparison,
|
||||
LobbySearchCast cast,
|
||||
MetadataValue value)
|
||||
{
|
||||
auto result = internal_->filter(internal_,
|
||||
const_cast<char*>(key),
|
||||
static_cast<EDiscordLobbySearchComparison>(comparison),
|
||||
static_cast<EDiscordLobbySearchCast>(cast),
|
||||
const_cast<char*>(value));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbySearchQuery::Sort(MetadataKey key, LobbySearchCast cast, MetadataValue value)
|
||||
{
|
||||
auto result = internal_->sort(internal_,
|
||||
const_cast<char*>(key),
|
||||
static_cast<EDiscordLobbySearchCast>(cast),
|
||||
const_cast<char*>(value));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbySearchQuery::Limit(std::uint32_t limit)
|
||||
{
|
||||
auto result = internal_->limit(internal_, limit);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbySearchQuery::Distance(LobbySearchDistance distance)
|
||||
{
|
||||
auto result =
|
||||
internal_->distance(internal_, static_cast<EDiscordLobbySearchDistance>(distance));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
426
source/application/discord/types.h
Normal file
426
source/application/discord/types.h
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
#pragma once
|
||||
|
||||
#include "ffi.h"
|
||||
#include "event.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
enum class Result {
|
||||
Ok,
|
||||
ServiceUnavailable,
|
||||
InvalidVersion,
|
||||
LockFailed,
|
||||
InternalError,
|
||||
InvalidPayload,
|
||||
InvalidCommand,
|
||||
InvalidPermissions,
|
||||
NotFetched,
|
||||
NotFound,
|
||||
Conflict,
|
||||
InvalidSecret,
|
||||
InvalidJoinSecret,
|
||||
NoEligibleActivity,
|
||||
InvalidInvite,
|
||||
NotAuthenticated,
|
||||
InvalidAccessToken,
|
||||
ApplicationMismatch,
|
||||
InvalidDataUrl,
|
||||
InvalidBase64,
|
||||
NotFiltered,
|
||||
LobbyFull,
|
||||
InvalidLobbySecret,
|
||||
InvalidFilename,
|
||||
InvalidFileSize,
|
||||
InvalidEntitlement,
|
||||
NotInstalled,
|
||||
NotRunning,
|
||||
InsufficientBuffer,
|
||||
PurchaseCanceled,
|
||||
};
|
||||
|
||||
enum class CreateFlags {
|
||||
Default = 0,
|
||||
NoRequireDiscord = 1,
|
||||
};
|
||||
|
||||
enum class LogLevel {
|
||||
Error = 1,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
};
|
||||
|
||||
enum class ImageType {
|
||||
User,
|
||||
};
|
||||
|
||||
enum class ActivityType {
|
||||
Playing,
|
||||
Streaming,
|
||||
Listening,
|
||||
Watching,
|
||||
};
|
||||
|
||||
enum class ActivityActionType {
|
||||
Join = 1,
|
||||
Spectate,
|
||||
};
|
||||
|
||||
enum class ActivityJoinRequestReply {
|
||||
No,
|
||||
Yes,
|
||||
Ignore,
|
||||
};
|
||||
|
||||
enum class Status {
|
||||
Offline = 0,
|
||||
Online = 1,
|
||||
Idle = 2,
|
||||
DoNotDisturb = 3,
|
||||
};
|
||||
|
||||
enum class RelationshipType {
|
||||
None,
|
||||
Friend,
|
||||
Blocked,
|
||||
PendingIncoming,
|
||||
PendingOutgoing,
|
||||
Implicit,
|
||||
};
|
||||
|
||||
enum class LobbyType {
|
||||
Private = 1,
|
||||
Public,
|
||||
};
|
||||
|
||||
enum class LobbySearchComparison {
|
||||
LessThanOrEqual = -2,
|
||||
LessThan,
|
||||
Equal,
|
||||
GreaterThan,
|
||||
GreaterThanOrEqual,
|
||||
NotEqual,
|
||||
};
|
||||
|
||||
enum class LobbySearchCast {
|
||||
String = 1,
|
||||
Number,
|
||||
};
|
||||
|
||||
enum class LobbySearchDistance {
|
||||
Local,
|
||||
Default,
|
||||
Extended,
|
||||
Global,
|
||||
};
|
||||
|
||||
enum class EntitlementType {
|
||||
Purchase = 1,
|
||||
PremiumSubscription,
|
||||
DeveloperGift,
|
||||
};
|
||||
|
||||
enum class SkuType {
|
||||
Application = 1,
|
||||
DLC,
|
||||
Consumable,
|
||||
Bundle,
|
||||
};
|
||||
|
||||
using ClientId = std::int64_t;
|
||||
using Version = std::int32_t;
|
||||
using Snowflake = std::int64_t;
|
||||
using Timestamp = std::int64_t;
|
||||
using UserId = Snowflake;
|
||||
using Locale = char const*;
|
||||
using Branch = char const*;
|
||||
using LobbyId = Snowflake;
|
||||
using LobbySecret = char const*;
|
||||
using MetadataKey = char const*;
|
||||
using MetadataValue = char const*;
|
||||
using NetworkPeerId = std::uint64_t;
|
||||
using NetworkChannelId = std::uint8_t;
|
||||
|
||||
class User final {
|
||||
public:
|
||||
void SetId(UserId id);
|
||||
UserId GetId() const;
|
||||
void SetUsername(char const* username);
|
||||
char const* GetUsername() const;
|
||||
void SetDiscriminator(char const* discriminator);
|
||||
char const* GetDiscriminator() const;
|
||||
void SetAvatar(char const* avatar);
|
||||
char const* GetAvatar() const;
|
||||
void SetBot(bool bot);
|
||||
bool GetBot() const;
|
||||
|
||||
private:
|
||||
DiscordUser internal_;
|
||||
};
|
||||
|
||||
class OAuth2Token final {
|
||||
public:
|
||||
void SetAccessToken(char const* accessToken);
|
||||
char const* GetAccessToken() const;
|
||||
void SetScopes(char const* scopes);
|
||||
char const* GetScopes() const;
|
||||
void SetExpires(Timestamp expires);
|
||||
Timestamp GetExpires() const;
|
||||
|
||||
private:
|
||||
DiscordOAuth2Token internal_;
|
||||
};
|
||||
|
||||
class ImageHandle final {
|
||||
public:
|
||||
void SetType(ImageType type);
|
||||
ImageType GetType() const;
|
||||
void SetId(std::int64_t id);
|
||||
std::int64_t GetId() const;
|
||||
void SetSize(std::uint32_t size);
|
||||
std::uint32_t GetSize() const;
|
||||
|
||||
private:
|
||||
DiscordImageHandle internal_;
|
||||
};
|
||||
|
||||
class ImageDimensions final {
|
||||
public:
|
||||
void SetWidth(std::uint32_t width);
|
||||
std::uint32_t GetWidth() const;
|
||||
void SetHeight(std::uint32_t height);
|
||||
std::uint32_t GetHeight() const;
|
||||
|
||||
private:
|
||||
DiscordImageDimensions internal_;
|
||||
};
|
||||
|
||||
class ActivityTimestamps final {
|
||||
public:
|
||||
void SetStart(Timestamp start);
|
||||
Timestamp GetStart() const;
|
||||
void SetEnd(Timestamp end);
|
||||
Timestamp GetEnd() const;
|
||||
|
||||
private:
|
||||
DiscordActivityTimestamps internal_;
|
||||
};
|
||||
|
||||
class ActivityAssets final {
|
||||
public:
|
||||
void SetLargeImage(char const* largeImage);
|
||||
char const* GetLargeImage() const;
|
||||
void SetLargeText(char const* largeText);
|
||||
char const* GetLargeText() const;
|
||||
void SetSmallImage(char const* smallImage);
|
||||
char const* GetSmallImage() const;
|
||||
void SetSmallText(char const* smallText);
|
||||
char const* GetSmallText() const;
|
||||
|
||||
private:
|
||||
DiscordActivityAssets internal_;
|
||||
};
|
||||
|
||||
class PartySize final {
|
||||
public:
|
||||
void SetCurrentSize(std::int32_t currentSize);
|
||||
std::int32_t GetCurrentSize() const;
|
||||
void SetMaxSize(std::int32_t maxSize);
|
||||
std::int32_t GetMaxSize() const;
|
||||
|
||||
private:
|
||||
DiscordPartySize internal_;
|
||||
};
|
||||
|
||||
class ActivityParty final {
|
||||
public:
|
||||
void SetId(char const* id);
|
||||
char const* GetId() const;
|
||||
PartySize& GetSize();
|
||||
PartySize const& GetSize() const;
|
||||
|
||||
private:
|
||||
DiscordActivityParty internal_;
|
||||
};
|
||||
|
||||
class ActivitySecrets final {
|
||||
public:
|
||||
void SetMatch(char const* match);
|
||||
char const* GetMatch() const;
|
||||
void SetJoin(char const* join);
|
||||
char const* GetJoin() const;
|
||||
void SetSpectate(char const* spectate);
|
||||
char const* GetSpectate() const;
|
||||
|
||||
private:
|
||||
DiscordActivitySecrets internal_;
|
||||
};
|
||||
|
||||
class Activity final {
|
||||
public:
|
||||
void SetType(ActivityType type);
|
||||
ActivityType GetType() const;
|
||||
void SetApplicationId(std::int64_t applicationId);
|
||||
std::int64_t GetApplicationId() const;
|
||||
void SetName(char const* name);
|
||||
char const* GetName() const;
|
||||
void SetState(char const* state);
|
||||
char const* GetState() const;
|
||||
void SetDetails(char const* details);
|
||||
char const* GetDetails() const;
|
||||
ActivityTimestamps& GetTimestamps();
|
||||
ActivityTimestamps const& GetTimestamps() const;
|
||||
ActivityAssets& GetAssets();
|
||||
ActivityAssets const& GetAssets() const;
|
||||
ActivityParty& GetParty();
|
||||
ActivityParty const& GetParty() const;
|
||||
ActivitySecrets& GetSecrets();
|
||||
ActivitySecrets const& GetSecrets() const;
|
||||
void SetInstance(bool instance);
|
||||
bool GetInstance() const;
|
||||
|
||||
private:
|
||||
DiscordActivity internal_;
|
||||
};
|
||||
|
||||
class Presence final {
|
||||
public:
|
||||
void SetStatus(Status status);
|
||||
Status GetStatus() const;
|
||||
Activity& GetActivity();
|
||||
Activity const& GetActivity() const;
|
||||
|
||||
private:
|
||||
DiscordPresence internal_;
|
||||
};
|
||||
|
||||
class Relationship final {
|
||||
public:
|
||||
void SetType(RelationshipType type);
|
||||
RelationshipType GetType() const;
|
||||
User& GetUser();
|
||||
User const& GetUser() const;
|
||||
Presence& GetPresence();
|
||||
Presence const& GetPresence() const;
|
||||
|
||||
private:
|
||||
DiscordRelationship internal_;
|
||||
};
|
||||
|
||||
class Lobby final {
|
||||
public:
|
||||
void SetId(LobbyId id);
|
||||
LobbyId GetId() const;
|
||||
void SetType(LobbyType type);
|
||||
LobbyType GetType() const;
|
||||
void SetOwnerId(UserId ownerId);
|
||||
UserId GetOwnerId() const;
|
||||
void SetSecret(LobbySecret secret);
|
||||
LobbySecret GetSecret() const;
|
||||
void SetCapacity(std::uint32_t capacity);
|
||||
std::uint32_t GetCapacity() const;
|
||||
void SetLocked(bool locked);
|
||||
bool GetLocked() const;
|
||||
|
||||
private:
|
||||
DiscordLobby internal_;
|
||||
};
|
||||
|
||||
class FileStat final {
|
||||
public:
|
||||
void SetFilename(char const* filename);
|
||||
char const* GetFilename() const;
|
||||
void SetSize(std::uint64_t size);
|
||||
std::uint64_t GetSize() const;
|
||||
void SetLastModified(std::uint64_t lastModified);
|
||||
std::uint64_t GetLastModified() const;
|
||||
|
||||
private:
|
||||
DiscordFileStat internal_;
|
||||
};
|
||||
|
||||
class Entitlement final {
|
||||
public:
|
||||
void SetId(Snowflake id);
|
||||
Snowflake GetId() const;
|
||||
void SetType(EntitlementType type);
|
||||
EntitlementType GetType() const;
|
||||
void SetSkuId(Snowflake skuId);
|
||||
Snowflake GetSkuId() const;
|
||||
|
||||
private:
|
||||
DiscordEntitlement internal_;
|
||||
};
|
||||
|
||||
class SkuPrice final {
|
||||
public:
|
||||
void SetAmount(std::uint32_t amount);
|
||||
std::uint32_t GetAmount() const;
|
||||
void SetCurrency(char const* currency);
|
||||
char const* GetCurrency() const;
|
||||
|
||||
private:
|
||||
DiscordSkuPrice internal_;
|
||||
};
|
||||
|
||||
class Sku final {
|
||||
public:
|
||||
void SetId(Snowflake id);
|
||||
Snowflake GetId() const;
|
||||
void SetType(SkuType type);
|
||||
SkuType GetType() const;
|
||||
void SetName(char const* name);
|
||||
char const* GetName() const;
|
||||
SkuPrice& GetPrice();
|
||||
SkuPrice const& GetPrice() const;
|
||||
|
||||
private:
|
||||
DiscordSku internal_;
|
||||
};
|
||||
|
||||
class LobbyTransaction final {
|
||||
public:
|
||||
Result SetType(LobbyType type);
|
||||
Result SetOwner(UserId ownerId);
|
||||
Result SetCapacity(std::uint32_t capacity);
|
||||
Result SetMetadata(MetadataKey key, MetadataValue value);
|
||||
Result DeleteMetadata(MetadataKey key);
|
||||
Result SetLocked(bool locked);
|
||||
|
||||
IDiscordLobbyTransaction** Receive() { return &internal_; }
|
||||
IDiscordLobbyTransaction* Internal() { return internal_; }
|
||||
|
||||
private:
|
||||
IDiscordLobbyTransaction* internal_;
|
||||
};
|
||||
|
||||
class LobbyMemberTransaction final {
|
||||
public:
|
||||
Result SetMetadata(MetadataKey key, MetadataValue value);
|
||||
Result DeleteMetadata(MetadataKey key);
|
||||
|
||||
IDiscordLobbyMemberTransaction** Receive() { return &internal_; }
|
||||
IDiscordLobbyMemberTransaction* Internal() { return internal_; }
|
||||
|
||||
private:
|
||||
IDiscordLobbyMemberTransaction* internal_;
|
||||
};
|
||||
|
||||
class LobbySearchQuery final {
|
||||
public:
|
||||
Result Filter(MetadataKey key,
|
||||
LobbySearchComparison comparison,
|
||||
LobbySearchCast cast,
|
||||
MetadataValue value);
|
||||
Result Sort(MetadataKey key, LobbySearchCast cast, MetadataValue value);
|
||||
Result Limit(std::uint32_t limit);
|
||||
Result Distance(LobbySearchDistance distance);
|
||||
|
||||
IDiscordLobbySearchQuery** Receive() { return &internal_; }
|
||||
IDiscordLobbySearchQuery* Internal() { return internal_; }
|
||||
|
||||
private:
|
||||
IDiscordLobbySearchQuery* internal_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
58
source/application/discord/user_manager.cpp
Normal file
58
source/application/discord/user_manager.cpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "user_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class UserEvents final {
|
||||
public:
|
||||
static void OnCurrentUserUpdate(void* callbackData)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->UserManager();
|
||||
module.OnCurrentUserUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordUserEvents UserManager::events_{
|
||||
&UserEvents::OnCurrentUserUpdate,
|
||||
};
|
||||
|
||||
Result UserManager::GetCurrentUser(User* currentUser)
|
||||
{
|
||||
if (!currentUser) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_current_user(internal_, reinterpret_cast<DiscordUser*>(currentUser));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void UserManager::GetUser(UserId userId, std::function<void(Result, User const&)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result, DiscordUser* user) -> void {
|
||||
std::unique_ptr<std::function<void(Result, User const&)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, User const&)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), *reinterpret_cast<User const*>(user));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, User const&)>> cb{};
|
||||
cb.reset(new std::function<void(Result, User const&)>(std::move(callback)));
|
||||
internal_->get_user(internal_, userId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
29
source/application/discord/user_manager.h
Normal file
29
source/application/discord/user_manager.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class UserManager final {
|
||||
public:
|
||||
~UserManager() = default;
|
||||
|
||||
Result GetCurrentUser(User* currentUser);
|
||||
void GetUser(UserId userId, std::function<void(Result, User const&)> callback);
|
||||
|
||||
Event<> OnCurrentUserUpdate;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
UserManager() = default;
|
||||
UserManager(UserManager const& rhs) = delete;
|
||||
UserManager& operator=(UserManager const& rhs) = delete;
|
||||
UserManager(UserManager&& rhs) = delete;
|
||||
UserManager& operator=(UserManager&& rhs) = delete;
|
||||
|
||||
IDiscordUserManager* internal_;
|
||||
static IDiscordUserEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
37
source/base/CMakeLists.txt
Normal file
37
source/base/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
INCLUDE_DIRECTORIES (
|
||||
${STAR_EXTERN_INCLUDES}
|
||||
${STAR_CORE_INCLUDES}
|
||||
${STAR_BASE_INCLUDES}
|
||||
)
|
||||
|
||||
SET (star_base_HEADERS
|
||||
StarAnimatedPartSet.hpp
|
||||
StarAssets.hpp
|
||||
StarAssetSource.hpp
|
||||
StarBlocksAlongLine.hpp
|
||||
StarCellularLightArray.hpp
|
||||
StarCellularLighting.hpp
|
||||
StarCellularLiquid.hpp
|
||||
StarConfiguration.hpp
|
||||
StarDirectoryAssetSource.hpp
|
||||
StarMixer.hpp
|
||||
StarPackedAssetSource.hpp
|
||||
StarVersion.hpp
|
||||
StarVersionOptionParser.hpp
|
||||
StarWorldGeometry.hpp
|
||||
)
|
||||
|
||||
SET (star_base_SOURCES
|
||||
StarAnimatedPartSet.cpp
|
||||
StarAssets.cpp
|
||||
StarCellularLighting.cpp
|
||||
StarConfiguration.cpp
|
||||
StarDirectoryAssetSource.cpp
|
||||
StarMixer.cpp
|
||||
StarPackedAssetSource.cpp
|
||||
StarVersionOptionParser.cpp
|
||||
StarWorldGeometry.cpp
|
||||
)
|
||||
|
||||
CONFIGURE_FILE (StarVersion.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp)
|
||||
ADD_LIBRARY (star_base OBJECT ${star_base_SOURCES} ${star_base_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/StarVersion.cpp)
|
||||
305
source/base/StarAnimatedPartSet.cpp
Normal file
305
source/base/StarAnimatedPartSet.cpp
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
#include "StarAnimatedPartSet.hpp"
|
||||
#include "StarMathCommon.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
AnimatedPartSet::AnimatedPartSet() {}
|
||||
|
||||
AnimatedPartSet::AnimatedPartSet(Json config) {
|
||||
for (auto const& stateTypePair : config.get("stateTypes", JsonObject()).iterateObject()) {
|
||||
auto const& stateTypeName = stateTypePair.first;
|
||||
auto const& stateTypeConfig = stateTypePair.second;
|
||||
|
||||
StateType newStateType;
|
||||
newStateType.priority = stateTypeConfig.getFloat("priority", 0.0f);
|
||||
newStateType.enabled = stateTypeConfig.getBool("enabled", true);
|
||||
newStateType.defaultState = stateTypeConfig.getString("default", "");
|
||||
newStateType.stateTypeProperties = stateTypeConfig.getObject("properties", {});
|
||||
|
||||
for (auto const& statePair : stateTypeConfig.get("states", JsonObject()).iterateObject()) {
|
||||
auto const& stateName = statePair.first;
|
||||
auto const& stateConfig = statePair.second;
|
||||
|
||||
auto newState = make_shared<State>();
|
||||
newState->frames = stateConfig.getInt("frames", 1);
|
||||
newState->cycle = stateConfig.getFloat("cycle", 1.0f);
|
||||
newState->animationMode = stringToAnimationMode(stateConfig.getString("mode", "end"));
|
||||
newState->transitionState = stateConfig.getString("transition", "");
|
||||
newState->stateProperties = stateConfig.getObject("properties", {});
|
||||
newState->stateFrameProperties = stateConfig.getObject("frameProperties", {});
|
||||
newStateType.states[stateName] = move(newState);
|
||||
}
|
||||
|
||||
newStateType.states.sortByKey();
|
||||
|
||||
newStateType.activeState.stateTypeName = stateTypeName;
|
||||
newStateType.activeStateDirty = true;
|
||||
|
||||
if (newStateType.defaultState.empty() && !newStateType.states.empty())
|
||||
newStateType.defaultState = newStateType.states.firstKey();
|
||||
|
||||
m_stateTypes[stateTypeName] = move(newStateType);
|
||||
}
|
||||
|
||||
// Sort state types by decreasing priority.
|
||||
m_stateTypes.sort([](pair<String, StateType> const& a, pair<String, StateType> const& b) {
|
||||
return b.second.priority < a.second.priority;
|
||||
});
|
||||
|
||||
for (auto const& partPair : config.get("parts", JsonObject()).iterateObject()) {
|
||||
auto const& partName = partPair.first;
|
||||
auto const& partConfig = partPair.second;
|
||||
|
||||
Part newPart;
|
||||
newPart.partProperties = partConfig.getObject("properties", {});
|
||||
|
||||
for (auto const& partStateTypePair : partConfig.get("partStates", JsonObject()).iterateObject()) {
|
||||
auto const& stateTypeName = partStateTypePair.first;
|
||||
|
||||
for (auto const& partStatePair : partStateTypePair.second.toObject()) {
|
||||
auto const& stateName = partStatePair.first;
|
||||
auto const& stateConfig = partStatePair.second;
|
||||
|
||||
PartState partState = {stateConfig.getObject("properties", {}), stateConfig.getObject("frameProperties", {})};
|
||||
newPart.partStates[stateTypeName][stateName] = move(partState);
|
||||
}
|
||||
}
|
||||
newPart.activePart.partName = partPair.first;
|
||||
newPart.activePartDirty = true;
|
||||
|
||||
m_parts[partName] = move(newPart);
|
||||
}
|
||||
|
||||
for (auto const& pair : m_stateTypes)
|
||||
setActiveState(pair.first, pair.second.defaultState, true);
|
||||
}
|
||||
|
||||
StringList AnimatedPartSet::stateTypes() const {
|
||||
return m_stateTypes.keys();
|
||||
}
|
||||
|
||||
void AnimatedPartSet::setStateTypeEnabled(String const& stateTypeName, bool enabled) {
|
||||
auto& stateType = m_stateTypes.get(stateTypeName);
|
||||
if (stateType.enabled != enabled) {
|
||||
stateType.enabled = enabled;
|
||||
for (auto& pair : m_parts)
|
||||
pair.second.activePartDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedPartSet::setEnabledStateTypes(StringList const& stateTypeNames) {
|
||||
for (auto& pair : m_stateTypes)
|
||||
pair.second.enabled = false;
|
||||
|
||||
for (auto const& stateTypeName : stateTypeNames)
|
||||
m_stateTypes.get(stateTypeName).enabled = true;
|
||||
|
||||
for (auto& pair : m_parts)
|
||||
pair.second.activePartDirty = true;
|
||||
}
|
||||
|
||||
bool AnimatedPartSet::stateTypeEnabled(String const& stateTypeName) const {
|
||||
return m_stateTypes.get(stateTypeName).enabled;
|
||||
}
|
||||
|
||||
StringList AnimatedPartSet::states(String const& stateTypeName) const {
|
||||
return m_stateTypes.get(stateTypeName).states.keys();
|
||||
}
|
||||
|
||||
StringList AnimatedPartSet::parts() const {
|
||||
return m_parts.keys();
|
||||
}
|
||||
|
||||
bool AnimatedPartSet::setActiveState(String const& stateTypeName, String const& stateName, bool alwaysStart) {
|
||||
auto& stateType = m_stateTypes.get(stateTypeName);
|
||||
if (stateType.activeState.stateName != stateName || alwaysStart) {
|
||||
stateType.activeState.stateName = stateName;
|
||||
stateType.activeState.timer = 0.0f;
|
||||
stateType.activeStatePointer = stateType.states.get(stateName).get();
|
||||
|
||||
stateType.activeStateDirty = true;
|
||||
for (auto& pair : m_parts)
|
||||
pair.second.activePartDirty = true;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedPartSet::restartState(String const& stateTypeName) {
|
||||
auto& stateType = m_stateTypes.get(stateTypeName);
|
||||
stateType.activeState.timer = 0.0f;
|
||||
|
||||
stateType.activeStateDirty = true;
|
||||
for (auto& pair : m_parts)
|
||||
pair.second.activePartDirty = true;
|
||||
}
|
||||
|
||||
AnimatedPartSet::ActiveStateInformation const& AnimatedPartSet::activeState(String const& stateTypeName) const {
|
||||
auto& stateType = const_cast<StateType&>(m_stateTypes.get(stateTypeName));
|
||||
const_cast<AnimatedPartSet*>(this)->freshenActiveState(stateType);
|
||||
return stateType.activeState;
|
||||
}
|
||||
|
||||
AnimatedPartSet::ActivePartInformation const& AnimatedPartSet::activePart(String const& partName) const {
|
||||
auto& part = const_cast<Part&>(m_parts.get(partName));
|
||||
const_cast<AnimatedPartSet*>(this)->freshenActivePart(part);
|
||||
return part.activePart;
|
||||
}
|
||||
|
||||
void AnimatedPartSet::forEachActiveState(function<void(String const&, ActiveStateInformation const&)> callback) const {
|
||||
for (auto const& p : m_stateTypes) {
|
||||
const_cast<AnimatedPartSet*>(this)->freshenActiveState(const_cast<StateType&>(p.second));
|
||||
callback(p.first, p.second.activeState);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedPartSet::forEachActivePart(function<void(String const&, ActivePartInformation const&)> callback) const {
|
||||
for (auto const& p : m_parts) {
|
||||
const_cast<AnimatedPartSet*>(this)->freshenActivePart(const_cast<Part&>(p.second));
|
||||
callback(p.first, p.second.activePart);
|
||||
}
|
||||
}
|
||||
|
||||
size_t AnimatedPartSet::activeStateIndex(String const& stateTypeName) const {
|
||||
auto const& stateType = m_stateTypes.get(stateTypeName);
|
||||
return *stateType.states.indexOf(stateType.activeState.stateName);
|
||||
}
|
||||
|
||||
bool AnimatedPartSet::setActiveStateIndex(String const& stateTypeName, size_t stateIndex, bool alwaysStart) {
|
||||
auto const& stateType = m_stateTypes.get(stateTypeName);
|
||||
String const& stateName = stateType.states.keyAt(stateIndex);
|
||||
return setActiveState(stateTypeName, stateName, alwaysStart);
|
||||
}
|
||||
|
||||
void AnimatedPartSet::update(float dt) {
|
||||
for (auto& pair : m_stateTypes) {
|
||||
auto& stateType = pair.second;
|
||||
auto const& state = *stateType.activeStatePointer;
|
||||
|
||||
stateType.activeState.timer += dt;
|
||||
if (stateType.activeState.timer > state.cycle) {
|
||||
if (state.animationMode == End) {
|
||||
stateType.activeState.timer = state.cycle;
|
||||
} else if (state.animationMode == Loop) {
|
||||
stateType.activeState.timer = std::fmod(stateType.activeState.timer, state.cycle);
|
||||
} else if (state.animationMode == Transition) {
|
||||
stateType.activeState.stateName = state.transitionState;
|
||||
stateType.activeState.timer = 0.0f;
|
||||
stateType.activeStatePointer = stateType.states.get(state.transitionState).get();
|
||||
}
|
||||
}
|
||||
|
||||
stateType.activeStateDirty = true;
|
||||
}
|
||||
|
||||
for (auto& pair : m_parts)
|
||||
pair.second.activePartDirty = true;
|
||||
}
|
||||
|
||||
void AnimatedPartSet::finishAnimations() {
|
||||
for (auto& pair : m_stateTypes) {
|
||||
auto& stateType = pair.second;
|
||||
|
||||
while (true) {
|
||||
auto const& state = *stateType.activeStatePointer;
|
||||
|
||||
if (state.animationMode == End) {
|
||||
stateType.activeState.timer = state.cycle;
|
||||
} else if (state.animationMode == Transition) {
|
||||
stateType.activeState.stateName = state.transitionState;
|
||||
stateType.activeState.timer = 0.0f;
|
||||
stateType.activeStatePointer = stateType.states.get(state.transitionState).get();
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
stateType.activeStateDirty = true;
|
||||
}
|
||||
|
||||
for (auto& pair : m_parts)
|
||||
pair.second.activePartDirty = true;
|
||||
}
|
||||
|
||||
AnimatedPartSet::AnimationMode AnimatedPartSet::stringToAnimationMode(String const& string) {
|
||||
if (string.equals("end", String::CaseInsensitive)) {
|
||||
return End;
|
||||
} else if (string.equals("loop", String::CaseInsensitive)) {
|
||||
return Loop;
|
||||
} else if (string.equals("transition", String::CaseInsensitive)) {
|
||||
return Transition;
|
||||
} else {
|
||||
throw AnimatedPartSetException(strf("No such AnimationMode '%s'", string));
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedPartSet::freshenActiveState(StateType& stateType) {
|
||||
if (stateType.activeStateDirty) {
|
||||
auto const& state = *stateType.activeStatePointer;
|
||||
auto& activeState = stateType.activeState;
|
||||
activeState.frame = clamp<int>(activeState.timer / state.cycle * state.frames, 0, state.frames - 1);
|
||||
|
||||
activeState.properties = stateType.stateTypeProperties;
|
||||
activeState.properties.merge(state.stateProperties, true);
|
||||
|
||||
for (auto const& pair : state.stateFrameProperties) {
|
||||
if (activeState.frame < pair.second.size())
|
||||
activeState.properties[pair.first] = pair.second.get(activeState.frame);
|
||||
}
|
||||
|
||||
stateType.activeStateDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedPartSet::freshenActivePart(Part& part) {
|
||||
if (part.activePartDirty) {
|
||||
// First reset all the active part information assuming that no state type
|
||||
// x state match exists.
|
||||
auto& activePart = part.activePart;
|
||||
activePart.activeState = {};
|
||||
activePart.properties = part.partProperties;
|
||||
|
||||
// Then go through each of the state types and states and look for a part
|
||||
// state match in order of priority.
|
||||
for (auto& stateTypePair : m_stateTypes) {
|
||||
auto const& stateTypeName = stateTypePair.first;
|
||||
auto& stateType = stateTypePair.second;
|
||||
|
||||
// Skip disabled state types
|
||||
if (!stateType.enabled)
|
||||
continue;
|
||||
|
||||
auto partStateType = part.partStates.ptr(stateTypeName);
|
||||
if (!partStateType)
|
||||
continue;
|
||||
|
||||
auto const& stateName = stateType.activeState.stateName;
|
||||
auto partState = partStateType->ptr(stateName);
|
||||
if (!partState)
|
||||
continue;
|
||||
|
||||
// If we have a partState match, then set the active state information.
|
||||
freshenActiveState(stateType);
|
||||
activePart.activeState = stateType.activeState;
|
||||
unsigned frame = stateType.activeState.frame;
|
||||
|
||||
// Then set the part state data, as well as any part state frame data if
|
||||
// the current frame is within the list size.
|
||||
activePart.properties.merge(partState->partStateProperties, true);
|
||||
|
||||
for (auto const& pair : partState->partStateFrameProperties) {
|
||||
if (frame < pair.second.size())
|
||||
activePart.properties[pair.first] = pair.second.get(frame);
|
||||
}
|
||||
|
||||
// Each part can only have one state type x state match, so we are done.
|
||||
break;
|
||||
}
|
||||
|
||||
part.activePartDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
163
source/base/StarAnimatedPartSet.hpp
Normal file
163
source/base/StarAnimatedPartSet.hpp
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
#ifndef STAR_ANIMATED_PART_SET_HPP
|
||||
#define STAR_ANIMATED_PART_SET_HPP
|
||||
|
||||
#include "StarOrderedMap.hpp"
|
||||
#include "StarJson.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_EXCEPTION(AnimatedPartSetException, StarException);
|
||||
|
||||
// Defines a "animated" data set constructed in such a way that it is very
|
||||
// useful for doing generic animations with lots of additional animation data.
|
||||
// It is made up of two concepts, "states" and "parts".
|
||||
//
|
||||
// States:
|
||||
//
|
||||
// There are N "state types" defined, which each defines a set of mutually
|
||||
// exclusive states that each "state type" can be in. For example, one state
|
||||
// type might be "movement", and the "movement" states might be "idle", "walk",
|
||||
// and "run. Another state type might be "attack" which could have as its
|
||||
// states "idle", and "melee". Each state type will have exactly one currently
|
||||
// active state, so this class may, for example, be in the total state of
|
||||
// "movement:idle" and "attack:melee". Each state within each state type is
|
||||
// animated, so that over time the state frame increases and may loop around,
|
||||
// or transition into another state so that that state type without interaction
|
||||
// may go from "melee" to "idle" when the "melee" state animation is finished.
|
||||
// This is defined by the individual state config in the configuration passed
|
||||
// into the constructor.
|
||||
//
|
||||
// Parts:
|
||||
//
|
||||
// Each instance of this class also can have N "Parts" defined, which are
|
||||
// groups of properties that "listen" to active states. Each part can "listen"
|
||||
// to one or more state types, and the first matching state x state type pair
|
||||
// (in order of state type priority which is specified in the config) is
|
||||
// chosen, and the properties from that state type and state are merged into
|
||||
// the part to produce the final active part information. Rather than having a
|
||||
// single image or image set for each part, since this class is intended to be
|
||||
// as generic as possible, all of this data is assumed to be queried from the
|
||||
// part properties, so that things such as image data as well as other things
|
||||
// like damage or collision polys can be stored along with the animation
|
||||
// frames, the part state, the base part, whichever is most applicable.
|
||||
class AnimatedPartSet {
|
||||
public:
|
||||
struct ActiveStateInformation {
|
||||
String stateTypeName;
|
||||
String stateName;
|
||||
float timer;
|
||||
unsigned frame;
|
||||
JsonObject properties;
|
||||
};
|
||||
|
||||
struct ActivePartInformation {
|
||||
String partName;
|
||||
// If a state match is found, this will be set.
|
||||
Maybe<ActiveStateInformation> activeState;
|
||||
JsonObject properties;
|
||||
};
|
||||
|
||||
AnimatedPartSet();
|
||||
AnimatedPartSet(Json config);
|
||||
|
||||
// Returns the available state types.
|
||||
StringList stateTypes() const;
|
||||
|
||||
// If a state type is disabled, no parts will match against it even
|
||||
// if they have entries for that state type.
|
||||
void setStateTypeEnabled(String const& stateTypeName, bool enabled);
|
||||
void setEnabledStateTypes(StringList const& stateTypeNames);
|
||||
bool stateTypeEnabled(String const& stateTypeName) const;
|
||||
|
||||
// Returns the available states for the given state type.
|
||||
StringList states(String const& stateTypeName) const;
|
||||
|
||||
StringList parts() const;
|
||||
|
||||
// Sets the active state for this state type. If the state is different than
|
||||
// the previously set state, will start the new states animation off at the
|
||||
// beginning. If alwaysStart is true, then starts the state animation off at
|
||||
// the beginning even if no state change has occurred. Returns true if a
|
||||
// state animation reset was done.
|
||||
bool setActiveState(String const& stateTypeName, String const& stateName, bool alwaysStart = false);
|
||||
|
||||
// Restart this given state type's timer off at the beginning.
|
||||
void restartState(String const& stateTypeName);
|
||||
|
||||
ActiveStateInformation const& activeState(String const& stateTypeName) const;
|
||||
ActivePartInformation const& activePart(String const& partName) const;
|
||||
|
||||
// Function will be given the name of each state type, and the
|
||||
// ActiveStateInformation for the active state for that state type.
|
||||
void forEachActiveState(function<void(String const&, ActiveStateInformation const&)> callback) const;
|
||||
|
||||
// Function will be given the name of each part, and the
|
||||
// ActivePartInformation for the active part.
|
||||
void forEachActivePart(function<void(String const&, ActivePartInformation const&)> callback) const;
|
||||
|
||||
// Useful for serializing state changes. Since each set of states for a
|
||||
// state type is ordered, it is possible to simply serialize and deserialize
|
||||
// the state index for that state type.
|
||||
size_t activeStateIndex(String const& stateTypeName) const;
|
||||
bool setActiveStateIndex(String const& stateTypeName, size_t stateIndex, bool alwaysStart = false);
|
||||
|
||||
// Animate each state type forward 'dt' time, and either change state frames
|
||||
// or transition to new states, depending on the config.
|
||||
void update(float dt);
|
||||
|
||||
// Pushes all the animations into their final state
|
||||
void finishAnimations();
|
||||
|
||||
private:
|
||||
enum AnimationMode {
|
||||
End,
|
||||
Loop,
|
||||
Transition
|
||||
};
|
||||
|
||||
struct State {
|
||||
unsigned frames;
|
||||
float cycle;
|
||||
AnimationMode animationMode;
|
||||
String transitionState;
|
||||
JsonObject stateProperties;
|
||||
JsonObject stateFrameProperties;
|
||||
};
|
||||
|
||||
struct StateType {
|
||||
float priority;
|
||||
bool enabled;
|
||||
String defaultState;
|
||||
JsonObject stateTypeProperties;
|
||||
OrderedHashMap<String, shared_ptr<State const>> states;
|
||||
|
||||
ActiveStateInformation activeState;
|
||||
State const* activeStatePointer;
|
||||
bool activeStateDirty;
|
||||
};
|
||||
|
||||
struct PartState {
|
||||
JsonObject partStateProperties;
|
||||
JsonObject partStateFrameProperties;
|
||||
};
|
||||
|
||||
struct Part {
|
||||
JsonObject partProperties;
|
||||
StringMap<StringMap<PartState>> partStates;
|
||||
|
||||
ActivePartInformation activePart;
|
||||
bool activePartDirty;
|
||||
};
|
||||
|
||||
static AnimationMode stringToAnimationMode(String const& string);
|
||||
|
||||
void freshenActiveState(StateType& stateType);
|
||||
void freshenActivePart(Part& part);
|
||||
|
||||
OrderedHashMap<String, StateType> m_stateTypes;
|
||||
StringMap<Part> m_parts;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
35
source/base/StarAssetSource.hpp
Normal file
35
source/base/StarAssetSource.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef STAR_ASSET_SOURCE_HPP
|
||||
#define STAR_ASSET_SOURCE_HPP
|
||||
|
||||
#include "StarIODevice.hpp"
|
||||
#include "StarJson.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(AssetSource);
|
||||
|
||||
STAR_EXCEPTION(AssetSourceException, StarException);
|
||||
|
||||
// An asset source could be a directory on a filesystem, where assets are
|
||||
// pulled directly from files, or a single pak-like file containing all assets,
|
||||
// where assets are pulled from the correct region of the pak-like file.
|
||||
class AssetSource {
|
||||
public:
|
||||
virtual ~AssetSource() = default;
|
||||
|
||||
// An asset source can have arbitrary metadata attached.
|
||||
virtual JsonObject metadata() const = 0;
|
||||
|
||||
// Should return all the available assets in this source
|
||||
virtual StringList assetPaths() const = 0;
|
||||
|
||||
// Open the given path in this source and return an IODevicePtr to it.
|
||||
virtual IODevicePtr open(String const& path) = 0;
|
||||
|
||||
// Read the entirety of the given path into a buffer.
|
||||
virtual ByteArray read(String const& path) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1113
source/base/StarAssets.cpp
Normal file
1113
source/base/StarAssets.cpp
Normal file
File diff suppressed because it is too large
Load diff
380
source/base/StarAssets.hpp
Normal file
380
source/base/StarAssets.hpp
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
#ifndef STAR_ASSETS_HPP
|
||||
#define STAR_ASSETS_HPP
|
||||
|
||||
#include "StarJson.hpp"
|
||||
#include "StarOrderedMap.hpp"
|
||||
#include "StarRect.hpp"
|
||||
#include "StarBiMap.hpp"
|
||||
#include "StarThread.hpp"
|
||||
#include "StarAssetSource.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(Font);
|
||||
STAR_CLASS(Audio);
|
||||
STAR_CLASS(Image);
|
||||
STAR_STRUCT(FramesSpecification);
|
||||
STAR_CLASS(Assets);
|
||||
|
||||
STAR_EXCEPTION(AssetException, StarException);
|
||||
|
||||
// Asset paths are not filesystem paths. '/' is always the directory separator,
|
||||
// and it is not possible to escape any asset source directory. '\' is never a
|
||||
// valid directory separator. All asset paths are considered case-insensitive.
|
||||
//
|
||||
// In addition to the path portion of the asset path, some asset types may also
|
||||
// have a sub-path, which is always separated from the path portion of the asset
|
||||
// by ':'. There can be at most 1 sub-path component.
|
||||
//
|
||||
// Image paths may also have a directives portion of the full asset path, which
|
||||
// must come after the path and optional sub-path comopnent. The directives
|
||||
// portion of the path starts with a '?', and '?' separates each subsquent
|
||||
// directive.
|
||||
struct AssetPath {
|
||||
static AssetPath split(String const& path);
|
||||
static String join(AssetPath const& path);
|
||||
|
||||
// Get / modify sub-path directly on a joined path string
|
||||
static String setSubPath(String const& joinedPath, String const& subPath);
|
||||
static String removeSubPath(String const& joinedPath);
|
||||
|
||||
// Get / modify directives directly on a joined path string
|
||||
static String getDirectives(String const& joinedPath);
|
||||
static String addDirectives(String const& joinedPath, String const& directives);
|
||||
static String removeDirectives(String const& joinedPath);
|
||||
|
||||
// The base directory name for any given path, including the trailing '/'.
|
||||
// Ignores sub-path and directives.
|
||||
static String directory(String const& path);
|
||||
|
||||
// The file part of any given path, ignoring sub-path and directives. Path
|
||||
// must be a file not a directory.
|
||||
static String filename(String const& path);
|
||||
|
||||
// The file extension of a given file path, ignoring directives and
|
||||
// sub-paths.
|
||||
static String extension(String const& path);
|
||||
|
||||
// Computes an absolute asset path from a relative path relative to another
|
||||
// asset. The sourcePath must be an absolute path (may point to a directory
|
||||
// or an asset in a directory, and ignores ':' sub-path or ? directives),
|
||||
// and the givenPath may be either an absolute *or* a relative path. If it
|
||||
// is an absolute path, it is returned unchanged. If it is a relative path,
|
||||
// then it is computed as relative to the directory component of the
|
||||
// sourcePath.
|
||||
static String relativeTo(String const& sourcePath, String const& givenPath);
|
||||
|
||||
String basePath;
|
||||
Maybe<String> subPath;
|
||||
StringList directives;
|
||||
|
||||
bool operator==(AssetPath const& rhs) const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, AssetPath const& rhs);
|
||||
|
||||
// The contents of an assets .frames file, which can be associated with one or
|
||||
// more images, and specifies named sub-rects of those images.
|
||||
struct FramesSpecification {
|
||||
// Get the target sub-rect of a given frame name (which can be an alias).
|
||||
// Returns nothing if the frame name is not found.
|
||||
Maybe<RectU> getRect(String const& frame) const;
|
||||
|
||||
// The full path to the .frames file from which this was loaded.
|
||||
String framesFile;
|
||||
// Named sub-frames
|
||||
StringMap<RectU> frames;
|
||||
// Aliases for named sub-frames, always points to a valid frame name in the
|
||||
// 'frames' map.
|
||||
StringMap<String> aliases;
|
||||
};
|
||||
|
||||
// The assets system can load image, font, json, and data assets from a set of
|
||||
// sources. Each source is either a directory on the filesystem or a single
|
||||
// packed asset file.
|
||||
//
|
||||
// Assets is thread safe and performs TTL caching.
|
||||
class Assets {
|
||||
public:
|
||||
struct Settings {
|
||||
// TTL for cached assets
|
||||
float assetTimeToLive;
|
||||
|
||||
// Audio under this length will be automatically decompressed
|
||||
float audioDecompressLimit;
|
||||
|
||||
// Number of background worker threads
|
||||
unsigned workerPoolSize;
|
||||
|
||||
// If given, if an image is unable to load, will log the error and load
|
||||
// this path instead
|
||||
Maybe<String> missingImage;
|
||||
|
||||
// Same, but for audio
|
||||
Maybe<String> missingAudio;
|
||||
|
||||
// When loading assets from a directory, will automatically ignore any
|
||||
// files whose asset paths matching any of the given patterns.
|
||||
StringList pathIgnore;
|
||||
|
||||
// Same, but only ignores the file for the purposes of calculating the
|
||||
// digest.
|
||||
StringList digestIgnore;
|
||||
};
|
||||
|
||||
Assets(Settings settings, StringList assetSources);
|
||||
~Assets();
|
||||
|
||||
// Returns a list of all the asset source paths used by Assets in load order.
|
||||
StringList assetSources() const;
|
||||
|
||||
// Return metadata for the given loaded asset source path
|
||||
JsonObject assetSourceMetadata(String const& sourcePath) const;
|
||||
|
||||
// An imperfect sha256 digest of the contents of all combined asset sources.
|
||||
// Useful for detecting if there are mismatched assets between a client and
|
||||
// server or if assets sources have changed from a previous load.
|
||||
ByteArray digest() const;
|
||||
|
||||
// Is there an asset associated with the given path? Path must not contain
|
||||
// sub-paths or directives.
|
||||
bool assetExists(String const& path) const;
|
||||
|
||||
// The name of the asset source within which the path exists.
|
||||
String assetSource(String const& path) const;
|
||||
|
||||
// Scans for all assets with the given suffix in any directory.
|
||||
StringList scan(String const& suffix) const;
|
||||
// Scans for all assets matching both prefix and suffix (prefix may be, for
|
||||
// example, a directory)
|
||||
StringList scan(String const& prefix, String const& suffix) const;
|
||||
// Scans all assets for files with the given extension, which is specially
|
||||
// indexed and much faster than a normal scan. Extension may contain leading
|
||||
// '.' character or it may be omitted.
|
||||
StringList scanExtension(String const& extension) const;
|
||||
|
||||
// Get json asset with an optional sub-path. The sub-path portion of the
|
||||
// path refers to a key in the top-level object, and may use dot notation
|
||||
// for deeper field access and [] notation for array access. Example:
|
||||
// "/path/to/json:key1.key2.key3[4]".
|
||||
Json json(String const& path) const;
|
||||
|
||||
// Either returns the json v, or, if v is a string type, returns the json
|
||||
// pointed to by interpreting v as a string path.
|
||||
Json fetchJson(Json const& v, String const& dir = "/") const;
|
||||
|
||||
// Load all the given jsons using background processing.
|
||||
void queueJsons(StringList const& paths) const;
|
||||
|
||||
// Returns *either* an image asset or a sub-frame. Frame files are JSON
|
||||
// descriptor files that reference a particular image and label separate
|
||||
// sub-rects of the image. If the given path has a ':' sub-path, then the
|
||||
// assets system will look for an associated .frames named either
|
||||
// <full-path-minus-extension>.frames or default.frames, going up to assets
|
||||
// root. May return the same ImageConstPtr for different paths if the paths
|
||||
// are equivalent or they are aliases of other image paths.
|
||||
ImageConstPtr image(String const& path) const;
|
||||
// Load images using background processing
|
||||
void queueImages(StringList const& paths) const;
|
||||
// Return the given image *if* it is already loaded, otherwise queue it for
|
||||
// loading.
|
||||
ImageConstPtr tryImage(String const& path) const;
|
||||
|
||||
// Returns the best associated FramesSpecification for a given image path, if
|
||||
// it exists. The given path must not contain sub-paths or directives, and
|
||||
// this function may return nullptr if no frames file is associated with the
|
||||
// given image path.
|
||||
FramesSpecificationConstPtr imageFrames(String const& path) const;
|
||||
|
||||
// Returns a pointer to a shared audio asset;
|
||||
AudioConstPtr audio(String const& path) const;
|
||||
// Load audios using background processing
|
||||
void queueAudios(StringList const& paths) const;
|
||||
// Return the given audio *if* it is already loaded, otherwise queue it for
|
||||
// loading.
|
||||
AudioConstPtr tryAudio(String const& path) const;
|
||||
|
||||
// Returns pointer to shared font asset
|
||||
FontConstPtr font(String const& path) const;
|
||||
|
||||
// Returns a bytes asset (Reads asset as an opaque binary blob)
|
||||
ByteArrayConstPtr bytes(String const& path) const;
|
||||
|
||||
// Bypass asset caching and open an asset file directly.
|
||||
IODevicePtr openFile(String const& basePath) const;
|
||||
|
||||
// Clear all cached assets that are not queued, persistent, or broken.
|
||||
void clearCache();
|
||||
|
||||
// Run a cleanup pass and remove any assets past their time to live.
|
||||
void cleanup();
|
||||
|
||||
private:
|
||||
enum class AssetType {
|
||||
Json,
|
||||
Image,
|
||||
Audio,
|
||||
Font,
|
||||
Bytes
|
||||
};
|
||||
|
||||
enum class QueuePriority {
|
||||
None,
|
||||
Working,
|
||||
PostProcess,
|
||||
Load
|
||||
};
|
||||
|
||||
struct AssetId {
|
||||
AssetType type;
|
||||
AssetPath path;
|
||||
|
||||
bool operator==(AssetId const& assetId) const;
|
||||
};
|
||||
|
||||
struct AssetIdHash {
|
||||
size_t operator()(AssetId const& id) const;
|
||||
};
|
||||
|
||||
struct AssetData {
|
||||
virtual ~AssetData() = default;
|
||||
|
||||
// Should return true if this asset is shared and still in use, so freeing
|
||||
// it from cache will not really free the resource, so it should persist in
|
||||
// the cache.
|
||||
virtual bool shouldPersist() const = 0;
|
||||
|
||||
double time = 0.0;
|
||||
bool needsPostProcessing = false;
|
||||
};
|
||||
|
||||
struct JsonData : AssetData {
|
||||
bool shouldPersist() const override;
|
||||
|
||||
Json json;
|
||||
};
|
||||
|
||||
// Image data for an image, sub-frame, or post-processed image.
|
||||
struct ImageData : AssetData {
|
||||
bool shouldPersist() const override;
|
||||
|
||||
ImageConstPtr image;
|
||||
|
||||
// *Optional* sub-frames data for this image, only will exist when the
|
||||
// image is a top-level image and has an associated frames file.
|
||||
FramesSpecificationConstPtr frames;
|
||||
|
||||
// If this image aliases another asset entry, this will be true and
|
||||
// shouldPersist will never be true (to ensure that this alias and its
|
||||
// target can be removed from the cache).
|
||||
bool alias = false;
|
||||
};
|
||||
|
||||
struct AudioData : AssetData {
|
||||
bool shouldPersist() const override;
|
||||
|
||||
AudioConstPtr audio;
|
||||
};
|
||||
|
||||
struct FontData : AssetData {
|
||||
bool shouldPersist() const override;
|
||||
|
||||
FontConstPtr font;
|
||||
};
|
||||
|
||||
struct BytesData : AssetData {
|
||||
bool shouldPersist() const override;
|
||||
|
||||
ByteArrayConstPtr bytes;
|
||||
};
|
||||
|
||||
struct AssetFileDescriptor {
|
||||
// The mixed case original source name;
|
||||
String sourceName;
|
||||
// The source that has the primary asset copy
|
||||
AssetSourcePtr source;
|
||||
// List of source names and sources for patches to this file.
|
||||
List<pair<String, AssetSourcePtr>> patchSources;
|
||||
};
|
||||
|
||||
static FramesSpecification parseFramesSpecification(Json const& frameConfig, String path);
|
||||
|
||||
void queueAssets(List<AssetId> const& assetIds) const;
|
||||
shared_ptr<AssetData> tryAsset(AssetId const& id) const;
|
||||
shared_ptr<AssetData> getAsset(AssetId const& id) const;
|
||||
|
||||
void workerMain();
|
||||
|
||||
// All methods below assume that the asset mutex is locked when calling.
|
||||
|
||||
// Do some processing that might take a long time and should not hold the
|
||||
// assets mutex during it. Unlocks the assets mutex while the function is in
|
||||
// progress and re-locks it on return or before exception is thrown.
|
||||
template <typename Function>
|
||||
decltype(auto) unlockDuring(Function f) const;
|
||||
|
||||
// Returns the best frames specification for the given image path, if it exists.
|
||||
FramesSpecificationConstPtr bestFramesSpecification(String const& basePath) const;
|
||||
|
||||
IODevicePtr open(String const& basePath) const;
|
||||
ByteArray read(String const& basePath) const;
|
||||
|
||||
Json readJson(String const& basePath) const;
|
||||
|
||||
// Load / post process an asset and log any exception. Returns true if the
|
||||
// work was performed (whether successful or not), false if the work is
|
||||
// blocking on something.
|
||||
bool doLoad(AssetId const& id) const;
|
||||
bool doPost(AssetId const& id) const;
|
||||
|
||||
// Assets can recursively depend on other assets, so the main entry point for
|
||||
// loading assets is in this separate method, and is safe for other loading
|
||||
// methods to call recursively. If there is an error loading the asset, this
|
||||
// method will throw. If, and only if, the asset is blocking on another busy
|
||||
// asset, this method will return null.
|
||||
shared_ptr<AssetData> loadAsset(AssetId const& id) const;
|
||||
|
||||
shared_ptr<AssetData> loadJson(AssetPath const& path) const;
|
||||
shared_ptr<AssetData> loadImage(AssetPath const& path) const;
|
||||
shared_ptr<AssetData> loadAudio(AssetPath const& path) const;
|
||||
shared_ptr<AssetData> loadFont(AssetPath const& path) const;
|
||||
shared_ptr<AssetData> loadBytes(AssetPath const& path) const;
|
||||
|
||||
shared_ptr<AssetData> postProcessAudio(shared_ptr<AssetData> const& original) const;
|
||||
|
||||
// Updates time on the given asset (with smearing).
|
||||
void freshen(shared_ptr<AssetData> const& asset) const;
|
||||
|
||||
Settings m_settings;
|
||||
|
||||
mutable Mutex m_assetsMutex;
|
||||
|
||||
mutable ConditionVariable m_assetsQueued;
|
||||
mutable OrderedHashMap<AssetId, QueuePriority, AssetIdHash> m_queue;
|
||||
|
||||
mutable ConditionVariable m_assetsDone;
|
||||
mutable HashMap<AssetId, shared_ptr<AssetData>, AssetIdHash> m_assetsCache;
|
||||
|
||||
mutable StringMap<String> m_bestFramesFiles;
|
||||
mutable StringMap<FramesSpecificationConstPtr> m_framesSpecifications;
|
||||
|
||||
// Paths of all used asset sources, in load order.
|
||||
StringList m_assetSources;
|
||||
|
||||
// Maps an asset path to the loaded asset source and vice versa
|
||||
BiMap<String, AssetSourcePtr> m_assetSourcePaths;
|
||||
|
||||
// Maps the source asset name to the source containing it
|
||||
CaseInsensitiveStringMap<AssetFileDescriptor> m_files;
|
||||
// Maps an extension to the files with that extension
|
||||
CaseInsensitiveStringMap<StringList> m_filesByExtension;
|
||||
|
||||
ByteArray m_digest;
|
||||
|
||||
List<ThreadFunction<void>> m_workerThreads;
|
||||
atomic<bool> m_stopThreads;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
108
source/base/StarBlocksAlongLine.hpp
Normal file
108
source/base/StarBlocksAlongLine.hpp
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#ifndef STAR_BLOCKS_ALONG_LINE_HPP
|
||||
#define STAR_BLOCKS_ALONG_LINE_HPP
|
||||
|
||||
#include "StarVector.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// Iterate over integral cells based on Bresenham's line drawing algorithm.
|
||||
// Returns false immediately when the callback returns false for any cell,
|
||||
// returns true after iterating through every cell otherwise.
|
||||
template <typename Scalar>
|
||||
bool forBlocksAlongLine(Vector<Scalar, 2> origin, Vector<Scalar, 2> const& dxdy, function<bool(int, int)> callback) {
|
||||
Vector<Scalar, 2> remote = origin + dxdy;
|
||||
|
||||
double dx = dxdy[0];
|
||||
if (dx < 0)
|
||||
dx *= -1;
|
||||
|
||||
double dy = dxdy[1];
|
||||
if (dy < 0)
|
||||
dy *= -1;
|
||||
|
||||
double oxfloor = floor(origin[0]);
|
||||
double oyfloor = floor(origin[1]);
|
||||
double rxfloor = floor(remote[0]);
|
||||
double ryfloor = floor(remote[1]);
|
||||
|
||||
if (dx == 0) {
|
||||
if (oyfloor < ryfloor) {
|
||||
for (int i = oyfloor; i <= ryfloor; ++i) {
|
||||
if (!callback(oxfloor, i))
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (int i = oyfloor; i >= ryfloor; --i) {
|
||||
if (!callback(oxfloor, i))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
} else if (dy == 0) {
|
||||
if (oxfloor < rxfloor) {
|
||||
for (int i = oxfloor; i <= rxfloor; ++i) {
|
||||
if (!callback(i, oyfloor))
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (int i = oxfloor; i >= rxfloor; --i) {
|
||||
if (!callback(i, oyfloor))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
} else {
|
||||
int x = oxfloor;
|
||||
int y = oyfloor;
|
||||
|
||||
int n = 1;
|
||||
int x_inc, y_inc;
|
||||
double error;
|
||||
|
||||
if (dxdy[0] > 0) {
|
||||
x_inc = 1;
|
||||
n += int(rxfloor) - x;
|
||||
error = (oxfloor + 1 - origin[0]) * dy;
|
||||
} else {
|
||||
x_inc = -1;
|
||||
n += x - int(rxfloor);
|
||||
error = (origin[0] - oxfloor) * dy;
|
||||
}
|
||||
|
||||
if (dxdy[1] > 0) {
|
||||
y_inc = 1;
|
||||
n += int(ryfloor) - y;
|
||||
error -= (oyfloor + 1 - origin[1]) * dx;
|
||||
} else {
|
||||
y_inc = -1;
|
||||
n += y - int(ryfloor);
|
||||
error -= (origin[1] - oyfloor) * dx;
|
||||
}
|
||||
|
||||
for (; n > 0; --n) {
|
||||
if (!callback(x, y))
|
||||
return false;
|
||||
|
||||
if (error > 0) {
|
||||
y += y_inc;
|
||||
error -= dx;
|
||||
} else if (error < 0) {
|
||||
x += x_inc;
|
||||
error += dy;
|
||||
} else {
|
||||
--n;
|
||||
y += y_inc;
|
||||
x += x_inc;
|
||||
error += dy;
|
||||
error -= dx;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
590
source/base/StarCellularLightArray.hpp
Normal file
590
source/base/StarCellularLightArray.hpp
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
#ifndef STAR_CELLULAR_LIGHT_ARRAY_HPP
|
||||
#define STAR_CELLULAR_LIGHT_ARRAY_HPP
|
||||
|
||||
#include "StarList.hpp"
|
||||
#include "StarVector.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// Operations for simple scalar lighting.
|
||||
struct ScalarLightTraits {
|
||||
typedef float Value;
|
||||
|
||||
static float spread(float source, float dest, float drop);
|
||||
static float subtract(float value, float drop);
|
||||
|
||||
static float maxIntensity(float value);
|
||||
static float minIntensity(float value);
|
||||
|
||||
static float max(float v1, float v2);
|
||||
};
|
||||
|
||||
// Operations for 3 component (colored) lighting. Spread and subtract are
|
||||
// applied proportionally, so that color ratios stay the same, to prevent hues
|
||||
// changing as light spreads.
|
||||
struct ColoredLightTraits {
|
||||
typedef Vec3F Value;
|
||||
|
||||
static Vec3F spread(Vec3F const& source, Vec3F const& dest, float drop);
|
||||
static Vec3F subtract(Vec3F value, float drop);
|
||||
|
||||
static float maxIntensity(Vec3F const& value);
|
||||
static float minIntensity(Vec3F const& value);
|
||||
|
||||
static Vec3F max(Vec3F const& v1, Vec3F const& v2);
|
||||
};
|
||||
|
||||
template <typename LightTraits>
|
||||
class CellularLightArray {
|
||||
public:
|
||||
typedef typename LightTraits::Value LightValue;
|
||||
|
||||
struct Cell {
|
||||
LightValue light;
|
||||
bool obstacle;
|
||||
};
|
||||
|
||||
struct SpreadLight {
|
||||
Vec2F position;
|
||||
LightValue value;
|
||||
};
|
||||
|
||||
struct PointLight {
|
||||
Vec2F position;
|
||||
LightValue value;
|
||||
float beam;
|
||||
float beamAngle;
|
||||
float beamAmbience;
|
||||
};
|
||||
|
||||
void setParameters(unsigned spreadPasses, float spreadMaxAir, float spreadMaxObstacle,
|
||||
float pointMaxAir, float pointMaxObstacle, float pointObstacleBoost);
|
||||
|
||||
// The border around the target lighting array where initial lighting / light
|
||||
// source data is required. Based on parameters.
|
||||
size_t borderCells() const;
|
||||
|
||||
// Begin a new calculation, setting internal storage to new width and height
|
||||
// (if these are the same as last time this is cheap). Always clears all
|
||||
// existing light and collision data.
|
||||
void begin(size_t newWidth, size_t newHeight);
|
||||
|
||||
// Position is in index space, spread lights will have no effect if they are
|
||||
// outside of the array. Integer points are assumed to be on the corners of
|
||||
// the grid (not the center)
|
||||
void addSpreadLight(SpreadLight const& spreadLight);
|
||||
void addPointLight(PointLight const& pointLight);
|
||||
|
||||
// Directly set the lighting values for this position.
|
||||
void setLight(size_t x, size_t y, LightValue const& light);
|
||||
|
||||
// Get current light value. Call after calling calculate() to pull final
|
||||
// data out.
|
||||
LightValue getLight(size_t x, size_t y) const;
|
||||
|
||||
// Set obstacle values for this position
|
||||
void setObstacle(size_t x, size_t y, bool obstacle);
|
||||
bool getObstacle(size_t x, size_t y) const;
|
||||
|
||||
Cell const& cell(size_t x, size_t y) const;
|
||||
Cell& cell(size_t x, size_t y);
|
||||
|
||||
Cell const& cellAtIndex(size_t index) const;
|
||||
Cell& cellAtIndex(size_t index);
|
||||
|
||||
// Calculate lighting in the given sub-rect, in order to properly do spread
|
||||
// lighting, and initial lighting must be given for the ambient border this
|
||||
// given rect, and the array size must be at least that large. xMax / yMax
|
||||
// are not inclusive, the range is [xMin, xMax) and [yMin, yMax).
|
||||
void calculate(size_t xMin, size_t yMin, size_t xMax, size_t yMax);
|
||||
|
||||
private:
|
||||
// Set 4 points based on interpolated light position and free space
|
||||
// attenuation.
|
||||
void setSpreadLightingPoints();
|
||||
|
||||
// Spreads light out in an octagonal based cellular automata
|
||||
void calculateLightSpread(size_t xmin, size_t ymin, size_t xmax, size_t ymax);
|
||||
|
||||
// Loops through each light and adds light strength based on distance and
|
||||
// obstacle attenuation. Calculates within the given sub-rect
|
||||
void calculatePointLighting(size_t xmin, size_t ymin, size_t xmax, size_t ymax);
|
||||
|
||||
// Run Xiaolin Wu's anti-aliased line drawing algorithm from start to end,
|
||||
// summing each block that would be drawn to to produce an attenuation. Not
|
||||
// circularized.
|
||||
float lineAttenuation(Vec2F const& start, Vec2F const& end, float perObstacleAttenuation, float maxAttenuation);
|
||||
|
||||
size_t m_width;
|
||||
size_t m_height;
|
||||
unique_ptr<Cell[]> m_cells;
|
||||
List<SpreadLight> m_spreadLights;
|
||||
List<PointLight> m_pointLights;
|
||||
|
||||
unsigned m_spreadPasses;
|
||||
float m_spreadMaxAir;
|
||||
float m_spreadMaxObstacle;
|
||||
float m_pointMaxAir;
|
||||
float m_pointMaxObstacle;
|
||||
float m_pointObstacleBoost;
|
||||
};
|
||||
|
||||
typedef CellularLightArray<ColoredLightTraits> ColoredCellularLightArray;
|
||||
typedef CellularLightArray<ScalarLightTraits> ScalarCellularLightArray;
|
||||
|
||||
inline float ScalarLightTraits::spread(float source, float dest, float drop) {
|
||||
return std::max(source - drop, dest);
|
||||
}
|
||||
|
||||
inline float ScalarLightTraits::subtract(float c, float drop) {
|
||||
return std::max(c - drop, 0.0f);
|
||||
}
|
||||
|
||||
inline float ScalarLightTraits::maxIntensity(float value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
inline float ScalarLightTraits::minIntensity(float value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
inline float ScalarLightTraits::max(float v1, float v2) {
|
||||
return std::max(v1, v2);
|
||||
}
|
||||
|
||||
inline Vec3F ColoredLightTraits::spread(Vec3F const& source, Vec3F const& dest, float drop) {
|
||||
float maxChannel = std::max(source[0], std::max(source[1], source[2]));
|
||||
if (maxChannel <= 0.0f)
|
||||
return dest;
|
||||
|
||||
drop /= maxChannel;
|
||||
return Vec3F(
|
||||
std::max(source[0] - source[0] * drop, dest[0]),
|
||||
std::max(source[1] - source[1] * drop, dest[1]),
|
||||
std::max(source[2] - source[2] * drop, dest[2])
|
||||
);
|
||||
}
|
||||
|
||||
inline Vec3F ColoredLightTraits::subtract(Vec3F c, float drop) {
|
||||
float max = std::max(std::max(c[0], c[1]), c[2]);
|
||||
if (max <= 0.0f)
|
||||
return c;
|
||||
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
float pdrop = (drop * c[i]) / max;
|
||||
if (c[i] > pdrop)
|
||||
c[i] -= pdrop;
|
||||
else
|
||||
c[i] = 0;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
inline float ColoredLightTraits::maxIntensity(Vec3F const& value) {
|
||||
return value.max();
|
||||
}
|
||||
|
||||
inline float ColoredLightTraits::minIntensity(Vec3F const& value) {
|
||||
return value.min();
|
||||
}
|
||||
|
||||
inline Vec3F ColoredLightTraits::max(Vec3F const& v1, Vec3F const& v2) {
|
||||
return vmax(v1, v2);
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
void CellularLightArray<LightTraits>::setParameters(unsigned spreadPasses, float spreadMaxAir, float spreadMaxObstacle,
|
||||
float pointMaxAir, float pointMaxObstacle, float pointObstacleBoost) {
|
||||
m_spreadPasses = spreadPasses;
|
||||
m_spreadMaxAir = spreadMaxAir;
|
||||
m_spreadMaxObstacle = spreadMaxObstacle;
|
||||
m_pointMaxAir = pointMaxAir;
|
||||
m_pointMaxObstacle = pointMaxObstacle;
|
||||
m_pointObstacleBoost = pointObstacleBoost;
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
size_t CellularLightArray<LightTraits>::borderCells() const {
|
||||
return (size_t)ceil(max(0.0f, max(m_spreadMaxAir, m_pointMaxAir)));
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
void CellularLightArray<LightTraits>::begin(size_t newWidth, size_t newHeight) {
|
||||
m_spreadLights.clear();
|
||||
m_pointLights.clear();
|
||||
starAssert(newWidth > 0 && newHeight > 0);
|
||||
|
||||
if (!m_cells || newWidth != m_width || newHeight != m_height) {
|
||||
m_width = newWidth;
|
||||
m_height = newHeight;
|
||||
|
||||
m_cells.reset(new Cell[m_width * m_height]());
|
||||
|
||||
} else {
|
||||
std::fill(m_cells.get(), m_cells.get() + m_width * m_height, Cell{LightValue{}, false});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
void CellularLightArray<LightTraits>::addSpreadLight(SpreadLight const& spreadLight) {
|
||||
m_spreadLights.append(spreadLight);
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
void CellularLightArray<LightTraits>::addPointLight(PointLight const& pointLight) {
|
||||
m_pointLights.append(pointLight);
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
void CellularLightArray<LightTraits>::setLight(size_t x, size_t y, LightValue const& lightValue) {
|
||||
cell(x, y).light = lightValue;
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
void CellularLightArray<LightTraits>::setObstacle(size_t x, size_t y, bool obstacle) {
|
||||
cell(x, y).obstacle = obstacle;
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
auto CellularLightArray<LightTraits>::getLight(size_t x, size_t y) const -> LightValue {
|
||||
return cell(x, y).light;
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
bool CellularLightArray<LightTraits>::getObstacle(size_t x, size_t y) const {
|
||||
return cell(x, y).obstacle;
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
auto CellularLightArray<LightTraits>::cell(size_t x, size_t y) const -> Cell const & {
|
||||
starAssert(x < m_width && y < m_height);
|
||||
return m_cells[x * m_height + y];
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
auto CellularLightArray<LightTraits>::cell(size_t x, size_t y) -> Cell & {
|
||||
starAssert(x < m_width && y < m_height);
|
||||
return m_cells[x * m_height + y];
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
auto CellularLightArray<LightTraits>::cellAtIndex(size_t index) const -> Cell const & {
|
||||
starAssert(index < m_width * m_height);
|
||||
return m_cells[index];
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
auto CellularLightArray<LightTraits>::cellAtIndex(size_t index) -> Cell & {
|
||||
starAssert(index < m_width * m_height);
|
||||
return m_cells[index];
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
void CellularLightArray<LightTraits>::calculate(size_t xMin, size_t yMin, size_t xMax, size_t yMax) {
|
||||
setSpreadLightingPoints();
|
||||
calculateLightSpread(xMin, yMin, xMax, yMax);
|
||||
calculatePointLighting(xMin, yMin, xMax, yMax);
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
void CellularLightArray<LightTraits>::setSpreadLightingPoints() {
|
||||
for (SpreadLight const& light : m_spreadLights) {
|
||||
// - 0.5f to correct for lights being on the grid corners and not center
|
||||
int minX = floor(light.position[0] - 0.5f);
|
||||
int minY = floor(light.position[1] - 0.5f);
|
||||
int maxX = minX + 1;
|
||||
int maxY = minY + 1;
|
||||
|
||||
float xdist = light.position[0] - minX - 0.5f;
|
||||
float ydist = light.position[1] - minY - 0.5f;
|
||||
|
||||
// Pick falloff here based on closest block obstacle value (probably not
|
||||
// best)
|
||||
Vec2I pos(light.position.floor());
|
||||
float oneBlockAtt;
|
||||
if (pos[0] >= 0 && pos[0] < (int)m_width && pos[1] >= 0 && pos[1] < (int)m_height && getObstacle(pos[0], pos[1]))
|
||||
oneBlockAtt = 1.0f / m_spreadMaxObstacle;
|
||||
else
|
||||
oneBlockAtt = 1.0f / m_spreadMaxAir;
|
||||
|
||||
// "pre fall-off" a 2x2 area of blocks to smooth out floating point
|
||||
// positions using the cellular algorithm
|
||||
|
||||
if (minX >= 0 && minX < (int)m_width && minY >= 0 && minY < (int)m_height)
|
||||
setLight(minX, minY, LightTraits::max(getLight(minX, minY), LightTraits::subtract(light.value, oneBlockAtt * (2.0f - (1.0f - xdist) - (1.0f - ydist)))));
|
||||
|
||||
if (minX >= 0 && minX < (int)m_width && maxY >= 0 && maxY < (int)m_height)
|
||||
setLight(minX, maxY, LightTraits::max(getLight(minX, maxY), LightTraits::subtract(light.value, oneBlockAtt * (2.0f - (1.0f - xdist) - (ydist)))));
|
||||
|
||||
if (maxX >= 0 && maxX < (int)m_width && minY >= 0 && minY < (int)m_height)
|
||||
setLight(maxX, minY, LightTraits::max(getLight(maxX, minY), LightTraits::subtract(light.value, oneBlockAtt * (2.0f - (xdist) - (1.0f - ydist)))));
|
||||
|
||||
if (maxX >= 0 && maxX < (int)m_width && maxY >= 0 && maxY < (int)m_height)
|
||||
setLight(maxX, maxY, LightTraits::max(getLight(maxX, maxY), LightTraits::subtract(light.value, oneBlockAtt * (2.0f - (xdist) - (ydist)))));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
void CellularLightArray<LightTraits>::calculateLightSpread(size_t xMin, size_t yMin, size_t xMax, size_t yMax) {
|
||||
starAssert(m_width > 0 && m_height > 0);
|
||||
|
||||
float dropoffAir = 1.0f / m_spreadMaxAir;
|
||||
float dropoffObstacle = 1.0f / m_spreadMaxObstacle;
|
||||
float dropoffAirDiag = 1.0f / m_spreadMaxAir * Constants::sqrt2;
|
||||
float dropoffObstacleDiag = 1.0f / m_spreadMaxObstacle * Constants::sqrt2;
|
||||
|
||||
// enlarge x/y min/max taking into ambient spread of light
|
||||
xMin = xMin - min(xMin, (size_t)ceil(m_spreadMaxAir));
|
||||
yMin = yMin - min(yMin, (size_t)ceil(m_spreadMaxAir));
|
||||
xMax = min(m_width, xMax + (size_t)ceil(m_spreadMaxAir));
|
||||
yMax = min(m_height, yMax + (size_t)ceil(m_spreadMaxAir));
|
||||
|
||||
for (unsigned p = 0; p < m_spreadPasses; ++p) {
|
||||
// Spread right and up and diag up right / diag down right
|
||||
for (size_t x = xMin + 1; x < xMax - 1; ++x) {
|
||||
size_t xCellOffset = x * m_height;
|
||||
size_t xRightCellOffset = (x + 1) * m_height;
|
||||
|
||||
for (size_t y = yMin + 1; y < yMax - 1; ++y) {
|
||||
auto cell = cellAtIndex(xCellOffset + y);
|
||||
auto& cellRight = cellAtIndex(xRightCellOffset + y);
|
||||
auto& cellUp = cellAtIndex(xCellOffset + y + 1);
|
||||
auto& cellRightUp = cellAtIndex(xRightCellOffset + y + 1);
|
||||
auto& cellRightDown = cellAtIndex(xRightCellOffset + y - 1);
|
||||
|
||||
float straightDropoff = cell.obstacle ? dropoffObstacle : dropoffAir;
|
||||
float diagDropoff = cell.obstacle ? dropoffObstacleDiag : dropoffAirDiag;
|
||||
|
||||
cellRight.light = LightTraits::spread(cell.light, cellRight.light, straightDropoff);
|
||||
cellUp.light = LightTraits::spread(cell.light, cellUp.light, straightDropoff);
|
||||
|
||||
cellRightUp.light = LightTraits::spread(cell.light, cellRightUp.light, diagDropoff);
|
||||
cellRightDown.light = LightTraits::spread(cell.light, cellRightDown.light, diagDropoff);
|
||||
}
|
||||
}
|
||||
|
||||
// Spread left and down and diag up left / diag down left
|
||||
for (size_t x = xMax - 2; x > xMin; --x) {
|
||||
size_t xCellOffset = x * m_height;
|
||||
size_t xLeftCellOffset = (x - 1) * m_height;
|
||||
|
||||
for (size_t y = yMax - 2; y > yMin; --y) {
|
||||
auto cell = cellAtIndex(xCellOffset + y);
|
||||
auto& cellLeft = cellAtIndex(xLeftCellOffset + y);
|
||||
auto& cellDown = cellAtIndex(xCellOffset + y - 1);
|
||||
auto& cellLeftUp = cellAtIndex(xLeftCellOffset + y + 1);
|
||||
auto& cellLeftDown = cellAtIndex(xLeftCellOffset + y - 1);
|
||||
|
||||
float straightDropoff = cell.obstacle ? dropoffObstacle : dropoffAir;
|
||||
float diagDropoff = cell.obstacle ? dropoffObstacleDiag : dropoffAirDiag;
|
||||
|
||||
cellLeft.light = LightTraits::spread(cell.light, cellLeft.light, straightDropoff);
|
||||
cellDown.light = LightTraits::spread(cell.light, cellDown.light, straightDropoff);
|
||||
|
||||
cellLeftUp.light = LightTraits::spread(cell.light, cellLeftUp.light, diagDropoff);
|
||||
cellLeftDown.light = LightTraits::spread(cell.light, cellLeftDown.light, diagDropoff);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
void CellularLightArray<LightTraits>::calculatePointLighting(size_t xmin, size_t ymin, size_t xmax, size_t ymax) {
|
||||
float perBlockObstacleAttenuation = 1.0f / m_pointMaxObstacle;
|
||||
float perBlockAirAttenuation = 1.0f / m_pointMaxAir;
|
||||
|
||||
for (PointLight light : m_pointLights) {
|
||||
if (light.position[0] < 0 || light.position[0] > m_width - 1 || light.position[1] < 0 || light.position[1] > m_height - 1)
|
||||
continue;
|
||||
|
||||
float maxIntensity = LightTraits::maxIntensity(light.value);
|
||||
Vec2F beamDirection = Vec2F(1, 0).rotate(light.beamAngle);
|
||||
|
||||
float maxRange = maxIntensity * m_pointMaxAir;
|
||||
// The min / max considering the radius of the light
|
||||
size_t lxmin = std::floor(std::max<float>(xmin, light.position[0] - maxRange));
|
||||
size_t lymin = std::floor(std::max<float>(ymin, light.position[1] - maxRange));
|
||||
size_t lxmax = std::ceil(std::min<float>(xmax, light.position[0] + maxRange));
|
||||
size_t lymax = std::ceil(std::min<float>(ymax, light.position[1] + maxRange));
|
||||
|
||||
for (size_t x = lxmin; x < lxmax; ++x) {
|
||||
for (size_t y = lymin; y < lymax; ++y) {
|
||||
LightValue lvalue = getLight(x, y);
|
||||
// + 0.5f to correct block position to center
|
||||
Vec2F blockPos = Vec2F(x + 0.5f, y + 0.5f);
|
||||
|
||||
Vec2F relativeLightPosition = blockPos - light.position;
|
||||
float distance = relativeLightPosition.magnitude();
|
||||
if (distance == 0.0f) {
|
||||
setLight(x, y, LightTraits::max(light.value, lvalue));
|
||||
continue;
|
||||
}
|
||||
|
||||
float attenuation = distance * perBlockAirAttenuation;
|
||||
if (attenuation >= 1.0f)
|
||||
continue;
|
||||
|
||||
Vec2F direction = relativeLightPosition / distance;
|
||||
if (light.beam > 0.0f) {
|
||||
attenuation += (1.0f - light.beamAmbience) * clamp(light.beam * (1.0f - direction * beamDirection), 0.0f, 1.0f);
|
||||
if (attenuation >= 1.0f)
|
||||
continue;
|
||||
}
|
||||
|
||||
float remainingAttenuation = maxIntensity - LightTraits::minIntensity(lvalue) - attenuation;
|
||||
if (remainingAttenuation <= 0.0f)
|
||||
continue;
|
||||
|
||||
// Need to circularize manhattan attenuation here
|
||||
float circularizedPerBlockObstacleAttenuation = perBlockObstacleAttenuation / max(fabs(direction[0]), fabs(direction[1]));
|
||||
float blockAttenuation = lineAttenuation(blockPos, light.position, circularizedPerBlockObstacleAttenuation, remainingAttenuation);
|
||||
|
||||
// Apply single obstacle boost (determine single obstacle by one
|
||||
// block unit of attenuation).
|
||||
attenuation += blockAttenuation + min(blockAttenuation, circularizedPerBlockObstacleAttenuation) * m_pointObstacleBoost;
|
||||
|
||||
if (attenuation < 1.0f)
|
||||
setLight(x, y, LightTraits::max(LightTraits::subtract(light.value, attenuation), lvalue));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LightTraits>
|
||||
float CellularLightArray<LightTraits>::lineAttenuation(Vec2F const& start, Vec2F const& end,
|
||||
float perObstacleAttenuation, float maxAttenuation) {
|
||||
// Run Xiaolin Wu's line algorithm from start to end, summing over colliding
|
||||
// blocks using perObstacleAttenuation.
|
||||
float obstacleAttenuation = 0.0;
|
||||
|
||||
// Apply correction because integer coordinates are lower left corner.
|
||||
float x1 = start[0] - 0.5;
|
||||
float y1 = start[1] - 0.5;
|
||||
float x2 = end[0] - 0.5;
|
||||
float y2 = end[1] - 0.5;
|
||||
|
||||
float dx = x2 - x1;
|
||||
float dy = y2 - y1;
|
||||
|
||||
if (fabs(dx) < fabs(dy)) {
|
||||
if (y2 < y1) {
|
||||
swap(y1, y2);
|
||||
swap(x1, x2);
|
||||
}
|
||||
|
||||
float gradient = dx / dy;
|
||||
|
||||
// first end point
|
||||
float yend = round(y1);
|
||||
float xend = x1 + gradient * (yend - y1);
|
||||
float ygap = rfpart(y1 + 0.5);
|
||||
int ypxl1 = yend;
|
||||
int xpxl1 = ipart(xend);
|
||||
|
||||
if (cell(xpxl1, ypxl1).obstacle)
|
||||
obstacleAttenuation += rfpart(xend) * ygap * perObstacleAttenuation;
|
||||
|
||||
if (cell(xpxl1 + 1, ypxl1).obstacle)
|
||||
obstacleAttenuation += fpart(xend) * ygap * perObstacleAttenuation;
|
||||
|
||||
if (obstacleAttenuation >= maxAttenuation)
|
||||
return maxAttenuation;
|
||||
|
||||
float interx = xend + gradient;
|
||||
|
||||
// second end point
|
||||
yend = round(y2);
|
||||
xend = x2 + gradient * (yend - y2);
|
||||
ygap = fpart(y2 + 0.5);
|
||||
int ypxl2 = yend;
|
||||
int xpxl2 = ipart(xend);
|
||||
|
||||
if (cell(xpxl2, ypxl2).obstacle)
|
||||
obstacleAttenuation += rfpart(xend) * ygap * perObstacleAttenuation;
|
||||
|
||||
if (cell(xpxl2 + 1, ypxl2).obstacle)
|
||||
obstacleAttenuation += fpart(xend) * ygap * perObstacleAttenuation;
|
||||
|
||||
if (obstacleAttenuation >= maxAttenuation)
|
||||
return maxAttenuation;
|
||||
|
||||
for (int y = ypxl1 + 1; y < ypxl2; ++y) {
|
||||
int interxIpart = ipart(interx);
|
||||
float interxFpart = interx - interxIpart;
|
||||
float interxRFpart = 1.0 - interxFpart;
|
||||
|
||||
if (cell(interxIpart, y).obstacle)
|
||||
obstacleAttenuation += interxRFpart * perObstacleAttenuation;
|
||||
if (cell(interxIpart + 1, y).obstacle)
|
||||
obstacleAttenuation += interxFpart * perObstacleAttenuation;
|
||||
|
||||
if (obstacleAttenuation >= maxAttenuation)
|
||||
return maxAttenuation;
|
||||
|
||||
interx += gradient;
|
||||
}
|
||||
} else {
|
||||
if (x2 < x1) {
|
||||
swap(x1, x2);
|
||||
swap(y1, y2);
|
||||
}
|
||||
|
||||
float gradient = dy / dx;
|
||||
|
||||
// first end point
|
||||
float xend = round(x1);
|
||||
float yend = y1 + gradient * (xend - x1);
|
||||
float xgap = rfpart(x1 + 0.5);
|
||||
int xpxl1 = xend;
|
||||
int ypxl1 = ipart(yend);
|
||||
|
||||
if (cell(xpxl1, ypxl1).obstacle)
|
||||
obstacleAttenuation += rfpart(yend) * xgap * perObstacleAttenuation;
|
||||
|
||||
if (cell(xpxl1, ypxl1 + 1).obstacle)
|
||||
obstacleAttenuation += fpart(yend) * xgap * perObstacleAttenuation;
|
||||
|
||||
if (obstacleAttenuation >= maxAttenuation)
|
||||
return maxAttenuation;
|
||||
|
||||
float intery = yend + gradient;
|
||||
|
||||
// second end point
|
||||
xend = round(x2);
|
||||
yend = y2 + gradient * (xend - x2);
|
||||
xgap = fpart(x2 + 0.5);
|
||||
int xpxl2 = xend;
|
||||
int ypxl2 = ipart(yend);
|
||||
|
||||
if (cell(xpxl2, ypxl2).obstacle)
|
||||
obstacleAttenuation += rfpart(yend) * xgap * perObstacleAttenuation;
|
||||
|
||||
if (cell(xpxl2, ypxl2 + 1).obstacle)
|
||||
obstacleAttenuation += fpart(yend) * xgap * perObstacleAttenuation;
|
||||
|
||||
if (obstacleAttenuation >= maxAttenuation)
|
||||
return maxAttenuation;
|
||||
|
||||
for (int x = xpxl1 + 1; x < xpxl2; ++x) {
|
||||
int interyIpart = ipart(intery);
|
||||
float interyFpart = intery - interyIpart;
|
||||
float interyRFpart = 1.0 - interyFpart;
|
||||
|
||||
if (cell(x, interyIpart).obstacle)
|
||||
obstacleAttenuation += interyRFpart * perObstacleAttenuation;
|
||||
if (cell(x, interyIpart + 1).obstacle)
|
||||
obstacleAttenuation += interyFpart * perObstacleAttenuation;
|
||||
|
||||
if (obstacleAttenuation >= maxAttenuation)
|
||||
return maxAttenuation;
|
||||
|
||||
intery += gradient;
|
||||
}
|
||||
}
|
||||
|
||||
return min(obstacleAttenuation, maxAttenuation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
160
source/base/StarCellularLighting.cpp
Normal file
160
source/base/StarCellularLighting.cpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#include "StarCellularLighting.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
CellularLightingCalculator::CellularLightingCalculator(bool monochrome) {
|
||||
setMonochrome(monochrome);
|
||||
}
|
||||
|
||||
void CellularLightingCalculator::setMonochrome(bool monochrome) {
|
||||
if (monochrome == m_monochrome)
|
||||
return;
|
||||
|
||||
m_monochrome = monochrome;
|
||||
if (monochrome)
|
||||
m_lightArray.setRight(ScalarCellularLightArray());
|
||||
else
|
||||
m_lightArray.setLeft(ColoredCellularLightArray());
|
||||
|
||||
if (m_config)
|
||||
setParameters(m_config);
|
||||
}
|
||||
|
||||
void CellularLightingCalculator::setParameters(Json const& config) {
|
||||
m_config = config;
|
||||
if (m_monochrome)
|
||||
m_lightArray.right().setParameters(
|
||||
config.getInt("spreadPasses"),
|
||||
config.getFloat("spreadMaxAir"),
|
||||
config.getFloat("spreadMaxObstacle"),
|
||||
config.getFloat("pointMaxAir"),
|
||||
config.getFloat("pointMaxObstacle"),
|
||||
config.getFloat("pointObstacleBoost")
|
||||
);
|
||||
else
|
||||
m_lightArray.left().setParameters(
|
||||
config.getInt("spreadPasses"),
|
||||
config.getFloat("spreadMaxAir"),
|
||||
config.getFloat("spreadMaxObstacle"),
|
||||
config.getFloat("pointMaxAir"),
|
||||
config.getFloat("pointMaxObstacle"),
|
||||
config.getFloat("pointObstacleBoost")
|
||||
);
|
||||
}
|
||||
|
||||
void CellularLightingCalculator::begin(RectI const& queryRegion) {
|
||||
m_queryRegion = queryRegion;
|
||||
if (m_monochrome) {
|
||||
m_calculationRegion = RectI(queryRegion).padded((int)m_lightArray.right().borderCells());
|
||||
m_lightArray.right().begin(m_calculationRegion.width(), m_calculationRegion.height());
|
||||
} else {
|
||||
m_calculationRegion = RectI(queryRegion).padded((int)m_lightArray.left().borderCells());
|
||||
m_lightArray.left().begin(m_calculationRegion.width(), m_calculationRegion.height());
|
||||
}
|
||||
}
|
||||
|
||||
RectI CellularLightingCalculator::calculationRegion() const {
|
||||
return m_calculationRegion;
|
||||
}
|
||||
|
||||
void CellularLightingCalculator::addSpreadLight(Vec2F const& position, Vec3F const& light) {
|
||||
Vec2F arrayPosition = position - Vec2F(m_calculationRegion.min());
|
||||
if (m_monochrome)
|
||||
m_lightArray.right().addSpreadLight({arrayPosition, light.max()});
|
||||
else
|
||||
m_lightArray.left().addSpreadLight({arrayPosition, light});
|
||||
}
|
||||
|
||||
void CellularLightingCalculator::addPointLight(Vec2F const& position, Vec3F const& light, float beam, float beamAngle, float beamAmbience) {
|
||||
Vec2F arrayPosition = position - Vec2F(m_calculationRegion.min());
|
||||
if (m_monochrome)
|
||||
m_lightArray.right().addPointLight({arrayPosition, light.max(), beam, beamAngle, beamAmbience});
|
||||
else
|
||||
m_lightArray.left().addPointLight({arrayPosition, light, beam, beamAngle, beamAmbience});
|
||||
}
|
||||
|
||||
void CellularLightingCalculator::calculate(Image& output) {
|
||||
Vec2S arrayMin = Vec2S(m_queryRegion.min() - m_calculationRegion.min());
|
||||
Vec2S arrayMax = Vec2S(m_queryRegion.max() - m_calculationRegion.min());
|
||||
|
||||
if (m_monochrome)
|
||||
m_lightArray.right().calculate(arrayMin[0], arrayMin[1], arrayMax[0], arrayMax[1]);
|
||||
else
|
||||
m_lightArray.left().calculate(arrayMin[0], arrayMin[1], arrayMax[0], arrayMax[1]);
|
||||
|
||||
output.reset(arrayMax[0] - arrayMin[0], arrayMax[1] - arrayMin[1], PixelFormat::RGB24);
|
||||
|
||||
for (size_t x = arrayMin[0]; x < arrayMax[0]; ++x) {
|
||||
for (size_t y = arrayMin[1]; y < arrayMax[1]; ++y) {
|
||||
if (m_monochrome)
|
||||
output.set24(x - arrayMin[0], y - arrayMin[1], Color::grayf(m_lightArray.right().getLight(x, y)).toRgb());
|
||||
else
|
||||
output.set24(x - arrayMin[0], y - arrayMin[1], Color::v3fToByte(m_lightArray.left().getLight(x, y)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CellularLightIntensityCalculator::setParameters(Json const& config) {
|
||||
m_lightArray.setParameters(
|
||||
config.getInt("spreadPasses"),
|
||||
config.getFloat("spreadMaxAir"),
|
||||
config.getFloat("spreadMaxObstacle"),
|
||||
config.getFloat("pointMaxAir"),
|
||||
config.getFloat("pointMaxObstacle"),
|
||||
config.getFloat("pointObstacleBoost")
|
||||
);
|
||||
}
|
||||
|
||||
void CellularLightIntensityCalculator::begin(Vec2F const& queryPosition) {
|
||||
m_queryPosition = queryPosition;
|
||||
m_queryRegion = RectI::withSize(Vec2I::floor(queryPosition - Vec2F::filled(0.5f)), Vec2I(2, 2));
|
||||
m_calculationRegion = RectI(m_queryRegion).padded((int)m_lightArray.borderCells());
|
||||
|
||||
m_lightArray.begin(m_calculationRegion.width(), m_calculationRegion.height());
|
||||
}
|
||||
|
||||
RectI CellularLightIntensityCalculator::calculationRegion() const {
|
||||
return m_calculationRegion;
|
||||
}
|
||||
|
||||
void CellularLightIntensityCalculator::setCell(Vec2I const& position, Cell const& cell) {
|
||||
setCellColumn(position, &cell, 1);
|
||||
}
|
||||
|
||||
void CellularLightIntensityCalculator::setCellColumn(Vec2I const& position, Cell const* cells, size_t count) {
|
||||
size_t baseIndex = (position[0] - m_calculationRegion.xMin()) * m_calculationRegion.height() + position[1] - m_calculationRegion.yMin();
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
m_lightArray.cellAtIndex(baseIndex + i) = cells[i];
|
||||
}
|
||||
|
||||
void CellularLightIntensityCalculator::addSpreadLight(Vec2F const& position, float light) {
|
||||
Vec2F arrayPosition = position - Vec2F(m_calculationRegion.min());
|
||||
m_lightArray.addSpreadLight({arrayPosition, light});
|
||||
}
|
||||
|
||||
void CellularLightIntensityCalculator::addPointLight(Vec2F const& position, float light, float beam, float beamAngle, float beamAmbience) {
|
||||
Vec2F arrayPosition = position - Vec2F(m_calculationRegion.min());
|
||||
m_lightArray.addPointLight({arrayPosition, light, beam, beamAngle, beamAmbience});
|
||||
}
|
||||
|
||||
|
||||
float CellularLightIntensityCalculator::calculate() {
|
||||
Vec2S arrayMin = Vec2S(m_queryRegion.min() - m_calculationRegion.min());
|
||||
Vec2S arrayMax = Vec2S(m_queryRegion.max() - m_calculationRegion.min());
|
||||
|
||||
m_lightArray.calculate(arrayMin[0], arrayMin[1], arrayMax[0], arrayMax[1]);
|
||||
|
||||
// Do 2d lerp to find lighting intensity
|
||||
|
||||
float ll = m_lightArray.getLight(arrayMin[0], arrayMin[1]);
|
||||
float lr = m_lightArray.getLight(arrayMin[0] + 1, arrayMin[1]);
|
||||
float ul = m_lightArray.getLight(arrayMin[0], arrayMin[1] + 1);
|
||||
float ur = m_lightArray.getLight(arrayMin[0] + 1, arrayMin[1] + 1);
|
||||
|
||||
float xl = m_queryPosition[0] - 0.5f - m_queryRegion.xMin();
|
||||
float yl = m_queryPosition[1] - 0.5f - m_queryRegion.yMin();
|
||||
|
||||
return lerp(yl, lerp(xl, ll, lr), lerp(xl, ul, ur));
|
||||
}
|
||||
|
||||
}
|
||||
96
source/base/StarCellularLighting.hpp
Normal file
96
source/base/StarCellularLighting.hpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#ifndef STAR_CELLULAR_LIGHTING_HPP
|
||||
#define STAR_CELLULAR_LIGHTING_HPP
|
||||
|
||||
#include "StarEither.hpp"
|
||||
#include "StarRect.hpp"
|
||||
#include "StarImage.hpp"
|
||||
#include "StarJson.hpp"
|
||||
#include "StarColor.hpp"
|
||||
#include "StarInterpolation.hpp"
|
||||
#include "StarCellularLightArray.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// Produce lighting values from an integral cellular grid. Allows for floating
|
||||
// positional point and cellular light sources, as well as pre-lighting cells
|
||||
// individually.
|
||||
class CellularLightingCalculator {
|
||||
public:
|
||||
CellularLightingCalculator(bool monochrome = false);
|
||||
|
||||
typedef ColoredCellularLightArray::Cell Cell;
|
||||
|
||||
void setMonochrome(bool monochrome);
|
||||
|
||||
void setParameters(Json const& config);
|
||||
|
||||
// Call 'begin' to start a calculation for the given region
|
||||
void begin(RectI const& queryRegion);
|
||||
|
||||
// Once begin is called, this will return the region that could possibly
|
||||
// affect the target calculation region. All lighting values should be set
|
||||
// for the given calculation region before calling 'calculate'.
|
||||
RectI calculationRegion() const;
|
||||
|
||||
size_t baseIndexFor(Vec2I const& position);
|
||||
|
||||
void setCellIndex(size_t cellIndex, Vec3F const& light, bool obstacle);
|
||||
|
||||
void addSpreadLight(Vec2F const& position, Vec3F const& light);
|
||||
void addPointLight(Vec2F const& position, Vec3F const& light, float beam, float beamAngle, float beamAmbience);
|
||||
|
||||
// Finish the calculation, and put the resulting color data in the given
|
||||
// output image. The image will be reset to the size of the region given in
|
||||
// the call to 'begin', and formatted as RGB24.
|
||||
void calculate(Image& output);
|
||||
|
||||
private:
|
||||
Json m_config;
|
||||
bool m_monochrome;
|
||||
Either<ColoredCellularLightArray, ScalarCellularLightArray> m_lightArray;
|
||||
RectI m_queryRegion;
|
||||
RectI m_calculationRegion;
|
||||
};
|
||||
|
||||
// Produce light intensity values using the same algorithm as
|
||||
// CellularLightingCalculator. Only calculates a single point at a time, and
|
||||
// uses scalar lights with no color calculation.
|
||||
class CellularLightIntensityCalculator {
|
||||
public:
|
||||
typedef ScalarCellularLightArray::Cell Cell;
|
||||
|
||||
void setParameters(Json const& config);
|
||||
|
||||
void begin(Vec2F const& queryPosition);
|
||||
|
||||
RectI calculationRegion() const;
|
||||
|
||||
void setCell(Vec2I const& position, Cell const& cell);
|
||||
void setCellColumn(Vec2I const& position, Cell const* cells, size_t count);
|
||||
|
||||
void addSpreadLight(Vec2F const& position, float light);
|
||||
void addPointLight(Vec2F const& position, float light, float beam, float beamAngle, float beamAmbience);
|
||||
|
||||
float calculate();
|
||||
|
||||
private:
|
||||
ScalarCellularLightArray m_lightArray;
|
||||
Vec2F m_queryPosition;
|
||||
RectI m_queryRegion;;
|
||||
RectI m_calculationRegion;
|
||||
};
|
||||
|
||||
inline size_t CellularLightingCalculator::baseIndexFor(Vec2I const& position) {
|
||||
return (position[0] - m_calculationRegion.xMin()) * m_calculationRegion.height() + position[1] - m_calculationRegion.yMin();
|
||||
}
|
||||
|
||||
inline void CellularLightingCalculator::setCellIndex(size_t cellIndex, Vec3F const& light, bool obstacle) {
|
||||
if (m_monochrome)
|
||||
m_lightArray.right().cellAtIndex(cellIndex) = ScalarCellularLightArray::Cell{light.sum() / 3, obstacle};
|
||||
else
|
||||
m_lightArray.left().cellAtIndex(cellIndex) = ColoredCellularLightArray::Cell{light, obstacle};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
658
source/base/StarCellularLiquid.hpp
Normal file
658
source/base/StarCellularLiquid.hpp
Normal file
|
|
@ -0,0 +1,658 @@
|
|||
#ifndef STAR_CELLULAR_LIQUID_HPP
|
||||
#define STAR_CELLULAR_LIQUID_HPP
|
||||
|
||||
#include "StarVariant.hpp"
|
||||
#include "StarRect.hpp"
|
||||
#include "StarMultiArray.hpp"
|
||||
#include "StarMap.hpp"
|
||||
#include "StarOrderedSet.hpp"
|
||||
#include "StarRandom.hpp"
|
||||
#include "StarBlockAllocator.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
struct CellularLiquidCollisionCell {};
|
||||
|
||||
template <typename LiquidId>
|
||||
struct CellularLiquidFlowCell {
|
||||
Maybe<LiquidId> liquid;
|
||||
float level;
|
||||
float pressure;
|
||||
};
|
||||
|
||||
template <typename LiquidId>
|
||||
struct CellularLiquidSourceCell {
|
||||
LiquidId liquid;
|
||||
float pressure;
|
||||
};
|
||||
|
||||
template <typename LiquidId>
|
||||
using CellularLiquidCell = Variant<CellularLiquidCollisionCell, CellularLiquidFlowCell<LiquidId>, CellularLiquidSourceCell<LiquidId>>;
|
||||
|
||||
template <typename LiquidId>
|
||||
struct CellularLiquidWorld {
|
||||
virtual ~CellularLiquidWorld();
|
||||
|
||||
virtual Vec2I uniqueLocation(Vec2I const& location) const;
|
||||
|
||||
virtual CellularLiquidCell<LiquidId> cell(Vec2I const& location) const = 0;
|
||||
|
||||
// Should return an amount between 0.0 and 1.0 as a percentage of liquid
|
||||
// drain at this position
|
||||
virtual float drainLevel(Vec2I const& location) const;
|
||||
|
||||
// Will be called only on cells which for which the cell method returned a
|
||||
// flow cell, to update the flow cell.
|
||||
virtual void setFlow(Vec2I const& location, CellularLiquidFlowCell<LiquidId> const& flow) = 0;
|
||||
|
||||
// Called once for every active liquid <-> liquid interaction of different
|
||||
// liquid types each update. Will be called AFTER pushing all the flow
|
||||
// values back out so modifications to liquids are sensible.
|
||||
virtual void liquidInteraction(Vec2I const& a, LiquidId aLiquid, Vec2I const& b, LiquidId bLiquid);
|
||||
// Called once for every liquid collision each update. Also called after
|
||||
// pushing all the flow values out, so changes to liquids can sensibly be
|
||||
// performed here.
|
||||
virtual void liquidCollision(Vec2I const& pos, LiquidId liquid, Vec2I const& collisionPos);
|
||||
};
|
||||
|
||||
struct LiquidCellEngineParameters {
|
||||
float lateralMoveFactor;
|
||||
float spreadOverfillUpFactor;
|
||||
float spreadOverfillLateralFactor;
|
||||
float spreadOverfillDownFactor;
|
||||
float pressureEqualizeFactor;
|
||||
float pressureMoveFactor;
|
||||
float maximumPressureLevelImbalance;
|
||||
float minimumLivenPressureChange;
|
||||
float minimumLivenLevelChange;
|
||||
float minimumLiquidLevel;
|
||||
float interactTransformationLevel;
|
||||
};
|
||||
|
||||
template <typename LiquidId>
|
||||
class LiquidCellEngine {
|
||||
public:
|
||||
typedef shared_ptr<CellularLiquidWorld<LiquidId>> CellularLiquidWorldPtr;
|
||||
|
||||
LiquidCellEngine(LiquidCellEngineParameters parameters, CellularLiquidWorldPtr cellWorld);
|
||||
|
||||
unsigned liquidTickDelta(LiquidId liquid);
|
||||
void setLiquidTickDelta(LiquidId liquid, unsigned tickDelta);
|
||||
|
||||
void setProcessingLimit(Maybe<unsigned> processingLimit);
|
||||
|
||||
List<RectI> noProcessingLimitRegions() const;
|
||||
void setNoProcessingLimitRegions(List<RectI> noProcessingLimitRegions);
|
||||
|
||||
void visitLocation(Vec2I const& location);
|
||||
void visitRegion(RectI const& region);
|
||||
|
||||
void update();
|
||||
|
||||
size_t activeCells() const;
|
||||
size_t activeCells(LiquidId liquid) const;
|
||||
bool isActive(Vec2I const& pos) const;
|
||||
|
||||
private:
|
||||
enum class Adjacency {
|
||||
Left,
|
||||
Right,
|
||||
Bottom,
|
||||
Top
|
||||
};
|
||||
|
||||
struct WorkingCell {
|
||||
Vec2I position;
|
||||
Maybe<LiquidId> liquid;
|
||||
bool sourceCell;
|
||||
float level;
|
||||
float pressure;
|
||||
|
||||
WorkingCell* leftCell;
|
||||
WorkingCell* rightCell;
|
||||
WorkingCell* topCell;
|
||||
WorkingCell* bottomCell;
|
||||
};
|
||||
|
||||
template <typename Key, typename Value>
|
||||
using BAHashMap = StableHashMap<Key, Value, hash<Key>, std::equal_to<Key>, BlockAllocator<pair<Key const, Value>, 4096>>;
|
||||
|
||||
template <typename Value>
|
||||
using BAHashSet = HashSet<Value, hash<Value>, std::equal_to<Value>>;
|
||||
|
||||
template <typename Value>
|
||||
using BAOrderedHashSet = OrderedHashSet<Value, hash<Value>, std::equal_to<Value>, BlockAllocator<Value, 4096>>;
|
||||
|
||||
void setup();
|
||||
void applyPressure();
|
||||
void spreadPressure();
|
||||
void limitPressure();
|
||||
void pressureMove();
|
||||
void spreadOverfill();
|
||||
void levelMove();
|
||||
void findInteractions();
|
||||
void finish();
|
||||
|
||||
WorkingCell* workingCell(Vec2I p);
|
||||
WorkingCell* adjacentCell(WorkingCell* cell, Adjacency adjacency);
|
||||
|
||||
void setPressure(float pressure, WorkingCell& cell);
|
||||
void transferPressure(float amount, WorkingCell& source, WorkingCell& dest, bool allowReverse);
|
||||
void transferLevel(float amount, WorkingCell& source, WorkingCell& dest, bool allowReverse);
|
||||
void setLevel(float level, WorkingCell& cell);
|
||||
|
||||
RandomSource m_random;
|
||||
LiquidCellEngineParameters m_engineParameters;
|
||||
CellularLiquidWorldPtr m_cellWorld;
|
||||
|
||||
BAHashMap<LiquidId, BAOrderedHashSet<Vec2I>> m_activeCells;
|
||||
BAHashMap<LiquidId, unsigned> m_liquidTickDeltas;
|
||||
Maybe<unsigned> m_processingLimit;
|
||||
List<RectI> m_noProcessingLimitRegions;
|
||||
uint64_t m_step;
|
||||
|
||||
BAHashMap<Vec2I, Maybe<WorkingCell>> m_workingCells;
|
||||
List<WorkingCell*> m_currentActiveCells;
|
||||
BAHashSet<Vec2I> m_nextActiveCells;
|
||||
BAHashSet<tuple<Vec2I, LiquidId, Vec2I, LiquidId>> m_liquidInteractions;
|
||||
BAHashSet<tuple<Vec2I, LiquidId, Vec2I>> m_liquidCollisions;
|
||||
};
|
||||
|
||||
template <typename LiquidId>
|
||||
CellularLiquidWorld<LiquidId>::~CellularLiquidWorld() {}
|
||||
|
||||
template <typename LiquidId>
|
||||
Vec2I CellularLiquidWorld<LiquidId>::uniqueLocation(Vec2I const& location) const {
|
||||
return location;
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
float CellularLiquidWorld<LiquidId>::drainLevel(Vec2I const&) const {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void CellularLiquidWorld<LiquidId>::liquidInteraction(Vec2I const&, LiquidId, Vec2I const&, LiquidId) {}
|
||||
|
||||
template <typename LiquidId>
|
||||
void CellularLiquidWorld<LiquidId>::liquidCollision(Vec2I const&, LiquidId, Vec2I const&) {}
|
||||
|
||||
template <typename LiquidId>
|
||||
LiquidCellEngine<LiquidId>::LiquidCellEngine(LiquidCellEngineParameters parameters, CellularLiquidWorldPtr cellWorld)
|
||||
: m_engineParameters(parameters), m_cellWorld(cellWorld), m_step(0) {}
|
||||
|
||||
template <typename LiquidId>
|
||||
unsigned LiquidCellEngine<LiquidId>::liquidTickDelta(LiquidId liquid) {
|
||||
return m_liquidTickDeltas.value(liquid, 1);
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::setLiquidTickDelta(LiquidId liquid, unsigned tickDelta) {
|
||||
m_liquidTickDeltas[liquid] = tickDelta;
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::setProcessingLimit(Maybe<unsigned> processingLimit) {
|
||||
m_processingLimit = processingLimit;
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
List<RectI> LiquidCellEngine<LiquidId>::noProcessingLimitRegions() const {
|
||||
return m_noProcessingLimitRegions;
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::setNoProcessingLimitRegions(List<RectI> noProcessingLimitRegions) {
|
||||
m_noProcessingLimitRegions = noProcessingLimitRegions;
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::visitLocation(Vec2I const& p) {
|
||||
m_nextActiveCells.add(p);
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::visitRegion(RectI const& region) {
|
||||
for (int x = region.xMin(); x < region.xMax(); ++x) {
|
||||
for (int y = region.yMin(); y < region.yMax(); ++y)
|
||||
m_nextActiveCells.add({x, y});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::update() {
|
||||
setup();
|
||||
applyPressure();
|
||||
spreadPressure();
|
||||
limitPressure();
|
||||
pressureMove();
|
||||
spreadOverfill();
|
||||
levelMove();
|
||||
findInteractions();
|
||||
finish();
|
||||
|
||||
++m_step;
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
size_t LiquidCellEngine<LiquidId>::activeCells() const {
|
||||
size_t totalSize = 0;
|
||||
for (auto const& p : m_activeCells)
|
||||
totalSize += p.second.size();
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
size_t LiquidCellEngine<LiquidId>::activeCells(LiquidId liquid) const {
|
||||
return m_activeCells.value(liquid).size();
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
bool LiquidCellEngine<LiquidId>::isActive(Vec2I const& pos) const {
|
||||
for (auto const& p : m_activeCells) {
|
||||
if (p.second.contains(pos))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::setup() {
|
||||
// In case an exception occurred during the last update, clear potentially
|
||||
// stale data here
|
||||
m_workingCells.clear();
|
||||
m_currentActiveCells.clear();
|
||||
|
||||
for (auto& activeCellsPair : m_activeCells) {
|
||||
unsigned tickDelta = liquidTickDelta(activeCellsPair.first);
|
||||
if (tickDelta == 0 || m_step % tickDelta != 0)
|
||||
continue;
|
||||
|
||||
size_t limitedCellNumber = 0;
|
||||
for (auto const& pos : activeCellsPair.second.values()) {
|
||||
if (m_processingLimit) {
|
||||
bool foundInUnlimitedRegion = false;
|
||||
for (auto const& region : m_noProcessingLimitRegions) {
|
||||
if (region.contains(pos)) {
|
||||
foundInUnlimitedRegion = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundInUnlimitedRegion) {
|
||||
if (limitedCellNumber < *m_processingLimit)
|
||||
++limitedCellNumber;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto cell = workingCell(pos);
|
||||
if (!cell || cell->liquid != activeCellsPair.first) {
|
||||
activeCellsPair.second.remove(pos);
|
||||
} else {
|
||||
m_currentActiveCells.append(cell);
|
||||
activeCellsPair.second.remove(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort(m_currentActiveCells, [](WorkingCell* lhs, WorkingCell* rhs) {
|
||||
return lhs->position[1] < rhs->position[1];
|
||||
});
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::applyPressure() {
|
||||
for (auto const& selfCell : m_currentActiveCells) {
|
||||
if (!selfCell->liquid || selfCell->sourceCell)
|
||||
continue;
|
||||
|
||||
auto topCell = adjacentCell(selfCell, Adjacency::Top);
|
||||
if (topCell && selfCell->liquid == topCell->liquid)
|
||||
setPressure(max(selfCell->pressure, topCell->pressure + min(topCell->level, 1.0f)), *selfCell);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::spreadPressure() {
|
||||
for (auto const& selfCell : m_currentActiveCells) {
|
||||
if (!selfCell->liquid)
|
||||
continue;
|
||||
|
||||
auto spreadPressure = [&](Adjacency adjacency, float bias) {
|
||||
auto targetCell = adjacentCell(selfCell, adjacency);
|
||||
if (targetCell && !targetCell->sourceCell)
|
||||
transferPressure((selfCell->pressure + bias - targetCell->pressure) * m_engineParameters.pressureEqualizeFactor, *selfCell, *targetCell, true);
|
||||
};
|
||||
|
||||
if (m_random.randb()) {
|
||||
spreadPressure(Adjacency::Left, 0.0f);
|
||||
spreadPressure(Adjacency::Right, 0.0f);
|
||||
} else {
|
||||
spreadPressure(Adjacency::Right, 0.0f);
|
||||
spreadPressure(Adjacency::Left, 0.0f);
|
||||
}
|
||||
|
||||
spreadPressure(Adjacency::Bottom, 1.0f);
|
||||
spreadPressure(Adjacency::Top, -1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::limitPressure() {
|
||||
for (auto const& selfCell : m_currentActiveCells) {
|
||||
float level = min(selfCell->level, 1.0f);
|
||||
auto topCell = adjacentCell(selfCell, Adjacency::Top);
|
||||
|
||||
// Force the pressure to the cell level if there is empty space above,
|
||||
// otherwise simply make sure the pressure is at least the level
|
||||
if (topCell && !topCell->liquid)
|
||||
setPressure(level, *selfCell);
|
||||
else
|
||||
setPressure(max(selfCell->pressure, level), *selfCell);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::pressureMove() {
|
||||
for (auto const& selfCell : m_currentActiveCells) {
|
||||
if (!selfCell->liquid)
|
||||
continue;
|
||||
|
||||
auto pressureMove = [&](Adjacency adjacency) {
|
||||
auto targetCell = adjacentCell(selfCell, adjacency);
|
||||
if (targetCell && !targetCell->sourceCell && targetCell->level >= selfCell->level) {
|
||||
float amount = (selfCell->pressure - targetCell->pressure) * m_engineParameters.pressureMoveFactor;
|
||||
amount = min(amount, selfCell->level - (1.0f - m_engineParameters.maximumPressureLevelImbalance));
|
||||
amount = min(amount, (1.0f + m_engineParameters.maximumPressureLevelImbalance) - targetCell->level);
|
||||
transferLevel(amount, *selfCell, *targetCell, false);
|
||||
}
|
||||
};
|
||||
|
||||
if (m_random.randb()) {
|
||||
pressureMove(Adjacency::Left);
|
||||
pressureMove(Adjacency::Right);
|
||||
} else {
|
||||
pressureMove(Adjacency::Right);
|
||||
pressureMove(Adjacency::Left);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::spreadOverfill() {
|
||||
for (auto const& selfCell : m_currentActiveCells) {
|
||||
if (!selfCell->liquid || selfCell->sourceCell)
|
||||
continue;
|
||||
|
||||
auto spreadOverfill = [&](Adjacency adjacency, float factor) {
|
||||
float overfill = selfCell->level - 1.0f;
|
||||
if (overfill > 0.0f) {
|
||||
auto targetCell = adjacentCell(selfCell, adjacency);
|
||||
if (targetCell)
|
||||
transferLevel(min(overfill, (selfCell->level - targetCell->level)) * factor, *selfCell, *targetCell, false);
|
||||
}
|
||||
};
|
||||
|
||||
spreadOverfill(Adjacency::Top, m_engineParameters.spreadOverfillUpFactor);
|
||||
|
||||
if (m_random.randb()) {
|
||||
spreadOverfill(Adjacency::Left, m_engineParameters.spreadOverfillLateralFactor);
|
||||
spreadOverfill(Adjacency::Right, m_engineParameters.spreadOverfillLateralFactor);
|
||||
} else {
|
||||
spreadOverfill(Adjacency::Right, m_engineParameters.spreadOverfillLateralFactor);
|
||||
spreadOverfill(Adjacency::Left, m_engineParameters.spreadOverfillLateralFactor);
|
||||
}
|
||||
|
||||
spreadOverfill(Adjacency::Bottom, m_engineParameters.spreadOverfillDownFactor);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::levelMove() {
|
||||
for (auto const& selfCell : m_currentActiveCells) {
|
||||
if (!selfCell->liquid)
|
||||
continue;
|
||||
|
||||
auto belowCell = adjacentCell(selfCell, Adjacency::Bottom);
|
||||
if (belowCell)
|
||||
transferLevel(min(1.0f - belowCell->level, selfCell->level), *selfCell, *belowCell, false);
|
||||
|
||||
setLevel(selfCell->level * (1.0f - m_cellWorld->drainLevel(selfCell->position)), *selfCell);
|
||||
|
||||
auto lateralMove = [&](Adjacency adjacency) {
|
||||
auto targetCell = adjacentCell(selfCell, adjacency);
|
||||
if (targetCell)
|
||||
transferLevel((selfCell->level - targetCell->level) * m_engineParameters.lateralMoveFactor, *selfCell, *targetCell, false);
|
||||
};
|
||||
|
||||
if (m_random.randb()) {
|
||||
lateralMove(Adjacency::Left);
|
||||
lateralMove(Adjacency::Right);
|
||||
} else {
|
||||
lateralMove(Adjacency::Right);
|
||||
lateralMove(Adjacency::Left);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::findInteractions() {
|
||||
for (auto const& selfCell : m_currentActiveCells) {
|
||||
if (!selfCell->liquid)
|
||||
continue;
|
||||
|
||||
for (auto adjacency : {Adjacency::Bottom, Adjacency::Top, Adjacency::Left, Adjacency::Right}) {
|
||||
auto targetCell = adjacentCell(selfCell, adjacency);
|
||||
if (!targetCell) {
|
||||
Vec2I adjacentPos = selfCell->position;
|
||||
if (adjacency == Adjacency::Left)
|
||||
adjacentPos += Vec2I(-1, 0);
|
||||
else if (adjacency == Adjacency::Right)
|
||||
adjacentPos += Vec2I(1, 0);
|
||||
else if (adjacency == Adjacency::Bottom)
|
||||
adjacentPos += Vec2I(0, -1);
|
||||
else if (adjacency == Adjacency::Top)
|
||||
adjacentPos += Vec2I(0, 1);
|
||||
m_liquidCollisions.add(make_tuple(selfCell->position, *selfCell->liquid, adjacentPos));
|
||||
|
||||
} else if (targetCell->liquid && *targetCell->liquid != *selfCell->liquid) {
|
||||
if (targetCell->level <= m_engineParameters.interactTransformationLevel
|
||||
|| selfCell->level <= m_engineParameters.interactTransformationLevel) {
|
||||
if (selfCell->level > targetCell->level)
|
||||
targetCell->liquid = selfCell->liquid;
|
||||
else
|
||||
selfCell->liquid = targetCell->liquid;
|
||||
} else {
|
||||
// Make sure to add the point pair in a predictable order so that any
|
||||
// combination of Vec2I points will be unique in m_liquidInteractions
|
||||
if (selfCell->position < targetCell->position)
|
||||
m_liquidInteractions.add(make_tuple(selfCell->position, *selfCell->liquid, targetCell->position, *targetCell->liquid));
|
||||
else
|
||||
m_liquidInteractions.add(make_tuple(targetCell->position, *targetCell->liquid, selfCell->position, *selfCell->liquid));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::finish() {
|
||||
m_currentActiveCells.clear();
|
||||
|
||||
for (auto& workingCellPair : take(m_workingCells)) {
|
||||
if (workingCellPair.second && !workingCellPair.second->sourceCell) {
|
||||
if (workingCellPair.second->liquid) {
|
||||
if (workingCellPair.second->level < m_engineParameters.minimumLiquidLevel)
|
||||
workingCellPair.second->level = 0.0f;
|
||||
} else {
|
||||
workingCellPair.second->level = 0.0f;
|
||||
}
|
||||
|
||||
if (workingCellPair.second->level == 0.0f) {
|
||||
workingCellPair.second->liquid = {};
|
||||
workingCellPair.second->pressure = 0.0f;
|
||||
}
|
||||
|
||||
m_cellWorld->setFlow(workingCellPair.second->position, CellularLiquidFlowCell<LiquidId>{
|
||||
workingCellPair.second->liquid, workingCellPair.second->level, workingCellPair.second->pressure});
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& interaction : take(m_liquidInteractions))
|
||||
m_cellWorld->liquidInteraction(get<0>(interaction), get<1>(interaction), get<2>(interaction), get<3>(interaction));
|
||||
|
||||
for (auto const& interaction : take(m_liquidCollisions))
|
||||
m_cellWorld->liquidCollision(get<0>(interaction), get<1>(interaction), get<2>(interaction));
|
||||
|
||||
for (auto const& c : take(m_nextActiveCells)) {
|
||||
auto visit = [this](Vec2I p) {
|
||||
p = m_cellWorld->uniqueLocation(p);
|
||||
auto cell = workingCell(p);
|
||||
if (cell && cell->liquid)
|
||||
m_activeCells[*cell->liquid].add(p);
|
||||
};
|
||||
|
||||
visit(c);
|
||||
visit(c + Vec2I(-1, 0));
|
||||
visit(c + Vec2I(1, 0));
|
||||
visit(c + Vec2I(0, -1));
|
||||
visit(c + Vec2I(0, 1));
|
||||
}
|
||||
|
||||
eraseWhere(m_activeCells, [](auto const& p) {
|
||||
return p.second.empty();
|
||||
});
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
typename LiquidCellEngine<LiquidId>::WorkingCell* LiquidCellEngine<LiquidId>::workingCell(Vec2I p) {
|
||||
p = m_cellWorld->uniqueLocation(p);
|
||||
|
||||
auto res = m_workingCells.insert(make_pair(p, Maybe<WorkingCell>()));
|
||||
if (res.second) {
|
||||
auto cellData = m_cellWorld->cell(p);
|
||||
if (auto flowCell = cellData.template ptr<CellularLiquidFlowCell<LiquidId>>())
|
||||
res.first->second = WorkingCell{p, flowCell->liquid, false, flowCell->level, flowCell->pressure, nullptr, nullptr, nullptr, nullptr};
|
||||
else if (auto sourceCell = cellData.template ptr<CellularLiquidSourceCell<LiquidId>>())
|
||||
res.first->second = WorkingCell{p, sourceCell->liquid, true, 1.0f, sourceCell->pressure, nullptr, nullptr, nullptr, nullptr};
|
||||
}
|
||||
return res.first->second.ptr();
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
typename LiquidCellEngine<LiquidId>::WorkingCell* LiquidCellEngine<LiquidId>::adjacentCell(
|
||||
WorkingCell* cell, Adjacency adjacency) {
|
||||
auto getCell = [this](WorkingCell*& cellptr, Vec2I cellPos) {
|
||||
if (cellptr)
|
||||
return cellptr;
|
||||
cellptr = workingCell(cellPos);
|
||||
return cellptr;
|
||||
};
|
||||
|
||||
if (adjacency == Adjacency::Left)
|
||||
return getCell(cell->leftCell, cell->position + Vec2I(-1, 0));
|
||||
else if (adjacency == Adjacency::Right)
|
||||
return getCell(cell->rightCell, cell->position + Vec2I(1, 0));
|
||||
else if (adjacency == Adjacency::Bottom)
|
||||
return getCell(cell->bottomCell, cell->position + Vec2I(0, -1));
|
||||
else if (adjacency == Adjacency::Top)
|
||||
return getCell(cell->topCell, cell->position + Vec2I(0, 1));
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::setPressure(float pressure, WorkingCell& cell) {
|
||||
if (!cell.liquid || cell.sourceCell)
|
||||
return;
|
||||
|
||||
if (fabs(cell.pressure - pressure) > m_engineParameters.minimumLivenPressureChange)
|
||||
m_nextActiveCells.add(cell.position);
|
||||
cell.pressure = pressure;
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::transferPressure(float amount, WorkingCell& source, WorkingCell& dest, bool allowReverse) {
|
||||
if (amount < 0.0f && allowReverse) {
|
||||
return transferPressure(-amount, dest, source, false);
|
||||
} else if (amount > 0.0f) {
|
||||
if (!source.liquid)
|
||||
return;
|
||||
|
||||
if (source.sourceCell && dest.sourceCell)
|
||||
return;
|
||||
|
||||
if (dest.liquid && dest.liquid != source.liquid)
|
||||
return;
|
||||
|
||||
amount = min(amount, source.pressure);
|
||||
|
||||
if (!source.sourceCell)
|
||||
source.pressure -= amount;
|
||||
|
||||
if (dest.liquid && !dest.sourceCell)
|
||||
dest.pressure += amount;
|
||||
|
||||
if (amount > m_engineParameters.minimumLivenPressureChange) {
|
||||
m_nextActiveCells.add(source.position);
|
||||
m_nextActiveCells.add(dest.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::setLevel(float level, WorkingCell& cell) {
|
||||
if (!cell.liquid || cell.sourceCell)
|
||||
return;
|
||||
|
||||
if (fabs(cell.level - level) > m_engineParameters.minimumLivenLevelChange)
|
||||
m_nextActiveCells.add(cell.position);
|
||||
|
||||
cell.level = level;
|
||||
|
||||
if (cell.level <= 0.0f) {
|
||||
cell.liquid = {};
|
||||
cell.level = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LiquidId>
|
||||
void LiquidCellEngine<LiquidId>::transferLevel(
|
||||
float amount, WorkingCell& source, WorkingCell& dest, bool allowReverse) {
|
||||
if (amount < 0.0f && allowReverse) {
|
||||
transferLevel(-amount, dest, source, false);
|
||||
|
||||
} else if (amount > 0.0f) {
|
||||
if (!source.liquid)
|
||||
return;
|
||||
|
||||
if (source.sourceCell && dest.sourceCell)
|
||||
return;
|
||||
|
||||
if (dest.liquid && dest.liquid != source.liquid)
|
||||
return;
|
||||
|
||||
amount = min(amount, source.level);
|
||||
if (!source.sourceCell)
|
||||
source.level -= amount;
|
||||
|
||||
if (!dest.sourceCell) {
|
||||
dest.level += amount;
|
||||
dest.liquid = source.liquid;
|
||||
}
|
||||
|
||||
if (!source.sourceCell && source.level == 0.0f)
|
||||
source.liquid = {};
|
||||
|
||||
if (amount > m_engineParameters.minimumLivenLevelChange) {
|
||||
m_nextActiveCells.add(source.position);
|
||||
m_nextActiveCells.add(dest.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
54
source/base/StarConfiguration.cpp
Normal file
54
source/base/StarConfiguration.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#include "StarConfiguration.hpp"
|
||||
#include "StarFile.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
Configuration::Configuration(Json defaultConfiguration, Json currentConfiguration)
|
||||
: m_defaultConfig(defaultConfiguration), m_currentConfig(currentConfiguration) {}
|
||||
|
||||
Json Configuration::defaultConfiguration() const {
|
||||
return m_defaultConfig;
|
||||
}
|
||||
|
||||
Json Configuration::currentConfiguration() const {
|
||||
return m_currentConfig;
|
||||
}
|
||||
|
||||
Json Configuration::get(String const& key) const {
|
||||
MutexLocker locker(m_mutex);
|
||||
return m_currentConfig.get(key, {});
|
||||
}
|
||||
|
||||
Json Configuration::getPath(String const& path) const {
|
||||
MutexLocker locker(m_mutex);
|
||||
return m_currentConfig.query(path, {});
|
||||
}
|
||||
|
||||
Json Configuration::getDefault(String const& key) const {
|
||||
MutexLocker locker(m_mutex);
|
||||
return m_defaultConfig.get(key, {});
|
||||
}
|
||||
|
||||
Json Configuration::getDefaultPath(String const& path) const {
|
||||
MutexLocker locker(m_mutex);
|
||||
return m_defaultConfig.query(path, {});
|
||||
}
|
||||
|
||||
void Configuration::set(String const& key, Json const& value) {
|
||||
MutexLocker locker(m_mutex);
|
||||
if (key == "configurationVersion")
|
||||
throw ConfigurationException("cannot set configurationVersion");
|
||||
|
||||
m_currentConfig = m_currentConfig.set(key, value);
|
||||
}
|
||||
|
||||
void Configuration::setPath(String const& path, Json const& value) {
|
||||
MutexLocker locker(m_mutex);
|
||||
if (path.splitAny("[].").get(0) == "configurationVersion")
|
||||
throw ConfigurationException("cannot set configurationVersion");
|
||||
|
||||
m_currentConfig = m_currentConfig.setPath(path, value);
|
||||
}
|
||||
|
||||
}
|
||||
39
source/base/StarConfiguration.hpp
Normal file
39
source/base/StarConfiguration.hpp
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#ifndef STAR_CONFIGURATION_HPP
|
||||
#define STAR_CONFIGURATION_HPP
|
||||
|
||||
#include "StarJson.hpp"
|
||||
#include "StarThread.hpp"
|
||||
#include "StarVersion.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(Configuration);
|
||||
|
||||
STAR_EXCEPTION(ConfigurationException, StarException);
|
||||
|
||||
class Configuration {
|
||||
public:
|
||||
Configuration(Json defaultConfiguration, Json currentConfiguration);
|
||||
|
||||
Json defaultConfiguration() const;
|
||||
Json currentConfiguration() const;
|
||||
|
||||
Json get(String const& key) const;
|
||||
Json getPath(String const& path) const;
|
||||
|
||||
Json getDefault(String const& key) const;
|
||||
Json getDefaultPath(String const& path) const;
|
||||
|
||||
void set(String const& key, Json const& value);
|
||||
void setPath(String const& path, Json const& value);
|
||||
|
||||
private:
|
||||
mutable Mutex m_mutex;
|
||||
|
||||
Json m_defaultConfig;
|
||||
Json m_currentConfig;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
96
source/base/StarDirectoryAssetSource.cpp
Normal file
96
source/base/StarDirectoryAssetSource.cpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#include "StarDirectoryAssetSource.hpp"
|
||||
#include "StarFile.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
DirectoryAssetSource::DirectoryAssetSource(String const& baseDirectory, StringList const& ignorePatterns) {
|
||||
m_baseDirectory = baseDirectory;
|
||||
m_ignorePatterns = ignorePatterns;
|
||||
|
||||
// Load metadata from either /_metadata or /.metadata, in that order.
|
||||
for (auto fileName : {"/_metadata", "/.metadata"}) {
|
||||
String metadataFile = toFilesystem(fileName);
|
||||
if (File::isFile(metadataFile)) {
|
||||
try {
|
||||
m_metadataFile = String(fileName);
|
||||
m_metadata = Json::parseJson(File::readFileString(metadataFile)).toObject();
|
||||
break;
|
||||
} catch (JsonException const& e) {
|
||||
throw AssetSourceException(strf("Could not load metadata file '%s' from assets", metadataFile), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't scan metadata files
|
||||
m_ignorePatterns.append("^/_metadata$");
|
||||
m_ignorePatterns.append("^/\\.metadata$");
|
||||
|
||||
scanAll("/", m_assetPaths);
|
||||
|
||||
m_assetPaths.sort();
|
||||
}
|
||||
|
||||
JsonObject DirectoryAssetSource::metadata() const {
|
||||
return m_metadata;
|
||||
}
|
||||
|
||||
StringList DirectoryAssetSource::assetPaths() const {
|
||||
return m_assetPaths;
|
||||
}
|
||||
|
||||
IODevicePtr DirectoryAssetSource::open(String const& path) {
|
||||
auto file = make_shared<File>(toFilesystem(path));
|
||||
file->open(IOMode::Read);
|
||||
return file;
|
||||
}
|
||||
|
||||
ByteArray DirectoryAssetSource::read(String const& path) {
|
||||
auto device = open(path);
|
||||
return device->readBytes(device->size());
|
||||
}
|
||||
|
||||
String DirectoryAssetSource::toFilesystem(String const& path) const {
|
||||
if (!path.beginsWith("/"))
|
||||
throw AssetSourceException::format("Asset path '%s' must be absolute in DirectoryAssetSource::toFilesystem", path);
|
||||
else
|
||||
return File::relativeTo(m_baseDirectory, File::convertDirSeparators(path.substr(1)));
|
||||
}
|
||||
|
||||
void DirectoryAssetSource::setMetadata(JsonObject metadata) {
|
||||
if (metadata != m_metadata) {
|
||||
if (!m_metadataFile)
|
||||
m_metadataFile = String("/_metadata");
|
||||
|
||||
m_metadata = move(metadata);
|
||||
|
||||
if (m_metadata.empty())
|
||||
File::remove(toFilesystem(*m_metadataFile));
|
||||
else
|
||||
File::writeFile(Json(m_metadata).printJson(2, true), toFilesystem(*m_metadataFile));
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryAssetSource::scanAll(String const& assetDirectory, StringList& output) const {
|
||||
auto shouldIgnore = [this](String const& assetPath) {
|
||||
for (auto const& pattern : m_ignorePatterns) {
|
||||
if (assetPath.regexMatch(pattern, false, false))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// path must be passed in including the trailing '/'
|
||||
String fsDirectory = toFilesystem(assetDirectory);
|
||||
for (auto entry : File::dirList(fsDirectory)) {
|
||||
String assetPath = assetDirectory + entry.first;
|
||||
if (entry.second) {
|
||||
scanAll(assetPath + "/", output);
|
||||
} else {
|
||||
if (!shouldIgnore(assetPath))
|
||||
output.append(move(assetPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
41
source/base/StarDirectoryAssetSource.hpp
Normal file
41
source/base/StarDirectoryAssetSource.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef STAR_DIRECTORY_ASSET_SOURCE_HPP
|
||||
#define STAR_DIRECTORY_ASSET_SOURCE_HPP
|
||||
|
||||
#include "StarAssetSource.hpp"
|
||||
#include "StarString.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(DirectoryAssetSource);
|
||||
|
||||
class DirectoryAssetSource : public AssetSource {
|
||||
public:
|
||||
// Any file that forms an asset path that matches any of the patterns in
|
||||
// 'ignorePatterns' is ignored.
|
||||
DirectoryAssetSource(String const& baseDirectory, StringList const& ignorePatterns = {});
|
||||
|
||||
JsonObject metadata() const override;
|
||||
StringList assetPaths() const override;
|
||||
|
||||
IODevicePtr open(String const& path) override;
|
||||
ByteArray read(String const& path) override;
|
||||
|
||||
// Converts an asset path to the path on the filesystem
|
||||
String toFilesystem(String const& path) const;
|
||||
|
||||
// Update metadata file or add a new one.
|
||||
void setMetadata(JsonObject metadata);
|
||||
|
||||
private:
|
||||
void scanAll(String const& assetDirectory, StringList& output) const;
|
||||
|
||||
String m_baseDirectory;
|
||||
List<String> m_ignorePatterns;
|
||||
Maybe<String> m_metadataFile;
|
||||
JsonObject m_metadata;
|
||||
StringList m_assetPaths;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
486
source/base/StarMixer.cpp
Normal file
486
source/base/StarMixer.cpp
Normal file
|
|
@ -0,0 +1,486 @@
|
|||
#include "StarMixer.hpp"
|
||||
#include "StarIterator.hpp"
|
||||
#include "StarInterpolation.hpp"
|
||||
#include "StarTime.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
namespace {
|
||||
float rateOfChangeFromRampTime(float rampTime) {
|
||||
static const float MaxRate = 10000.0f;
|
||||
|
||||
if (rampTime < 1.0f / MaxRate)
|
||||
return MaxRate;
|
||||
else
|
||||
return 1.0f / rampTime;
|
||||
}
|
||||
}
|
||||
|
||||
AudioInstance::AudioInstance(Audio const& audio)
|
||||
: m_audio(audio) {
|
||||
m_mixerGroup = MixerGroup::Effects;
|
||||
|
||||
m_volume = {1.0f, 1.0f, 0};
|
||||
|
||||
m_pitchMultiplier = 1.0f;
|
||||
m_pitchMultiplierTarget = 1.0f;
|
||||
m_pitchMultiplierVelocity = 0;
|
||||
|
||||
m_loops = 0;
|
||||
m_stopping = false;
|
||||
m_finished = false;
|
||||
|
||||
m_rangeMultiplier = 1.0f;
|
||||
|
||||
m_clockStopFadeOut = 0.0f;
|
||||
}
|
||||
|
||||
Maybe<Vec2F> AudioInstance::position() const {
|
||||
MutexLocker locker(m_mutex);
|
||||
return m_position;
|
||||
}
|
||||
|
||||
void AudioInstance::setPosition(Maybe<Vec2F> position) {
|
||||
MutexLocker locker(m_mutex);
|
||||
m_position = position;
|
||||
}
|
||||
|
||||
void AudioInstance::translate(Vec2F const& distance) {
|
||||
MutexLocker locker(m_mutex);
|
||||
if (m_position)
|
||||
*m_position += distance;
|
||||
else
|
||||
m_position = distance;
|
||||
}
|
||||
|
||||
float AudioInstance::rangeMultiplier() const {
|
||||
MutexLocker locker(m_mutex);
|
||||
return m_rangeMultiplier;
|
||||
}
|
||||
|
||||
void AudioInstance::setRangeMultiplier(float rangeMultiplier) {
|
||||
MutexLocker locker(m_mutex);
|
||||
m_rangeMultiplier = rangeMultiplier;
|
||||
}
|
||||
|
||||
void AudioInstance::setVolume(float targetValue, float rampTime) {
|
||||
starAssert(targetValue >= 0);
|
||||
MutexLocker locker(m_mutex);
|
||||
|
||||
if (m_stopping)
|
||||
return;
|
||||
|
||||
if (rampTime <= 0.0f) {
|
||||
m_volume.value = targetValue;
|
||||
m_volume.target = targetValue;
|
||||
m_volume.velocity = 0.0f;
|
||||
} else {
|
||||
m_volume.target = targetValue;
|
||||
m_volume.velocity = rateOfChangeFromRampTime(rampTime);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioInstance::setPitchMultiplier(float targetValue, float rampTime) {
|
||||
starAssert(targetValue >= 0);
|
||||
MutexLocker locker(m_mutex);
|
||||
|
||||
if (m_stopping)
|
||||
return;
|
||||
|
||||
if (rampTime <= 0.0f) {
|
||||
m_pitchMultiplier = targetValue;
|
||||
m_pitchMultiplierTarget = targetValue;
|
||||
m_pitchMultiplierVelocity = 0.0f;
|
||||
} else {
|
||||
m_pitchMultiplierTarget = targetValue;
|
||||
m_pitchMultiplierVelocity = rateOfChangeFromRampTime(rampTime);
|
||||
}
|
||||
}
|
||||
|
||||
int AudioInstance::loops() const {
|
||||
MutexLocker locker(m_mutex);
|
||||
return m_loops;
|
||||
}
|
||||
|
||||
void AudioInstance::setLoops(int loops) {
|
||||
MutexLocker locker(m_mutex);
|
||||
m_loops = loops;
|
||||
}
|
||||
|
||||
double AudioInstance::currentTime() const {
|
||||
return m_audio.currentTime();
|
||||
}
|
||||
|
||||
double AudioInstance::totalTime() const {
|
||||
return m_audio.totalTime();
|
||||
}
|
||||
|
||||
void AudioInstance::seekTime(double time) {
|
||||
m_audio.seekTime(time);
|
||||
}
|
||||
|
||||
MixerGroup AudioInstance::mixerGroup() const {
|
||||
MutexLocker locker(m_mutex);
|
||||
return m_mixerGroup;
|
||||
}
|
||||
|
||||
void AudioInstance::setMixerGroup(MixerGroup mixerGroup) {
|
||||
MutexLocker locker(m_mutex);
|
||||
m_mixerGroup = mixerGroup;
|
||||
}
|
||||
|
||||
void AudioInstance::setClockStart(Maybe<int64_t> clockStartTime) {
|
||||
MutexLocker locker(m_mutex);
|
||||
m_clockStart = clockStartTime;
|
||||
}
|
||||
|
||||
void AudioInstance::setClockStop(Maybe<int64_t> clockStopTime, int64_t fadeOutTime) {
|
||||
MutexLocker locker(m_mutex);
|
||||
m_clockStop = clockStopTime;
|
||||
m_clockStopFadeOut = fadeOutTime;
|
||||
}
|
||||
|
||||
void AudioInstance::stop(float rampTime) {
|
||||
MutexLocker locker(m_mutex);
|
||||
|
||||
if (rampTime <= 0.0f) {
|
||||
m_volume.value = 0.0f;
|
||||
m_volume.target = 0.0f;
|
||||
m_volume.velocity = 0.0f;
|
||||
|
||||
m_pitchMultiplierTarget = 0.0f;
|
||||
m_pitchMultiplierVelocity = 0.0f;
|
||||
} else {
|
||||
m_volume.target = 0.0f;
|
||||
m_volume.velocity = rateOfChangeFromRampTime(rampTime);
|
||||
}
|
||||
|
||||
m_stopping = true;
|
||||
}
|
||||
|
||||
bool AudioInstance::finished() const {
|
||||
return m_finished;
|
||||
}
|
||||
|
||||
Mixer::Mixer(unsigned sampleRate, unsigned channels) {
|
||||
m_sampleRate = sampleRate;
|
||||
m_channels = channels;
|
||||
|
||||
m_volume = {1.0f, 1.0f, 0};
|
||||
|
||||
m_groupVolumes[MixerGroup::Effects] = {1.0f, 1.0f, 0};
|
||||
m_groupVolumes[MixerGroup::Music] = {1.0f, 1.0f, 0};
|
||||
m_groupVolumes[MixerGroup::Cinematic] = {1.0f, 1.0f, 0};
|
||||
}
|
||||
|
||||
unsigned Mixer::sampleRate() const {
|
||||
return m_sampleRate;
|
||||
}
|
||||
|
||||
unsigned Mixer::channels() const {
|
||||
return m_channels;
|
||||
}
|
||||
|
||||
void Mixer::addEffect(String const& effectName, EffectFunction effectFunction, float rampTime) {
|
||||
MutexLocker locker(m_effectsMutex);
|
||||
m_effects[effectName] = make_shared<EffectInfo>(EffectInfo{effectFunction, 0.0f, rateOfChangeFromRampTime(rampTime), false});
|
||||
}
|
||||
|
||||
void Mixer::removeEffect(String const& effectName, float rampTime) {
|
||||
MutexLocker locker(m_effectsMutex);
|
||||
if (m_effects.contains(effectName))
|
||||
m_effects[effectName]->velocity = -rateOfChangeFromRampTime(rampTime);
|
||||
}
|
||||
|
||||
StringList Mixer::currentEffects() {
|
||||
MutexLocker locker(m_effectsMutex);
|
||||
return m_effects.keys();
|
||||
}
|
||||
|
||||
bool Mixer::hasEffect(String const& effectName) {
|
||||
MutexLocker locker(m_effectsMutex);
|
||||
return m_effects.contains(effectName);
|
||||
}
|
||||
|
||||
void Mixer::setVolume(float volume, float rampTime) {
|
||||
MutexLocker locker(m_mutex);
|
||||
m_volume.target = volume;
|
||||
m_volume.velocity = rateOfChangeFromRampTime(rampTime);
|
||||
}
|
||||
|
||||
void Mixer::play(AudioInstancePtr sample) {
|
||||
MutexLocker locker(m_queueMutex);
|
||||
m_audios.add(move(sample), AudioState{List<float>(m_channels, 1.0f)});
|
||||
}
|
||||
|
||||
void Mixer::stopAll(float rampTime) {
|
||||
MutexLocker locker(m_queueMutex);
|
||||
float vel = rateOfChangeFromRampTime(rampTime);
|
||||
for (auto const& p : m_audios)
|
||||
p.first->stop(vel);
|
||||
}
|
||||
|
||||
void Mixer::read(int16_t* outBuffer, size_t frameCount) {
|
||||
// Make this method as least locky as possible by copying all the needed
|
||||
// member data before the expensive audio / effect stuff.
|
||||
unsigned sampleRate;
|
||||
unsigned channels;
|
||||
float volume;
|
||||
float volumeVelocity;
|
||||
float targetVolume;
|
||||
Map<MixerGroup, RampedValue> groupVolumes;
|
||||
|
||||
{
|
||||
MutexLocker locker(m_mutex);
|
||||
sampleRate = m_sampleRate;
|
||||
channels = m_channels;
|
||||
volume = m_volume.value;
|
||||
volumeVelocity = m_volume.velocity;
|
||||
targetVolume = m_volume.target;
|
||||
groupVolumes = m_groupVolumes;
|
||||
}
|
||||
|
||||
size_t bufferSize = frameCount * m_channels;
|
||||
m_mixBuffer.resize(bufferSize, 0);
|
||||
|
||||
float time = (float)frameCount / sampleRate;
|
||||
float beginVolume = volume;
|
||||
float endVolume = approach(targetVolume, volume, volumeVelocity * time);
|
||||
|
||||
Map<MixerGroup, float> groupEndVolumes;
|
||||
for (auto p : groupVolumes)
|
||||
groupEndVolumes[p.first] = approach(p.second.target, p.second.value, p.second.velocity * time);
|
||||
|
||||
auto sampleStartTime = Time::millisecondsSinceEpoch();
|
||||
unsigned millisecondsInBuffer = (bufferSize * 1000) / (channels * sampleRate);
|
||||
auto sampleEndTime = sampleStartTime + millisecondsInBuffer;
|
||||
|
||||
for (size_t i = 0; i < bufferSize; ++i)
|
||||
outBuffer[i] = 0;
|
||||
|
||||
{
|
||||
MutexLocker locker(m_queueMutex);
|
||||
// Mix all active sounds
|
||||
for (auto& p : m_audios) {
|
||||
auto& audioInstance = p.first;
|
||||
auto& audioState = p.second;
|
||||
|
||||
MutexLocker audioLocker(audioInstance->m_mutex);
|
||||
|
||||
if (audioInstance->m_finished)
|
||||
continue;
|
||||
|
||||
if (audioInstance->m_clockStart && *audioInstance->m_clockStart > sampleEndTime)
|
||||
continue;
|
||||
|
||||
float groupVolume = groupVolumes[audioInstance->m_mixerGroup].value;
|
||||
float groupEndVolume = groupEndVolumes[audioInstance->m_mixerGroup];
|
||||
|
||||
bool finished = false;
|
||||
|
||||
float audioStopVolBegin = audioInstance->m_volume.value;
|
||||
float audioStopVolEnd = (audioInstance->m_volume.velocity > 0)
|
||||
? approach(audioInstance->m_volume.target, audioStopVolBegin, audioInstance->m_volume.velocity * time)
|
||||
: audioInstance->m_volume.value;
|
||||
|
||||
float pitchMultiplier = (audioInstance->m_pitchMultiplierVelocity > 0)
|
||||
? approach(audioInstance->m_pitchMultiplierTarget, audioInstance->m_pitchMultiplier, audioInstance->m_pitchMultiplierVelocity * time)
|
||||
: audioInstance->m_pitchMultiplier;
|
||||
|
||||
if (audioStopVolEnd == 0.0f && audioInstance->m_stopping)
|
||||
finished = true;
|
||||
|
||||
size_t ramt = 0;
|
||||
if (audioInstance->m_clockStart && *audioInstance->m_clockStart > sampleStartTime) {
|
||||
int silentSamples = (*audioInstance->m_clockStart - sampleStartTime) * sampleRate / 1000;
|
||||
for (unsigned i = 0; i < silentSamples * channels; ++i)
|
||||
m_mixBuffer[i] = 0;
|
||||
ramt += silentSamples * channels;
|
||||
}
|
||||
ramt += audioInstance->m_audio.resample(channels, sampleRate, m_mixBuffer.ptr() + ramt, bufferSize - ramt, pitchMultiplier);
|
||||
while (ramt != bufferSize && !finished) {
|
||||
// Only seek back to the beginning and read more data if loops is < 0
|
||||
// (loop forever), or we have more loops to go, otherwise, the sample is
|
||||
// finished.
|
||||
if (audioInstance->m_loops != 0) {
|
||||
audioInstance->m_audio.seekSample(0);
|
||||
ramt += audioInstance->m_audio.resample(channels, sampleRate, m_mixBuffer.ptr() + ramt, bufferSize - ramt, pitchMultiplier);
|
||||
if (audioInstance->m_loops > 0)
|
||||
--audioInstance->m_loops;
|
||||
} else {
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
if (audioInstance->m_clockStop && *audioInstance->m_clockStop < sampleEndTime) {
|
||||
for (size_t s = 0; s < ramt / channels; ++s) {
|
||||
unsigned millisecondsInBuffer = (s * 1000) / sampleRate;
|
||||
auto sampleTime = sampleStartTime + millisecondsInBuffer;
|
||||
if (sampleTime > *audioInstance->m_clockStop) {
|
||||
float volume = 0.0f;
|
||||
if (audioInstance->m_clockStopFadeOut > 0)
|
||||
volume = 1.0f - (float)(sampleTime - *audioInstance->m_clockStop) / (float)audioInstance->m_clockStopFadeOut;
|
||||
|
||||
if (volume <= 0) {
|
||||
for (size_t c = 0; c < channels; ++c)
|
||||
m_mixBuffer[s * channels + c] = 0;
|
||||
} else {
|
||||
for (size_t c = 0; c < channels; ++c)
|
||||
m_mixBuffer[s * channels + c] = m_mixBuffer[s * channels + c] * volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sampleEndTime > *audioInstance->m_clockStop + audioInstance->m_clockStopFadeOut)
|
||||
finished = true;
|
||||
}
|
||||
|
||||
for (size_t s = 0; s < ramt / channels; ++s) {
|
||||
float vol = lerp((float)s / frameCount, beginVolume * groupVolume * audioStopVolBegin, endVolume * groupEndVolume * audioStopVolEnd);
|
||||
for (size_t c = 0; c < channels; ++c) {
|
||||
float sample = m_mixBuffer[s * channels + c] * vol * audioState.positionalChannelVolumes[c] * audioInstance->m_volume.value;
|
||||
outBuffer[s * channels + c] = clamp(sample + outBuffer[s * channels + c], -32767.0f, 32767.0f);
|
||||
}
|
||||
}
|
||||
|
||||
audioInstance->m_volume.value = audioStopVolEnd;
|
||||
audioInstance->m_finished = finished;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
MutexLocker locker(m_effectsMutex);
|
||||
// Apply all active effects
|
||||
for (auto const& pair : m_effects) {
|
||||
auto const& effectInfo = pair.second;
|
||||
if (effectInfo->finished)
|
||||
continue;
|
||||
|
||||
float effectBegin = effectInfo->amount;
|
||||
float effectEnd;
|
||||
if (effectInfo->velocity < 0)
|
||||
effectEnd = approach(0.0f, effectBegin, -effectInfo->velocity * time);
|
||||
else
|
||||
effectEnd = approach(1.0f, effectBegin, effectInfo->velocity * time);
|
||||
|
||||
std::copy(outBuffer, outBuffer + bufferSize, m_mixBuffer.begin());
|
||||
effectInfo->effectFunction(m_mixBuffer.ptr(), frameCount, channels);
|
||||
|
||||
for (size_t s = 0; s < frameCount; ++s) {
|
||||
float amt = lerp((float)s / frameCount, effectBegin, effectEnd);
|
||||
for (size_t c = 0; c < channels; ++c) {
|
||||
int16_t prev = outBuffer[s * channels + c];
|
||||
outBuffer[s * channels + c] = lerp(amt, prev, m_mixBuffer[s * channels + c]);
|
||||
}
|
||||
}
|
||||
|
||||
effectInfo->amount = effectEnd;
|
||||
effectInfo->finished = effectInfo->amount <= 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
MutexLocker locker(m_mutex);
|
||||
|
||||
m_volume.value = endVolume;
|
||||
|
||||
for (auto p : groupEndVolumes)
|
||||
m_groupVolumes[p.first].value = p.second;
|
||||
}
|
||||
}
|
||||
|
||||
Mixer::EffectFunction Mixer::lowpass(size_t avgSize) const {
|
||||
struct LowPass {
|
||||
LowPass(size_t avgSize) : avgSize(avgSize) {}
|
||||
|
||||
size_t avgSize;
|
||||
List<Deque<float>> filter;
|
||||
|
||||
void operator()(int16_t* buffer, size_t frames, unsigned channels) {
|
||||
filter.resize(channels);
|
||||
for (size_t f = 0; f < frames; ++f) {
|
||||
for (size_t c = 0; c < channels; ++c) {
|
||||
auto& filterChannel = filter[c];
|
||||
filterChannel.append(buffer[f * channels + c] / 32767.0f);
|
||||
while (filterChannel.size() > avgSize)
|
||||
filterChannel.takeFirst();
|
||||
buffer[f * channels + c] = sum(filterChannel) / (float)avgSize * 32767.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return LowPass(avgSize);
|
||||
}
|
||||
|
||||
Mixer::EffectFunction Mixer::echo(float time, float dry, float wet) const {
|
||||
struct Echo {
|
||||
unsigned echoLength;
|
||||
float dry;
|
||||
float wet;
|
||||
List<Deque<float>> filter;
|
||||
|
||||
void operator()(int16_t* buffer, size_t frames, unsigned channels) {
|
||||
if (echoLength == 0)
|
||||
return;
|
||||
|
||||
filter.resize(channels);
|
||||
for (size_t c = 0; c < channels; ++c) {
|
||||
auto& filterChannel = filter[c];
|
||||
if (filterChannel.empty())
|
||||
filterChannel.resize(echoLength, 0);
|
||||
}
|
||||
|
||||
for (size_t f = 0; f < frames; ++f) {
|
||||
for (size_t c = 0; c < channels; ++c) {
|
||||
auto& filterChannel = filter[c];
|
||||
buffer[f * channels + c] = buffer[f * channels + c] * dry + filter[c][0] * wet;
|
||||
filterChannel.append(buffer[f * channels + c]);
|
||||
while (filterChannel.size() > echoLength)
|
||||
filterChannel.takeFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return Echo{(unsigned)(time * m_sampleRate), dry, wet, {}};
|
||||
}
|
||||
|
||||
void Mixer::setGroupVolume(MixerGroup group, float targetValue, float rampTime) {
|
||||
MutexLocker locker(m_mutex);
|
||||
if (rampTime <= 0.0f) {
|
||||
m_groupVolumes[group].value = targetValue;
|
||||
m_groupVolumes[group].target = targetValue;
|
||||
m_groupVolumes[group].velocity = 0.0f;
|
||||
} else {
|
||||
m_groupVolumes[group].target = targetValue;
|
||||
m_groupVolumes[group].velocity = rateOfChangeFromRampTime(rampTime);
|
||||
}
|
||||
}
|
||||
|
||||
void Mixer::update(PositionalAttenuationFunction positionalAttenuationFunction) {
|
||||
{
|
||||
MutexLocker locker(m_queueMutex);
|
||||
eraseWhere(m_audios, [&](auto& p) {
|
||||
if (p.first->m_finished)
|
||||
return true;
|
||||
|
||||
if (positionalAttenuationFunction && p.first->m_position) {
|
||||
for (unsigned c = 0; c < m_channels; ++c)
|
||||
p.second.positionalChannelVolumes[c] = 1.0f - positionalAttenuationFunction(c, *p.first->m_position, p.first->m_rangeMultiplier);
|
||||
} else {
|
||||
for (unsigned c = 0; c < m_channels; ++c)
|
||||
p.second.positionalChannelVolumes[c] = 1.0f;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
MutexLocker locker(m_effectsMutex);
|
||||
eraseWhere(m_effects, [](auto const& p) {
|
||||
return p.second->finished;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
167
source/base/StarMixer.hpp
Normal file
167
source/base/StarMixer.hpp
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
#ifndef STAR_MIXER_HPP
|
||||
#define STAR_MIXER_HPP
|
||||
|
||||
#include "StarAudio.hpp"
|
||||
#include "StarThread.hpp"
|
||||
#include "StarList.hpp"
|
||||
#include "StarMap.hpp"
|
||||
#include "StarSet.hpp"
|
||||
#include "StarVector.hpp"
|
||||
#include "StarMaybe.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(AudioInstance);
|
||||
STAR_CLASS(Mixer);
|
||||
|
||||
struct RampedValue {
|
||||
float value;
|
||||
float target;
|
||||
float velocity;
|
||||
};
|
||||
|
||||
enum class MixerGroup : uint8_t {
|
||||
Effects,
|
||||
Music,
|
||||
Cinematic
|
||||
};
|
||||
|
||||
class AudioInstance {
|
||||
public:
|
||||
AudioInstance(Audio const& audio);
|
||||
|
||||
Maybe<Vec2F> position() const;
|
||||
void setPosition(Maybe<Vec2F> position);
|
||||
// If the audio has no position, sets the position to zero before translating
|
||||
void translate(Vec2F const& distance);
|
||||
|
||||
float rangeMultiplier() const;
|
||||
void setRangeMultiplier(float rangeMultiplier);
|
||||
|
||||
void setVolume(float targetValue, float rampTime = 0.0f);
|
||||
void setPitchMultiplier(float targetValue, float rampTime = 0.0f);
|
||||
|
||||
// Returns the currently remaining loops
|
||||
int loops() const;
|
||||
// Sets the remaining loops, set to 0 to stop looping
|
||||
void setLoops(int loops);
|
||||
|
||||
// Returns the current audio playing time position
|
||||
double currentTime() const;
|
||||
// Total length of time of the audio in seconds
|
||||
double totalTime() const;
|
||||
// Seeks the audio to the current time in seconds
|
||||
void seekTime(double time);
|
||||
|
||||
// The MixerGroup defaults to Effects
|
||||
MixerGroup mixerGroup() const;
|
||||
void setMixerGroup(MixerGroup mixerGroup);
|
||||
|
||||
// If set, uses wall clock time in milliseconds to set precise start and stop
|
||||
// times for the AudioInstance
|
||||
void setClockStart(Maybe<int64_t> clockStartTime);
|
||||
void setClockStop(Maybe<int64_t> clockStopTime, int64_t fadeOutTime = 0);
|
||||
|
||||
void stop(float rampTime = 0.0f);
|
||||
bool finished() const;
|
||||
|
||||
private:
|
||||
friend class Mixer;
|
||||
|
||||
mutable Mutex m_mutex;
|
||||
|
||||
Audio m_audio;
|
||||
|
||||
MixerGroup m_mixerGroup;
|
||||
|
||||
RampedValue m_volume;
|
||||
|
||||
float m_pitchMultiplier;
|
||||
float m_pitchMultiplierTarget;
|
||||
float m_pitchMultiplierVelocity;
|
||||
|
||||
int m_loops;
|
||||
bool m_stopping;
|
||||
bool m_finished;
|
||||
|
||||
Maybe<Vec2F> m_position;
|
||||
float m_rangeMultiplier;
|
||||
|
||||
Maybe<int64_t> m_clockStart;
|
||||
Maybe<int64_t> m_clockStop;
|
||||
int64_t m_clockStopFadeOut;
|
||||
};
|
||||
|
||||
// Thread safe mixer class with basic effects support.
|
||||
class Mixer {
|
||||
public:
|
||||
typedef function<void(int16_t* buffer, size_t frames, unsigned channels)> EffectFunction;
|
||||
typedef function<float(unsigned, Vec2F, float)> PositionalAttenuationFunction;
|
||||
|
||||
Mixer(unsigned sampleRate, unsigned channels);
|
||||
|
||||
unsigned sampleRate() const;
|
||||
unsigned channels() const;
|
||||
|
||||
// Construct a really crappy low-pass filter based on averaging
|
||||
EffectFunction lowpass(size_t avgSize) const;
|
||||
// Construct a very simple echo filter.
|
||||
EffectFunction echo(float time, float dry, float wet) const;
|
||||
|
||||
// Adds / removes effects that affect all playback.
|
||||
void addEffect(String const& effectName, EffectFunction effectFunction, float rampTime);
|
||||
void removeEffect(String const& effectName, float rampTime);
|
||||
StringList currentEffects();
|
||||
bool hasEffect(String const& effectName);
|
||||
|
||||
// Global volume
|
||||
void setVolume(float volume, float rampTime);
|
||||
|
||||
// per mixer group volume
|
||||
void setGroupVolume(MixerGroup group, float targetValue, float rampTime = 0.0f);
|
||||
|
||||
void play(AudioInstancePtr sample);
|
||||
|
||||
void stopAll(float rampTime);
|
||||
|
||||
// Reads pending audio data. This is thread safe with the other Mixer
|
||||
// methods, but only one call to read may be active at a time.
|
||||
void read(int16_t* samples, size_t frameCount);
|
||||
|
||||
// Call within the main loop of the program using Mixer, calculates
|
||||
// positional attenuation of audio and does cleanup.
|
||||
void update(PositionalAttenuationFunction positionalAttenuationFunction = {});
|
||||
|
||||
private:
|
||||
struct EffectInfo {
|
||||
EffectFunction effectFunction;
|
||||
float amount;
|
||||
float velocity;
|
||||
bool finished;
|
||||
};
|
||||
|
||||
struct AudioState {
|
||||
List<float> positionalChannelVolumes;
|
||||
};
|
||||
|
||||
Mutex m_mutex;
|
||||
unsigned m_sampleRate;
|
||||
unsigned m_channels;
|
||||
|
||||
RampedValue m_volume;
|
||||
|
||||
Mutex m_queueMutex;
|
||||
|
||||
HashMap<AudioInstancePtr, AudioState> m_audios;
|
||||
|
||||
Mutex m_effectsMutex;
|
||||
StringMap<shared_ptr<EffectInfo>> m_effects;
|
||||
|
||||
List<int16_t> m_mixBuffer;
|
||||
|
||||
Map<MixerGroup, RampedValue> m_groupVolumes;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
158
source/base/StarPackedAssetSource.cpp
Normal file
158
source/base/StarPackedAssetSource.cpp
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
#include "StarPackedAssetSource.hpp"
|
||||
#include "StarDirectoryAssetSource.hpp"
|
||||
#include "StarOrderedSet.hpp"
|
||||
#include "StarDataStreamDevices.hpp"
|
||||
#include "StarDataStreamExtra.hpp"
|
||||
#include "StarSha256.hpp"
|
||||
#include "StarFile.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
void PackedAssetSource::build(DirectoryAssetSource& directorySource, String const& targetPackedFile,
|
||||
StringList const& extensionSorting, BuildProgressCallback progressCallback) {
|
||||
FilePtr file = File::open(targetPackedFile, IOMode::ReadWrite | IOMode::Truncate);
|
||||
|
||||
DataStreamIODevice ds(file);
|
||||
|
||||
ds.writeData("SBAsset6", 8);
|
||||
// Skip 8 bytes, this will be a pointer to the index once we are done.
|
||||
ds.seek(8, IOSeek::Relative);
|
||||
|
||||
// Insert every found entry into the packed file, and also simultaneously
|
||||
// compute the full index.
|
||||
StringMap<pair<uint64_t, uint64_t>> index;
|
||||
|
||||
OrderedHashSet<String> extensionOrdering;
|
||||
for (auto const& str : extensionSorting)
|
||||
extensionOrdering.add(str.toLower());
|
||||
|
||||
StringList assetPaths = directorySource.assetPaths();
|
||||
|
||||
// Returns a value for the asset that can be used to predictably sort assets
|
||||
// by name and then by extension, where every extension listed in
|
||||
// "extensionSorting" will come first, and then any extension not listed will
|
||||
// come after.
|
||||
auto getOrderingValue = [&extensionOrdering](String const& asset) -> pair<size_t, String> {
|
||||
String extension;
|
||||
auto lastDot = asset.findLast(".");
|
||||
if (lastDot != NPos)
|
||||
extension = asset.substr(lastDot + 1);
|
||||
|
||||
if (auto i = extensionOrdering.indexOf(extension.toLower())) {
|
||||
return {*i, asset.toLower()};
|
||||
} else {
|
||||
return {extensionOrdering.size(), asset.toLower()};
|
||||
}
|
||||
};
|
||||
|
||||
assetPaths.sort([&getOrderingValue](String const& a, String const& b) {
|
||||
return getOrderingValue(a) < getOrderingValue(b);
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < assetPaths.size(); ++i) {
|
||||
String const& assetPath = assetPaths[i];
|
||||
ByteArray contents = directorySource.read(assetPath);
|
||||
|
||||
if (progressCallback)
|
||||
progressCallback(i, assetPaths.size(), directorySource.toFilesystem(assetPath), assetPath);
|
||||
index.add(assetPath, {ds.pos(), contents.size()});
|
||||
ds.writeBytes(contents);
|
||||
}
|
||||
|
||||
uint64_t indexStart = ds.pos();
|
||||
ds.writeData("INDEX", 5);
|
||||
ds.write(directorySource.metadata());
|
||||
ds.write(index);
|
||||
|
||||
ds.seek(8);
|
||||
ds.write(indexStart);
|
||||
}
|
||||
|
||||
PackedAssetSource::PackedAssetSource(String const& filename) {
|
||||
m_packedFile = File::open(filename, IOMode::Read);
|
||||
|
||||
DataStreamIODevice ds(m_packedFile);
|
||||
if (ds.readBytes(8) != ByteArray("SBAsset6", 8))
|
||||
throw AssetSourceException("Packed assets file format unrecognized!");
|
||||
|
||||
uint64_t indexStart = ds.read<uint64_t>();
|
||||
|
||||
ds.seek(indexStart);
|
||||
ByteArray header = ds.readBytes(5);
|
||||
if (header != ByteArray("INDEX", 5))
|
||||
throw AssetSourceException("No index header found!");
|
||||
ds.read(m_metadata);
|
||||
ds.read(m_index);
|
||||
}
|
||||
|
||||
JsonObject PackedAssetSource::metadata() const {
|
||||
return m_metadata;
|
||||
}
|
||||
|
||||
StringList PackedAssetSource::assetPaths() const {
|
||||
return m_index.keys();
|
||||
}
|
||||
|
||||
IODevicePtr PackedAssetSource::open(String const& path) {
|
||||
struct AssetReader : public IODevice {
|
||||
AssetReader(FilePtr file, StreamOffset offset, StreamOffset size)
|
||||
: file(file), fileOffset(offset), assetSize(size), assetPos(0) {
|
||||
setMode(IOMode::Read);
|
||||
}
|
||||
|
||||
size_t read(char* data, size_t len) override {
|
||||
len = min<StreamOffset>(len, assetSize - assetPos);
|
||||
file->readFullAbsolute(fileOffset + assetPos, data, len);
|
||||
assetPos += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t write(char const*, size_t) override {
|
||||
throw IOException("Assets IODevices are read-only");
|
||||
}
|
||||
|
||||
StreamOffset size() override {
|
||||
return assetSize;
|
||||
}
|
||||
|
||||
StreamOffset pos() override {
|
||||
return assetPos;
|
||||
}
|
||||
|
||||
bool atEnd() override {
|
||||
return assetPos >= assetSize;
|
||||
}
|
||||
|
||||
void seek(StreamOffset p, IOSeek mode) override {
|
||||
if (mode == IOSeek::Absolute)
|
||||
assetPos = p;
|
||||
else if (mode == IOSeek::Relative)
|
||||
assetPos = clamp<StreamOffset>(assetPos + p, 0, assetSize);
|
||||
else
|
||||
assetPos = clamp<StreamOffset>(assetSize - p, 0, assetSize);
|
||||
}
|
||||
|
||||
FilePtr file;
|
||||
StreamOffset fileOffset;
|
||||
StreamOffset assetSize;
|
||||
StreamOffset assetPos;
|
||||
};
|
||||
|
||||
auto p = m_index.ptr(path);
|
||||
if (!p)
|
||||
throw AssetSourceException::format("Requested file '%s' does not exist in the packed assets file", path);
|
||||
|
||||
return make_shared<AssetReader>(m_packedFile, p->first, p->second);
|
||||
}
|
||||
|
||||
ByteArray PackedAssetSource::read(String const& path) {
|
||||
auto p = m_index.ptr(path);
|
||||
if (!p)
|
||||
throw AssetSourceException::format("Requested file '%s' does not exist in the packed assets file", path);
|
||||
|
||||
ByteArray data(p->second, 0);
|
||||
m_packedFile->readFullAbsolute(p->first, data.ptr(), p->second);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
45
source/base/StarPackedAssetSource.hpp
Normal file
45
source/base/StarPackedAssetSource.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#ifndef STAR_PACKED_ASSET_SOURCE_HPP
|
||||
#define STAR_PACKED_ASSET_SOURCE_HPP
|
||||
|
||||
#include "StarOrderedMap.hpp"
|
||||
#include "StarFile.hpp"
|
||||
#include "StarDirectoryAssetSource.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(PackedAssetSource);
|
||||
|
||||
class PackedAssetSource : public AssetSource {
|
||||
public:
|
||||
typedef function<void(size_t, size_t, String, String)> BuildProgressCallback;
|
||||
|
||||
// Build a packed asset file from the given DirectoryAssetSource.
|
||||
//
|
||||
// 'extensionSorting' sorts the packed file with file extensions that case
|
||||
// insensitive match the given extensions in the order they are given. If a
|
||||
// file has an extension that doesn't match any in this list, it goes after
|
||||
// all other files. All files are sorted secondarily by case insensitive
|
||||
// alphabetical order.
|
||||
//
|
||||
// If given, 'progressCallback' will be called with the total number of
|
||||
// files, the current file number, the file name, and the asset path.
|
||||
static void build(DirectoryAssetSource& directorySource, String const& targetPackedFile,
|
||||
StringList const& extensionSorting = {}, BuildProgressCallback progressCallback = {});
|
||||
|
||||
PackedAssetSource(String const& packedFileName);
|
||||
|
||||
JsonObject metadata() const override;
|
||||
StringList assetPaths() const override;
|
||||
|
||||
IODevicePtr open(String const& path) override;
|
||||
ByteArray read(String const& path) override;
|
||||
|
||||
private:
|
||||
FilePtr m_packedFile;
|
||||
JsonObject m_metadata;
|
||||
OrderedHashMap<String, pair<uint64_t, uint64_t>> m_index;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
9
source/base/StarVersion.cpp.in
Normal file
9
source/base/StarVersion.cpp.in
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#include "StarVersion.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
char const* const StarVersionString = "1.4.4";
|
||||
char const* const StarSourceIdentifierString = "${STAR_SOURCE_IDENTIFIER}";
|
||||
char const* const StarArchitectureString = "${STAR_SYSTEM} ${STAR_ARCHITECTURE}";
|
||||
|
||||
}
|
||||
16
source/base/StarVersion.hpp
Normal file
16
source/base/StarVersion.hpp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef STAR_VERSION_HPP
|
||||
#define STAR_VERSION_HPP
|
||||
|
||||
#include "StarConfig.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
extern char const* const StarVersionString;
|
||||
extern char const* const StarSourceIdentifierString;
|
||||
extern char const* const StarArchitectureString;
|
||||
|
||||
typedef uint32_t VersionNumber;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
46
source/base/StarVersionOptionParser.cpp
Normal file
46
source/base/StarVersionOptionParser.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#include "StarVersionOptionParser.hpp"
|
||||
#include "StarFile.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
void VersionOptionParser::printVersion(std::ostream& os) {
|
||||
format(os, "Starbound Version %s (%s)\n", StarVersionString, StarArchitectureString);
|
||||
format(os, "Source Identifier - %s\n", StarSourceIdentifierString);
|
||||
}
|
||||
|
||||
VersionOptionParser::VersionOptionParser() {
|
||||
addSwitch("help", "Show help text");
|
||||
addSwitch("version", "Print version info");
|
||||
}
|
||||
|
||||
VersionOptionParser::Options VersionOptionParser::parseOrDie(StringList const& cmdLineArguments) const {
|
||||
Options options;
|
||||
StringList errors;
|
||||
tie(options, errors) = OptionParser::parseOptions(cmdLineArguments);
|
||||
|
||||
if (options.switches.contains("version"))
|
||||
printVersion(std::cout);
|
||||
|
||||
if (options.switches.contains("help"))
|
||||
printHelp(std::cout);
|
||||
|
||||
if (options.switches.contains("version") || options.switches.contains("help"))
|
||||
std::exit(0);
|
||||
|
||||
if (!errors.empty()) {
|
||||
for (auto const& err : errors)
|
||||
coutf("Error: %s\n", err);
|
||||
coutf("\n");
|
||||
printHelp(std::cout);
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
VersionOptionParser::Options VersionOptionParser::commandParseOrDie(int argc, char** argv) {
|
||||
setCommandName(File::baseName(argv[0]));
|
||||
return parseOrDie(StringList(argc - 1, argv + 1));
|
||||
}
|
||||
|
||||
}
|
||||
27
source/base/StarVersionOptionParser.hpp
Normal file
27
source/base/StarVersionOptionParser.hpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef STAR_VERSION_OPTION_PARSER_HPP
|
||||
#define STAR_VERSION_OPTION_PARSER_HPP
|
||||
|
||||
#include "StarOptionParser.hpp"
|
||||
#include "StarVersion.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// Option parser that accepts -h to print the help and exit and -v to print the
|
||||
// version and exit.
|
||||
class VersionOptionParser : public OptionParser {
|
||||
public:
|
||||
static void printVersion(std::ostream& os);
|
||||
|
||||
VersionOptionParser();
|
||||
|
||||
// Parse the command line options, or, in the case of an error, -h, or -v,
|
||||
// prints the appropriate text and immediately exits.
|
||||
Options parseOrDie(StringList const& cmdLineArguments) const;
|
||||
|
||||
// First sets the command name based on argv[0], then calls parseOrDie.
|
||||
Options commandParseOrDie(int argc, char** argv);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
358
source/base/StarWorldGeometry.cpp
Normal file
358
source/base/StarWorldGeometry.cpp
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
#include "StarWorldGeometry.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
function<float(float, float)> WorldGeometry::xDiffFunction() const {
|
||||
if (m_size[0] == 0) {
|
||||
return [](float x1, float x2) -> float { return x1 - x2; };
|
||||
} else {
|
||||
unsigned xsize = m_size[0];
|
||||
return [xsize](float x1, float x2) -> float { return wrapDiffF<float>(x1, x2, xsize); };
|
||||
}
|
||||
}
|
||||
|
||||
function<Vec2F(Vec2F, Vec2F)> WorldGeometry::diffFunction() const {
|
||||
if (m_size[0] == 0) {
|
||||
return [](Vec2F const& a, Vec2F const& b) -> Vec2F { return a - b; };
|
||||
} else {
|
||||
unsigned xsize = m_size[0];
|
||||
return [xsize](Vec2F const& a, Vec2F const& b) -> Vec2F {
|
||||
return Vec2F(wrapDiffF<float>(a[0], b[0], xsize), a[1] - b[1]);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function<float(float, float, float)> WorldGeometry::xLerpFunction(Maybe<float> discontinuityThreshold) const {
|
||||
if (m_size[0] == 0) {
|
||||
return [](float, float min, float) -> float { return min; };
|
||||
} else {
|
||||
unsigned xsize = m_size[0];
|
||||
return [discontinuityThreshold, xsize](float offset, float min, float max) -> float {
|
||||
float distance = wrapDiffF<float>(max, min, xsize);
|
||||
if (discontinuityThreshold && distance > *discontinuityThreshold)
|
||||
return min + distance;
|
||||
return min + offset * distance;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function<Vec2F(float, Vec2F, Vec2F)> WorldGeometry::lerpFunction(Maybe<float> discontinuityThreshold) const {
|
||||
if (m_size[0] == 0) {
|
||||
return [](float, Vec2F const& min, Vec2F const&) -> Vec2F { return min; };
|
||||
} else {
|
||||
unsigned xsize = m_size[0];
|
||||
return [discontinuityThreshold, xsize](float offset, Vec2F const& min, Vec2F const& max) -> Vec2F {
|
||||
Vec2F distance = Vec2F(wrapDiffF<float>(max[0], min[0], xsize), max[1] - min[1]);
|
||||
if (discontinuityThreshold && distance.magnitude() > *discontinuityThreshold)
|
||||
return min + distance;
|
||||
return min + offset * distance;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
StaticList<RectF, 2> WorldGeometry::splitRect(RectF const& bbox) const {
|
||||
if (bbox.isNull() || m_size[0] == 0)
|
||||
return {bbox};
|
||||
|
||||
Vec2F minWrap = xwrap(bbox.min());
|
||||
RectF bboxWrap = RectF(minWrap, minWrap + bbox.size());
|
||||
|
||||
// This does not work for ranges greater than m_size[0] wide!
|
||||
starAssert(bbox.xMax() - bbox.xMin() <= (float)m_size[0]);
|
||||
|
||||
// Since min is wrapped, we're only checking to see if max is on the other
|
||||
// side of the wrap point
|
||||
if (bboxWrap.xMax() > m_size[0]) {
|
||||
return {RectF(bboxWrap.xMin(), bboxWrap.yMin(), m_size[0], bboxWrap.yMax()),
|
||||
RectF(0, bboxWrap.yMin(), bboxWrap.xMax() - m_size[0], bboxWrap.yMax())};
|
||||
} else {
|
||||
return {bboxWrap};
|
||||
}
|
||||
}
|
||||
|
||||
StaticList<RectF, 2> WorldGeometry::splitRect(RectF bbox, Vec2F const& position) const {
|
||||
bbox.translate(position);
|
||||
return splitRect(bbox);
|
||||
}
|
||||
|
||||
StaticList<RectI, 2> WorldGeometry::splitRect(RectI const bbox) const {
|
||||
if (bbox.isNull() || m_size[0] == 0)
|
||||
return {bbox};
|
||||
|
||||
Vec2I minWrap = xwrap(bbox.min());
|
||||
RectI bboxWrap = RectI(minWrap, minWrap + bbox.size());
|
||||
|
||||
// This does not work for ranges greater than m_size[0] wide!
|
||||
starAssert(bbox.xMax() - bbox.xMin() <= (int)m_size[0]);
|
||||
|
||||
// Since min is wrapped, we're only checking to see if max is on the other
|
||||
// side of the wrap point
|
||||
if (bboxWrap.xMax() > (int)m_size[0]) {
|
||||
return {RectI(bboxWrap.xMin(), bboxWrap.yMin(), m_size[0], bboxWrap.yMax()),
|
||||
RectI(0, bboxWrap.yMin(), bboxWrap.xMax() - m_size[0], bboxWrap.yMax())};
|
||||
} else {
|
||||
return {bboxWrap};
|
||||
}
|
||||
}
|
||||
|
||||
StaticList<Line2F, 2> WorldGeometry::splitLine(Line2F line, bool preserveDirection) const {
|
||||
if (m_size[0] == 0)
|
||||
return {line};
|
||||
|
||||
bool swapDirection = line.makePositive() && preserveDirection;
|
||||
Vec2F minWrap = xwrap(line.min());
|
||||
|
||||
// diff is safe because we're looking for the line gnostic diff
|
||||
Line2F lineWrap = Line2F(minWrap, minWrap + line.diff());
|
||||
|
||||
// Since min is wrapped, we're only checking to see if max is on the other
|
||||
// side of the wrap point
|
||||
if (lineWrap.max()[0] > m_size[0]) {
|
||||
Vec2F intersection = lineWrap.intersection(Line2F(Vec2F(m_size[0], 0), Vec2F(m_size)), true).point;
|
||||
if (swapDirection)
|
||||
return {Line2F(lineWrap.max() - Vec2F(m_size[0], 0), Vec2F(0, intersection[1])),
|
||||
Line2F(Vec2F(m_size[0], intersection[1]), lineWrap.min())};
|
||||
else
|
||||
return {Line2F(lineWrap.min(), Vec2F(m_size[0], intersection[1])),
|
||||
Line2F(Vec2F(0, intersection[1]), lineWrap.max() - Vec2F(m_size[0], 0))};
|
||||
} else {
|
||||
if (swapDirection)
|
||||
lineWrap.reverse();
|
||||
return {lineWrap};
|
||||
}
|
||||
}
|
||||
|
||||
StaticList<Line2F, 2> WorldGeometry::splitLine(Line2F line, Vec2F const& position, bool preserveDirection) const {
|
||||
line.translate(position);
|
||||
return splitLine(line, preserveDirection);
|
||||
}
|
||||
|
||||
StaticList<PolyF, 2> WorldGeometry::splitPoly(PolyF const& poly) const {
|
||||
if (poly.isNull() || m_size[0] == 0)
|
||||
return {poly};
|
||||
|
||||
Array<PolyF, 2> res;
|
||||
bool polySelect = false;
|
||||
|
||||
Line2F worldBoundRight = {Vec2F(m_size[0], 0), Vec2F(m_size[0], 1)};
|
||||
Line2F worldBoundLeft = {Vec2F(0, 0), Vec2F(0, 1)};
|
||||
|
||||
for (unsigned i = 0; i < poly.sides(); i++) {
|
||||
Line2F segment = poly.side(i);
|
||||
if ((segment.min()[0] < 0) ^ (segment.max()[0] < 0)) {
|
||||
Vec2F worldCorrect = {(float)m_size[0], 0};
|
||||
Vec2F intersect = segment.intersection(worldBoundLeft, true).point;
|
||||
if (segment.min()[0] < 0) {
|
||||
res[polySelect].add(segment.min() + worldCorrect);
|
||||
res[polySelect].add(Vec2F(m_size[0], intersect[1]));
|
||||
polySelect = !polySelect;
|
||||
res[polySelect].add(Vec2F(0, intersect[1]));
|
||||
} else {
|
||||
res[polySelect].add(segment.min());
|
||||
res[polySelect].add(Vec2F(0, intersect[1]));
|
||||
polySelect = !polySelect;
|
||||
res[polySelect].add(Vec2F(m_size[0], intersect[1]));
|
||||
}
|
||||
} else if ((segment.min()[0] > m_size[0]) ^ (segment.max()[0] > m_size[0])) {
|
||||
Vec2F worldCorrect = {(float)m_size[0], 0};
|
||||
Vec2F intersect = segment.intersection(worldBoundRight, true).point;
|
||||
if (segment.min()[0] > m_size[0]) {
|
||||
res[polySelect].add(segment.min() - worldCorrect);
|
||||
res[polySelect].add(Vec2F(0, intersect[1]));
|
||||
polySelect = !polySelect;
|
||||
res[polySelect].add(Vec2F(m_size[0], intersect[1]));
|
||||
} else {
|
||||
res[polySelect].add(segment.min());
|
||||
res[polySelect].add(Vec2F(m_size[0], intersect[1]));
|
||||
polySelect = !polySelect;
|
||||
res[polySelect].add(Vec2F(0, intersect[1]));
|
||||
}
|
||||
} else {
|
||||
if (segment.min()[0] < 0) {
|
||||
res[polySelect].add(segment.min() + Vec2F((float)m_size[0], 0));
|
||||
} else if (segment.min()[0] > m_size[0]) {
|
||||
res[polySelect].add(segment.min() - Vec2F((float)m_size[0], 0));
|
||||
} else {
|
||||
res[polySelect].add(segment.min());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (res[1].isNull())
|
||||
return {res[0]};
|
||||
if (res[0].isNull())
|
||||
return {res[1]};
|
||||
else
|
||||
return {res[0], res[1]};
|
||||
}
|
||||
|
||||
StaticList<PolyF, 2> WorldGeometry::splitPoly(PolyF poly, Vec2F const& position) const {
|
||||
poly.translate(position);
|
||||
return splitPoly(poly);
|
||||
}
|
||||
|
||||
StaticList<Vec2I, 2> WorldGeometry::splitXRegion(Vec2I const& xRegion) const {
|
||||
if (m_size[0] == 0)
|
||||
return {xRegion};
|
||||
|
||||
starAssert(xRegion[1] >= xRegion[0]);
|
||||
|
||||
// This does not work for ranges greater than m_size[0] wide!
|
||||
starAssert(xRegion[1] - xRegion[0] <= (int)m_size[0]);
|
||||
|
||||
int x1 = xwrap(xRegion[0]);
|
||||
int x2 = x1 + xRegion[1] - xRegion[0];
|
||||
|
||||
if (x2 > (int)m_size[0]) {
|
||||
return {Vec2I(x1, m_size[0]), Vec2I(0.0f, x2 - m_size[0])};
|
||||
} else {
|
||||
return {{x1, x2}};
|
||||
}
|
||||
}
|
||||
|
||||
StaticList<Vec2F, 2> WorldGeometry::splitXRegion(Vec2F const& xRegion) const {
|
||||
if (m_size[0] == 0)
|
||||
return {xRegion};
|
||||
|
||||
starAssert(xRegion[1] >= xRegion[0]);
|
||||
|
||||
// This does not work for ranges greater than m_size[0] wide!
|
||||
starAssert(xRegion[1] - xRegion[0] <= (float)m_size[0]);
|
||||
|
||||
float x1 = xwrap(xRegion[0]);
|
||||
float x2 = x1 + xRegion[1] - xRegion[0];
|
||||
|
||||
if (x2 > m_size[0]) {
|
||||
return {Vec2F(x1, m_size[0]), Vec2F(0.0f, x2 - m_size[0])};
|
||||
} else {
|
||||
return {{x1, x2}};
|
||||
}
|
||||
}
|
||||
|
||||
bool WorldGeometry::rectContains(RectF const& rect, Vec2F const& pos) const {
|
||||
auto wpos = xwrap(pos);
|
||||
for (auto const& r : splitRect(rect)) {
|
||||
if (r.contains(wpos))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WorldGeometry::rectIntersectsRect(RectF const& rect1, RectF const& rect2) const {
|
||||
for (auto const& r1 : splitRect(rect1)) {
|
||||
for (auto const& r2 : splitRect(rect2)) {
|
||||
if (r1.intersects(r2))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RectF WorldGeometry::rectOverlap(RectF const& rect1, RectF const& rect2) const {
|
||||
return rect1.overlap(RectF::withSize(nearestTo(rect1.min(), rect2.min()), rect2.size()));
|
||||
}
|
||||
|
||||
bool WorldGeometry::polyContains(PolyF const& poly, Vec2F const& pos) const {
|
||||
auto wpos = xwrap(pos);
|
||||
for (auto const& p : splitPoly(poly)) {
|
||||
if (p.contains(wpos))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float WorldGeometry::polyOverlapArea(PolyF const& poly1, PolyF const& poly2) const {
|
||||
float area = 0.0f;
|
||||
for (auto const& p1 : splitPoly(poly1)) {
|
||||
for (auto const& p2 : splitPoly(poly2))
|
||||
area += PolyF::clip(p1, p2).convexArea();
|
||||
}
|
||||
return area;
|
||||
}
|
||||
|
||||
bool WorldGeometry::lineIntersectsRect(Line2F const& line, RectF const& rect) const {
|
||||
for (auto l : splitLine(line)) {
|
||||
for (auto box : splitRect(rect)) {
|
||||
if (box.intersects(l)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WorldGeometry::lineIntersectsPoly(Line2F const& line, PolyF const& poly) const {
|
||||
for (auto a : splitLine(line)) {
|
||||
for (auto b : splitPoly(poly)) {
|
||||
if (b.intersects(a)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WorldGeometry::polyIntersectsPoly(PolyF const& polyA, PolyF const& polyB) const {
|
||||
for (auto a : splitPoly(polyA)) {
|
||||
for (auto b : splitPoly(polyB)) {
|
||||
if (b.intersects(a))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WorldGeometry::rectIntersectsCircle(RectF const& rect, Vec2F const& center, float radius) const {
|
||||
if (rect.contains(center))
|
||||
return true;
|
||||
for (auto const& e : rect.edges()) {
|
||||
if (lineIntersectsCircle(e, center, radius))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WorldGeometry::lineIntersectsCircle(Line2F const& line, Vec2F const& center, float radius) const {
|
||||
for (auto const& sline : splitLine(line)) {
|
||||
if (sline.distanceTo(nearestTo(sline.center(), center)) <= radius)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Maybe<Vec2F> WorldGeometry::lineIntersectsPolyAt(Line2F const& line, PolyF const& poly) const {
|
||||
for (auto a : splitLine(line, true)) {
|
||||
for (auto b : splitPoly(poly)) {
|
||||
if (auto intersection = b.lineIntersection(a))
|
||||
return intersection->point;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
float WorldGeometry::polyDistance(PolyF const& poly, Vec2F const& point) const {
|
||||
auto spoint = nearestTo(poly.center(), point);
|
||||
return poly.distance(spoint);
|
||||
}
|
||||
|
||||
Vec2F WorldGeometry::nearestCoordInBox(RectF const& box, Vec2F const& pos) const {
|
||||
RectF t(box);
|
||||
auto offset = t.center();
|
||||
auto r = diff(pos, offset);
|
||||
t.setCenter({});
|
||||
return t.nearestCoordTo(r) + offset;
|
||||
}
|
||||
|
||||
Vec2F WorldGeometry::diffToNearestCoordInBox(RectF const& box, Vec2F const& pos) const {
|
||||
RectF t(box);
|
||||
auto offset = t.center();
|
||||
auto r = diff(pos, offset);
|
||||
t.setCenter({});
|
||||
auto coord = t.nearestCoordTo(r) + offset;
|
||||
return diff(pos, coord);
|
||||
}
|
||||
|
||||
}
|
||||
263
source/base/StarWorldGeometry.hpp
Normal file
263
source/base/StarWorldGeometry.hpp
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
#ifndef STAR_WORLD_GEOMETRY_HPP
|
||||
#define STAR_WORLD_GEOMETRY_HPP
|
||||
|
||||
#include "StarPoly.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(WorldGeometry);
|
||||
|
||||
// Utility class for dealing with the non-euclidean nature of the World.
|
||||
// Handles the surprisingly complex job of deciding intersections and splitting
|
||||
// geometry across the world wrap boundary.
|
||||
class WorldGeometry {
|
||||
public:
|
||||
// A null WorldGeometry will have diff / wrap methods etc be the normal
|
||||
// euclidean variety.
|
||||
WorldGeometry();
|
||||
WorldGeometry(unsigned width, unsigned height);
|
||||
WorldGeometry(Vec2U const& size);
|
||||
|
||||
bool isNull();
|
||||
|
||||
bool operator==(WorldGeometry const& other) const;
|
||||
bool operator!=(WorldGeometry const& other) const;
|
||||
|
||||
unsigned width() const;
|
||||
unsigned height() const;
|
||||
Vec2U size() const;
|
||||
|
||||
// Wrap given point back into world space by wrapping x
|
||||
int xwrap(int x) const;
|
||||
float xwrap(float x) const;
|
||||
// Only wraps x component.
|
||||
Vec2F xwrap(Vec2F const& pos) const;
|
||||
Vec2I xwrap(Vec2I const& pos) const;
|
||||
|
||||
// y value is clamped to be in the range [0, height)
|
||||
float yclamp(float y) const;
|
||||
|
||||
// Wraps and clamps position
|
||||
Vec2F limit(Vec2F const& pos) const;
|
||||
|
||||
bool crossesWrap(float xMin, float xMax) const;
|
||||
|
||||
// Do these two inexes point to the same location
|
||||
bool equal(Vec2I const& p1, Vec2I const& p2) const;
|
||||
|
||||
// Same as wrap, returns unsigned type.
|
||||
unsigned index(int x) const;
|
||||
Vec2U index(Vec2I const& i) const;
|
||||
|
||||
// returns right only distance from x2 to x1 (or x1 - x2). Always positive.
|
||||
int pdiff(int x1, int x2) const;
|
||||
|
||||
// Shortest difference between two given points. Always returns diff on the
|
||||
// "side" that x1 is on.
|
||||
float diff(float x1, float x2) const;
|
||||
int diff(int x1, int x2) const;
|
||||
|
||||
// Same but for 2d vectors
|
||||
Vec2F diff(Vec2F const& p1, Vec2F const& p2) const;
|
||||
Vec2I diff(Vec2I const& p1, Vec2I const& p2) const;
|
||||
|
||||
// Midpoint of the shortest line connecting two points.
|
||||
Vec2F midpoint(Vec2F const& p1, Vec2F const& p2) const;
|
||||
|
||||
function<float(float, float)> xDiffFunction() const;
|
||||
function<Vec2F(Vec2F, Vec2F)> diffFunction() const;
|
||||
function<float(float, float, float)> xLerpFunction(Maybe<float> discontinuityThreshold = {}) const;
|
||||
function<Vec2F(float, Vec2F, Vec2F)> lerpFunction(Maybe<float> discontinuityThreshold = {}) const;
|
||||
|
||||
// Wrapping functions are not guaranteed to work for objects larger than
|
||||
// worldWidth / 2. Bad things can happen.
|
||||
|
||||
// Split the given Rect across world boundaries.
|
||||
StaticList<RectF, 2> splitRect(RectF const& bbox) const;
|
||||
// Split the given Rect after translating it by position.
|
||||
StaticList<RectF, 2> splitRect(RectF bbox, Vec2F const& position) const;
|
||||
|
||||
StaticList<RectI, 2> splitRect(RectI bbox) const;
|
||||
|
||||
// Same but for Line
|
||||
StaticList<Line2F, 2> splitLine(Line2F line, bool preserveDirection = false) const;
|
||||
StaticList<Line2F, 2> splitLine(Line2F line, Vec2F const& position, bool preserveDirection = false) const;
|
||||
|
||||
// Same but for Poly
|
||||
StaticList<PolyF, 2> splitPoly(PolyF const& poly) const;
|
||||
StaticList<PolyF, 2> splitPoly(PolyF poly, Vec2F const& position) const;
|
||||
|
||||
// Split a horizontal region of the world across the world wrap point.
|
||||
StaticList<Vec2I, 2> splitXRegion(Vec2I const& xRegion) const;
|
||||
StaticList<Vec2F, 2> splitXRegion(Vec2F const& xRegion) const;
|
||||
|
||||
bool rectContains(RectF const& rect1, Vec2F const& pos) const;
|
||||
bool rectIntersectsRect(RectF const& rect1, RectF const& rect2) const;
|
||||
RectF rectOverlap(RectF const& rect1, RectF const& rect2) const;
|
||||
bool polyContains(PolyF const& poly, Vec2F const& pos) const;
|
||||
float polyOverlapArea(PolyF const& poly1, PolyF const& poly2) const;
|
||||
|
||||
bool lineIntersectsRect(Line2F const& line, RectF const& rect) const;
|
||||
bool lineIntersectsPoly(Line2F const& line, PolyF const& poly) const;
|
||||
bool polyIntersectsPoly(PolyF const& poly1, PolyF const& poly2) const;
|
||||
|
||||
bool rectIntersectsCircle(RectF const& rect, Vec2F const& center, float radius) const;
|
||||
bool lineIntersectsCircle(Line2F const& line, Vec2F const& center, float radius) const;
|
||||
|
||||
Maybe<Vec2F> lineIntersectsPolyAt(Line2F const& line, PolyF const& poly) const;
|
||||
|
||||
// Returns the distance from a point to any part of the given poly
|
||||
float polyDistance(PolyF const& poly, Vec2F const& point) const;
|
||||
|
||||
// Produces a point that is on the same "side" of the world as the source point.
|
||||
int nearestTo(int source, int target) const;
|
||||
float nearestTo(float source, float target) const;
|
||||
Vec2I nearestTo(Vec2I const& source, Vec2I const& target) const;
|
||||
Vec2F nearestTo(Vec2F const& source, Vec2F const& target) const;
|
||||
|
||||
Vec2F nearestCoordInBox(RectF const& box, Vec2F const& pos) const;
|
||||
Vec2F diffToNearestCoordInBox(RectF const& box, Vec2F const& pos) const;
|
||||
|
||||
private:
|
||||
Vec2U m_size;
|
||||
};
|
||||
|
||||
inline WorldGeometry::WorldGeometry()
|
||||
: m_size(Vec2U()) {}
|
||||
|
||||
inline WorldGeometry::WorldGeometry(unsigned width, unsigned height)
|
||||
: m_size(width, height) {}
|
||||
|
||||
inline WorldGeometry::WorldGeometry(Vec2U const& size)
|
||||
: m_size(size) {}
|
||||
|
||||
inline bool WorldGeometry::isNull() {
|
||||
return m_size == Vec2U();
|
||||
}
|
||||
|
||||
inline bool WorldGeometry::operator==(WorldGeometry const& other) const {
|
||||
return m_size == other.m_size;
|
||||
}
|
||||
|
||||
inline bool WorldGeometry::operator!=(WorldGeometry const& other) const {
|
||||
return m_size != other.m_size;
|
||||
}
|
||||
|
||||
inline unsigned WorldGeometry::width() const {
|
||||
return m_size[0];
|
||||
}
|
||||
|
||||
inline unsigned WorldGeometry::height() const {
|
||||
return m_size[1];
|
||||
}
|
||||
|
||||
inline Vec2U WorldGeometry::size() const {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
inline int WorldGeometry::xwrap(int x) const {
|
||||
if (m_size[0] == 0)
|
||||
return x;
|
||||
else
|
||||
return pmod<int>(x, m_size[0]);
|
||||
}
|
||||
|
||||
inline float WorldGeometry::xwrap(float x) const {
|
||||
if (m_size[0] == 0)
|
||||
return x;
|
||||
else
|
||||
return pfmod<float>(x, m_size[0]);
|
||||
}
|
||||
|
||||
inline Vec2F WorldGeometry::xwrap(Vec2F const& pos) const {
|
||||
return {xwrap(pos[0]), pos[1]};
|
||||
}
|
||||
|
||||
inline Vec2I WorldGeometry::xwrap(Vec2I const& pos) const {
|
||||
return {xwrap(pos[0]), pos[1]};
|
||||
}
|
||||
|
||||
inline float WorldGeometry::yclamp(float y) const {
|
||||
return clamp<float>(y, 0, std::nextafter(m_size[1], 0.0f));
|
||||
}
|
||||
|
||||
inline Vec2F WorldGeometry::limit(Vec2F const& pos) const {
|
||||
return {xwrap(pos[0]), yclamp(pos[1])};
|
||||
}
|
||||
|
||||
inline bool WorldGeometry::crossesWrap(float xMin, float xMax) const {
|
||||
return xwrap(xMax) < xwrap(xMin);
|
||||
}
|
||||
|
||||
inline bool WorldGeometry::equal(Vec2I const& p1, Vec2I const& p2) const {
|
||||
return index(p1) == index(p2);
|
||||
}
|
||||
|
||||
inline unsigned WorldGeometry::index(int x) const {
|
||||
return (unsigned)xwrap(x);
|
||||
}
|
||||
|
||||
inline Vec2U WorldGeometry::index(Vec2I const& i) const {
|
||||
return Vec2U(xwrap(i[0]), i[1]);
|
||||
}
|
||||
|
||||
inline int WorldGeometry::pdiff(int x1, int x2) const {
|
||||
if (m_size[0] == 0)
|
||||
return x1 - x2;
|
||||
else
|
||||
return pmod<int>(x1 - x2, m_size[0]);
|
||||
}
|
||||
|
||||
inline float WorldGeometry::diff(float x1, float x2) const {
|
||||
if (m_size[0] == 0)
|
||||
return x1 - x2;
|
||||
else
|
||||
return wrapDiffF<float>(x1, x2, m_size[0]);
|
||||
}
|
||||
|
||||
inline int WorldGeometry::diff(int x1, int x2) const {
|
||||
if (m_size[0] == 0)
|
||||
return x1 - x2;
|
||||
else
|
||||
return wrapDiff<int>(x1, x2, m_size[0]);
|
||||
}
|
||||
|
||||
inline Vec2F WorldGeometry::diff(Vec2F const& p1, Vec2F const& p2) const {
|
||||
float xdiff = diff(p1[0], p2[0]);
|
||||
return {xdiff, p1[1] - p2[1]};
|
||||
}
|
||||
|
||||
inline Vec2I WorldGeometry::diff(Vec2I const& p1, Vec2I const& p2) const {
|
||||
int xdiff = diff(p1[0], p2[0]);
|
||||
return {xdiff, p1[1] - p2[1]};
|
||||
}
|
||||
|
||||
inline Vec2F WorldGeometry::midpoint(Vec2F const& p1, Vec2F const& p2) const {
|
||||
return xwrap(diff(p1, p2) / 2 + p2);
|
||||
}
|
||||
|
||||
inline int WorldGeometry::nearestTo(int source, int target) const {
|
||||
if (abs(target - source) < (int)(m_size[0] / 2))
|
||||
return target;
|
||||
else
|
||||
return diff(target, source) + source;
|
||||
}
|
||||
|
||||
inline float WorldGeometry::nearestTo(float source, float target) const {
|
||||
if (abs(target - source) < (float)(m_size[0] / 2))
|
||||
return target;
|
||||
else
|
||||
return diff(target, source) + source;
|
||||
}
|
||||
|
||||
inline Vec2I WorldGeometry::nearestTo(Vec2I const& source, Vec2I const& target) const {
|
||||
return Vec2I(nearestTo(source[0], target[0]), target[1]);
|
||||
}
|
||||
|
||||
inline Vec2F WorldGeometry::nearestTo(Vec2F const& source, Vec2F const& target) const {
|
||||
return Vec2F(nearestTo(source[0], target[0]), target[1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
31
source/client/CMakeLists.txt
Normal file
31
source/client/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
INCLUDE_DIRECTORIES (
|
||||
${STAR_EXTERN_INCLUDES}
|
||||
${STAR_CORE_INCLUDES}
|
||||
${STAR_BASE_INCLUDES}
|
||||
${STAR_GAME_INCLUDES}
|
||||
${STAR_PLATFORM_INCLUDES}
|
||||
${STAR_APPLICATION_INCLUDES}
|
||||
${STAR_RENDERING_INCLUDES}
|
||||
${STAR_WINDOWING_INCLUDES}
|
||||
${STAR_FRONTEND_INCLUDES}
|
||||
)
|
||||
|
||||
SET (star_client_HEADERS
|
||||
StarClientApplication.hpp
|
||||
)
|
||||
|
||||
SET (star_client_SOURCES
|
||||
StarClientApplication.cpp
|
||||
)
|
||||
|
||||
IF (STAR_SYSTEM_WINDOWS)
|
||||
SET (star_client_RESOURCES
|
||||
starbound.rc
|
||||
)
|
||||
ENDIF ()
|
||||
|
||||
ADD_EXECUTABLE (starbound WIN32
|
||||
$<TARGET_OBJECTS:star_extern> $<TARGET_OBJECTS:star_core> $<TARGET_OBJECTS:star_base> $<TARGET_OBJECTS:star_game>
|
||||
$<TARGET_OBJECTS:star_application> $<TARGET_OBJECTS:star_rendering> $<TARGET_OBJECTS:star_windowing> $<TARGET_OBJECTS:star_frontend>
|
||||
${star_client_HEADERS} ${star_client_SOURCES} ${star_client_RESOURCES})
|
||||
TARGET_LINK_LIBRARIES (starbound ${STAR_EXT_LIBS} ${STAR_EXT_GUI_LIBS})
|
||||
903
source/client/StarClientApplication.cpp
Normal file
903
source/client/StarClientApplication.cpp
Normal file
|
|
@ -0,0 +1,903 @@
|
|||
#include "StarClientApplication.hpp"
|
||||
#include "StarConfiguration.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarFile.hpp"
|
||||
#include "StarEncode.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarVersion.hpp"
|
||||
#include "StarPlayer.hpp"
|
||||
#include "StarPlayerStorage.hpp"
|
||||
#include "StarPlayerLog.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarWorldTemplate.hpp"
|
||||
#include "StarWorldClient.hpp"
|
||||
#include "StarRootLoader.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
Json const AdditionalAssetsSettings = Json::parseJson(R"JSON(
|
||||
{
|
||||
"missingImage" : "/assetmissing.png",
|
||||
"missingAudio" : "/assetmissing.wav"
|
||||
}
|
||||
)JSON");
|
||||
|
||||
Json const AdditionalDefaultConfiguration = Json::parseJson(R"JSON(
|
||||
{
|
||||
"configurationVersion" : {
|
||||
"client" : 8
|
||||
},
|
||||
|
||||
"allowAssetsMismatch" : false,
|
||||
"vsync" : true,
|
||||
"limitTextureAtlasSize" : false,
|
||||
"useMultiTexturing" : true,
|
||||
"audioChannelSeparation" : [-25, 25],
|
||||
|
||||
"sfxVol" : 100,
|
||||
"musicVol" : 70,
|
||||
"windowedResolution" : [1000, 600],
|
||||
"fullscreenResolution" : [1920, 1080],
|
||||
"fullscreen" : false,
|
||||
"borderless" : false,
|
||||
"maximized" : true,
|
||||
"zoomLevel" : 3.0,
|
||||
"speechBubbles" : true,
|
||||
|
||||
"title" : {
|
||||
"multiPlayerAddress" : "",
|
||||
"multiPlayerPort" : "",
|
||||
"multiPlayerAccount" : ""
|
||||
},
|
||||
|
||||
"bindings" : {
|
||||
"PlayerUp" : [ { "type" : "key", "value" : "W", "mods" : [] } ],
|
||||
"PlayerDown" : [ { "type" : "key", "value" : "S", "mods" : [] } ],
|
||||
"PlayerLeft" : [ { "type" : "key", "value" : "A", "mods" : [] } ],
|
||||
"PlayerRight" : [ { "type" : "key", "value" : "D", "mods" : [] } ],
|
||||
"PlayerJump" : [ { "type" : "key", "value" : "Space", "mods" : [] } ],
|
||||
"PlayerDropItem" : [ { "type" : "key", "value" : "Q", "mods" : [] } ],
|
||||
"PlayerInteract" : [ { "type" : "key", "value" : "E", "mods" : [] } ],
|
||||
"PlayerShifting" : [ { "type" : "key", "value" : "RShift", "mods" : [] }, { "type" : "key", "value" : "LShift", "mods" : [] } ],
|
||||
"PlayerTechAction1" : [ { "type" : "key", "value" : "F", "mods" : [] } ],
|
||||
"PlayerTechAction2" : [],
|
||||
"PlayerTechAction3" : [],
|
||||
"EmoteBlabbering" : [ { "type" : "key", "value" : "Right", "mods" : ["LCtrl", "LShift"] } ],
|
||||
"EmoteShouting" : [ { "type" : "key", "value" : "Up", "mods" : ["LCtrl", "LAlt"] } ],
|
||||
"EmoteHappy" : [ { "type" : "key", "value" : "Up", "mods" : [] } ],
|
||||
"EmoteSad" : [ { "type" : "key", "value" : "Down", "mods" : [] } ],
|
||||
"EmoteNeutral" : [ { "type" : "key", "value" : "Left", "mods" : [] } ],
|
||||
"EmoteLaugh" : [ { "type" : "key", "value" : "Left", "mods" : [ "LCtrl" ] } ],
|
||||
"EmoteAnnoyed" : [ { "type" : "key", "value" : "Right", "mods" : [] } ],
|
||||
"EmoteOh" : [ { "type" : "key", "value" : "Right", "mods" : [ "LCtrl" ] } ],
|
||||
"EmoteOooh" : [ { "type" : "key", "value" : "Down", "mods" : [ "LCtrl" ] } ],
|
||||
"EmoteBlink" : [ { "type" : "key", "value" : "Up", "mods" : [ "LCtrl" ] } ],
|
||||
"EmoteWink" : [ { "type" : "key", "value" : "Up", "mods" : ["LCtrl", "LShift"] } ],
|
||||
"EmoteEat" : [ { "type" : "key", "value" : "Down", "mods" : ["LCtrl", "LShift"] } ],
|
||||
"EmoteSleep" : [ { "type" : "key", "value" : "Left", "mods" : ["LCtrl", "LShift"] } ],
|
||||
"ShowLabels" : [ { "type" : "key", "value" : "RAlt", "mods" : [] }, { "type" : "key", "value" : "LAlt", "mods" : [] } ],
|
||||
"CameraShift" : [ { "type" : "key", "value" : "RCtrl", "mods" : [] }, { "type" : "key", "value" : "LCtrl", "mods" : [] } ],
|
||||
"TitleBack" : [ { "type" : "key", "value" : "Esc", "mods" : [] } ],
|
||||
"CinematicSkip" : [ { "type" : "key", "value" : "Esc", "mods" : [] } ],
|
||||
"CinematicNext" : [ { "type" : "key", "value" : "Right", "mods" : [] }, { "type" : "key", "value" : "Return", "mods" : [] } ],
|
||||
"GuiClose" : [ { "type" : "key", "value" : "Esc", "mods" : [] } ],
|
||||
"GuiShifting" : [ { "type" : "key", "value" : "RShift", "mods" : [] }, { "type" : "key", "value" : "LShift", "mods" : [] } ],
|
||||
"KeybindingCancel" : [ { "type" : "key", "value" : "Esc", "mods" : [] } ],
|
||||
"KeybindingClear" : [ { "type" : "key", "value" : "Del", "mods" : [] }, { "type" : "key", "value" : "Backspace", "mods" : [] } ],
|
||||
"ChatPageUp" : [ { "type" : "key", "value" : "PageUp", "mods" : [] } ],
|
||||
"ChatPageDown" : [ { "type" : "key", "value" : "PageDown", "mods" : [] } ],
|
||||
"ChatPreviousLine" : [ { "type" : "key", "value" : "Up", "mods" : [] } ],
|
||||
"ChatNextLine" : [ { "type" : "key", "value" : "Down", "mods" : [] } ],
|
||||
"ChatSendLine" : [ { "type" : "key", "value" : "Return", "mods" : [] } ],
|
||||
"ChatBegin" : [ { "type" : "key", "value" : "Return", "mods" : [] } ],
|
||||
"ChatBeginCommand" : [ { "type" : "key", "value" : "/", "mods" : [] } ],
|
||||
"ChatStop" : [ { "type" : "key", "value" : "Esc", "mods" : [] } ],
|
||||
"InterfaceHideHud" : [ { "type" : "key", "value" : "Z", "mods" : [ "LAlt" ] } ],
|
||||
"InterfaceChangeBarGroup" : [ { "type" : "key", "value" : "X", "mods" : [] } ],
|
||||
"InterfaceDeselectHands" : [ { "type" : "key", "value" : "Z", "mods" : [] } ],
|
||||
"InterfaceBar1" : [ { "type" : "key", "value" : "1", "mods" : [] } ],
|
||||
"InterfaceBar2" : [ { "type" : "key", "value" : "2", "mods" : [] } ],
|
||||
"InterfaceBar3" : [ { "type" : "key", "value" : "3", "mods" : [] } ],
|
||||
"InterfaceBar4" : [ { "type" : "key", "value" : "4", "mods" : [] } ],
|
||||
"InterfaceBar5" : [ { "type" : "key", "value" : "5", "mods" : [] } ],
|
||||
"InterfaceBar6" : [ { "type" : "key", "value" : "6", "mods" : [] } ],
|
||||
"InterfaceBar7" : [],
|
||||
"InterfaceBar8" : [],
|
||||
"InterfaceBar9" : [],
|
||||
"InterfaceBar10" : [],
|
||||
"EssentialBar1" : [ { "type" : "key", "value" : "R", "mods" : [] } ],
|
||||
"EssentialBar2" : [ { "type" : "key", "value" : "T", "mods" : [] } ],
|
||||
"EssentialBar3" : [ { "type" : "key", "value" : "Y", "mods" : [] } ],
|
||||
"EssentialBar4" : [ { "type" : "key", "value" : "N", "mods" : [] } ],
|
||||
"InterfaceRepeatCommand" : [ { "type" : "key", "value" : "P", "mods" : [] } ],
|
||||
"InterfaceToggleFullscreen" : [ { "type" : "key", "value" : "F11", "mods" : [] } ],
|
||||
"InterfaceReload" : [ ],
|
||||
"InterfaceEscapeMenu" : [ { "type" : "key", "value" : "Esc", "mods" : [] } ],
|
||||
"InterfaceInventory" : [ { "type" : "key", "value" : "I", "mods" : [] } ],
|
||||
"InterfaceCodex" : [ { "type" : "key", "value" : "L", "mods" : [] } ],
|
||||
"InterfaceQuest" : [ { "type" : "key", "value" : "J", "mods" : [] } ],
|
||||
"InterfaceCrafting" : [ { "type" : "key", "value" : "C", "mods" : [] } ]
|
||||
}
|
||||
}
|
||||
)JSON");
|
||||
|
||||
void ClientApplication::startup(StringList const& cmdLineArgs) {
|
||||
RootLoader rootLoader({AdditionalAssetsSettings, AdditionalDefaultConfiguration, String("starbound.log"), LogLevel::Info, false, String("starbound.config")});
|
||||
m_root = rootLoader.initOrDie(cmdLineArgs).first;
|
||||
|
||||
Logger::info("Client Version %s (%s) Source ID: %s Protocol: %s", StarVersionString, StarArchitectureString, StarSourceIdentifierString, StarProtocolVersion);
|
||||
|
||||
auto assets = m_root->assets();
|
||||
m_minInterfaceScale = assets->json("/interface.config:minInterfaceScale").toInt();
|
||||
m_maxInterfaceScale = assets->json("/interface.config:maxInterfaceScale").toInt();
|
||||
m_crossoverRes = jsonToVec2F(assets->json("/interface.config:interfaceCrossoverRes"));
|
||||
}
|
||||
|
||||
void ClientApplication::shutdown() {
|
||||
m_mainInterface.reset();
|
||||
|
||||
if (m_universeClient)
|
||||
m_universeClient->disconnect();
|
||||
|
||||
if (m_universeServer) {
|
||||
m_universeServer->stop();
|
||||
m_universeServer->join();
|
||||
m_universeServer.reset();
|
||||
}
|
||||
|
||||
if (m_statistics) {
|
||||
m_statistics->writeStatistics();
|
||||
m_statistics.reset();
|
||||
}
|
||||
|
||||
m_universeClient.reset();
|
||||
m_statistics.reset();
|
||||
}
|
||||
|
||||
void ClientApplication::applicationInit(ApplicationControllerPtr appController) {
|
||||
Application::applicationInit(appController);
|
||||
|
||||
appController->setCursorVisible(false);
|
||||
|
||||
AudioFormat audioFormat = appController->enableAudio();
|
||||
m_mainMixer = make_shared<MainMixer>(audioFormat.sampleRate, audioFormat.channels);
|
||||
|
||||
m_mainMixer->setVolume(0.5);
|
||||
|
||||
m_guiContext = make_shared<GuiContext>(m_mainMixer->mixer(), appController);
|
||||
|
||||
appController->setTargetUpdateRate(1.0f / WorldTimestep);
|
||||
|
||||
auto configuration = m_root->configuration();
|
||||
bool vsync = configuration->get("vsync").toBool();
|
||||
Vec2U windowedSize = jsonToVec2U(configuration->get("windowedResolution"));
|
||||
Vec2U fullscreenSize = jsonToVec2U(configuration->get("fullscreenResolution"));
|
||||
bool fullscreen = configuration->get("fullscreen").toBool();
|
||||
bool borderless = configuration->get("borderless").toBool();
|
||||
bool maximized = configuration->get("maximized").toBool();
|
||||
|
||||
appController->setApplicationTitle(m_root->assets()->json("/client.config:windowTitle").toString());
|
||||
appController->setVSyncEnabled(vsync);
|
||||
|
||||
if (fullscreen)
|
||||
appController->setFullscreenWindow(fullscreenSize);
|
||||
else if (borderless)
|
||||
appController->setBorderlessWindow();
|
||||
else if (maximized)
|
||||
appController->setMaximizedWindow();
|
||||
else
|
||||
appController->setNormalWindow(windowedSize);
|
||||
|
||||
appController->setMaxFrameSkip(m_root->assets()->json("/client.config:maxFrameSkip").toUInt());
|
||||
appController->setUpdateTrackWindow(m_root->assets()->json("/client.config:updateTrackWindow").toFloat());
|
||||
}
|
||||
|
||||
void ClientApplication::renderInit(RendererPtr renderer) {
|
||||
Application::renderInit(renderer);
|
||||
|
||||
String rendererConfig = strf("/rendering/%s.config", renderer->rendererId());
|
||||
if (m_root->assets()->assetExists(rendererConfig))
|
||||
renderer->setEffectConfig(m_root->assets()->json(rendererConfig));
|
||||
else
|
||||
Logger::warn("No rendering config found for renderer with id '%s'", renderer->rendererId());
|
||||
|
||||
if (m_root->configuration()->get("limitTextureAtlasSize").optBool().value(false))
|
||||
renderer->setSizeLimitEnabled(true);
|
||||
|
||||
renderer->setMultiTexturingEnabled(m_root->configuration()->get("useMultiTexturing").optBool().value(true));
|
||||
|
||||
m_guiContext->renderInit(renderer);
|
||||
|
||||
m_cinematicOverlay = make_shared<Cinematic>();
|
||||
m_errorScreen = make_shared<ErrorScreen>();
|
||||
|
||||
if (m_titleScreen)
|
||||
m_titleScreen->renderInit(renderer);
|
||||
if (m_worldPainter)
|
||||
m_worldPainter->renderInit(renderer);
|
||||
|
||||
changeState(MainAppState::Mods);
|
||||
}
|
||||
|
||||
void ClientApplication::windowChanged(WindowMode windowMode, Vec2U screenSize) {
|
||||
auto config = m_root->configuration();
|
||||
if (windowMode == WindowMode::Fullscreen) {
|
||||
config->set("fullscreenResolution", jsonFromVec2U(screenSize));
|
||||
config->set("fullscreen", true);
|
||||
config->set("borderless", false);
|
||||
} else if (windowMode == WindowMode::Borderless) {
|
||||
config->set("borderless", true);
|
||||
config->set("fullscreen", false);
|
||||
} else if (windowMode == WindowMode::Maximized) {
|
||||
config->set("maximized", true);
|
||||
config->set("fullscreen", false);
|
||||
config->set("borderless", false);
|
||||
config->set("windowedResolution", jsonFromVec2U(screenSize));
|
||||
} else {
|
||||
config->set("maximized", false);
|
||||
config->set("fullscreen", false);
|
||||
config->set("borderless", false);
|
||||
config->set("windowedResolution", jsonFromVec2U(screenSize));
|
||||
}
|
||||
}
|
||||
|
||||
void ClientApplication::processInput(InputEvent const& event) {
|
||||
if (auto keyDown = event.ptr<KeyDownEvent>()) {
|
||||
m_heldKeyEvents.append(*keyDown);
|
||||
m_edgeKeyEvents.append(*keyDown);
|
||||
} else if (auto keyUp = event.ptr<KeyUpEvent>()) {
|
||||
eraseWhere(m_heldKeyEvents, [&](auto& keyEvent) {
|
||||
return keyEvent.key == keyUp->key;
|
||||
});
|
||||
|
||||
Maybe<KeyMod> modKey = KeyModNames.maybeLeft(KeyNames.getRight(keyUp->key));
|
||||
if (modKey)
|
||||
m_heldKeyEvents.transform([&](auto& keyEvent) {
|
||||
return KeyDownEvent{keyEvent.key, keyEvent.mods & ~*modKey};
|
||||
});
|
||||
}
|
||||
|
||||
if (m_state == MainAppState::Splash) {
|
||||
m_cinematicOverlay->handleInputEvent(event);
|
||||
|
||||
} else if (m_state == MainAppState::ModsWarning || m_state == MainAppState::Error) {
|
||||
m_errorScreen->handleInputEvent(event);
|
||||
|
||||
} else if (m_state == MainAppState::Title) {
|
||||
if (!m_cinematicOverlay->handleInputEvent(event))
|
||||
m_titleScreen->handleInputEvent(event);
|
||||
|
||||
} else if (m_state == MainAppState::SinglePlayer || m_state == MainAppState::MultiPlayer) {
|
||||
if (!m_cinematicOverlay->handleInputEvent(event))
|
||||
m_mainInterface->handleInputEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientApplication::update() {
|
||||
if (m_state >= MainAppState::Title) {
|
||||
if (auto p2pNetworkingService = appController()->p2pNetworkingService()) {
|
||||
if (auto join = p2pNetworkingService->pullPendingJoin()) {
|
||||
m_pendingMultiPlayerConnection = PendingMultiPlayerConnection{join.takeValue(), {}, {}};
|
||||
changeState(MainAppState::Title);
|
||||
}
|
||||
|
||||
if (auto req = p2pNetworkingService->pullJoinRequest())
|
||||
m_mainInterface->queueJoinRequest(*req);
|
||||
|
||||
p2pNetworkingService->update();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_state == MainAppState::Mods)
|
||||
updateMods();
|
||||
else if (m_state == MainAppState::ModsWarning)
|
||||
updateModsWarning();
|
||||
if (m_state == MainAppState::Splash)
|
||||
updateSplash();
|
||||
else if (m_state == MainAppState::Error)
|
||||
updateError();
|
||||
else if (m_state == MainAppState::Title)
|
||||
updateTitle();
|
||||
else if (m_state > MainAppState::Title)
|
||||
updateRunning();
|
||||
|
||||
m_guiContext->cleanup();
|
||||
m_edgeKeyEvents.clear();
|
||||
}
|
||||
|
||||
void ClientApplication::render() {
|
||||
auto config = m_root->configuration();
|
||||
auto assets = m_root->assets();
|
||||
|
||||
if (m_guiContext->windowWidth() >= m_crossoverRes[0] && m_guiContext->windowHeight() >= m_crossoverRes[1])
|
||||
m_guiContext->setInterfaceScale(m_maxInterfaceScale);
|
||||
else
|
||||
m_guiContext->setInterfaceScale(m_minInterfaceScale);
|
||||
|
||||
if (m_state == MainAppState::Mods || m_state == MainAppState::Splash) {
|
||||
m_cinematicOverlay->render();
|
||||
|
||||
} else if (m_state == MainAppState::Title) {
|
||||
m_titleScreen->render();
|
||||
m_cinematicOverlay->render();
|
||||
|
||||
} else if (m_state > MainAppState::Title) {
|
||||
if (auto worldClient = m_universeClient->worldClient()) {
|
||||
if (auto renderer = Application::renderer())
|
||||
renderer->setEffectParameter("lightMapEnabled", true);
|
||||
worldClient->render(m_renderData, TilePainter::BorderTileSize);
|
||||
m_worldPainter->render(m_renderData);
|
||||
m_mainInterface->renderInWorldElements();
|
||||
if (auto renderer = Application::renderer())
|
||||
renderer->setEffectParameter("lightMapEnabled", false);
|
||||
}
|
||||
m_mainInterface->render();
|
||||
m_cinematicOverlay->render();
|
||||
|
||||
} else if (m_state == MainAppState::ModsWarning || m_state == MainAppState::Error) {
|
||||
m_errorScreen->render();
|
||||
}
|
||||
}
|
||||
|
||||
void ClientApplication::getAudioData(int16_t* sampleData, size_t frameCount) {
|
||||
m_mainMixer->read(sampleData, frameCount);
|
||||
}
|
||||
|
||||
void ClientApplication::changeState(MainAppState newState) {
|
||||
MainAppState oldState = m_state;
|
||||
m_state = newState;
|
||||
|
||||
if (m_state == MainAppState::Quit)
|
||||
appController()->quit();
|
||||
|
||||
if (newState == MainAppState::Mods)
|
||||
m_cinematicOverlay->load(m_root->assets()->json("/cinematics/mods/modloading.cinematic"));
|
||||
|
||||
if (newState == MainAppState::Splash) {
|
||||
m_cinematicOverlay->load(m_root->assets()->json("/cinematics/splash.cinematic"));
|
||||
m_rootLoader = Thread::invoke("Async root loader", [this]() {
|
||||
m_root->fullyLoad();
|
||||
});
|
||||
}
|
||||
|
||||
if (oldState > MainAppState::Title && m_state <= MainAppState::Title) {
|
||||
m_mainInterface.reset();
|
||||
if (m_universeClient)
|
||||
m_universeClient->disconnect();
|
||||
|
||||
if (m_universeServer) {
|
||||
m_universeServer->stop();
|
||||
m_universeServer->join();
|
||||
m_universeServer.reset();
|
||||
}
|
||||
m_cinematicOverlay->stop();
|
||||
|
||||
if (auto p2pNetworkingService = appController()->p2pNetworkingService()) {
|
||||
p2pNetworkingService->setJoinUnavailable();
|
||||
p2pNetworkingService->setAcceptingP2PConnections(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (oldState > MainAppState::Title && m_state == MainAppState::Title)
|
||||
m_titleScreen->resetState();
|
||||
|
||||
if (oldState >= MainAppState::Title && m_state < MainAppState::Title) {
|
||||
m_playerStorage.reset();
|
||||
|
||||
if (m_statistics) {
|
||||
m_statistics->writeStatistics();
|
||||
m_statistics.reset();
|
||||
}
|
||||
|
||||
m_universeClient.reset();
|
||||
m_mainMixer->setUniverseClient({});
|
||||
m_titleScreen.reset();
|
||||
}
|
||||
|
||||
if (oldState < MainAppState::Title && m_state >= MainAppState::Title) {
|
||||
if (m_rootLoader)
|
||||
m_rootLoader.finish();
|
||||
|
||||
m_cinematicOverlay->stop();
|
||||
|
||||
m_playerStorage = make_shared<PlayerStorage>(m_root->toStoragePath("player"));
|
||||
m_statistics = make_shared<Statistics>(m_root->toStoragePath("player"), appController()->statisticsService());
|
||||
m_universeClient = make_shared<UniverseClient>(m_playerStorage, m_statistics);
|
||||
m_mainMixer->setUniverseClient(m_universeClient);
|
||||
m_titleScreen = make_shared<TitleScreen>(m_playerStorage, m_mainMixer->mixer());
|
||||
if (auto renderer = Application::renderer())
|
||||
m_titleScreen->renderInit(renderer);
|
||||
}
|
||||
|
||||
if (m_state == MainAppState::Title) {
|
||||
auto configuration = m_root->configuration();
|
||||
|
||||
if (m_pendingMultiPlayerConnection) {
|
||||
if (auto address = m_pendingMultiPlayerConnection->server.ptr<HostAddressWithPort>()) {
|
||||
m_titleScreen->setMultiPlayerAddress(toString(address->address()));
|
||||
m_titleScreen->setMultiPlayerPort(toString(address->port()));
|
||||
m_titleScreen->setMultiPlayerAccount(configuration->getPath("title.multiPlayerAccount").toString());
|
||||
m_titleScreen->goToMultiPlayerSelectCharacter(false);
|
||||
} else {
|
||||
m_titleScreen->goToMultiPlayerSelectCharacter(true);
|
||||
}
|
||||
} else {
|
||||
m_titleScreen->setMultiPlayerAddress(configuration->getPath("title.multiPlayerAddress").toString());
|
||||
m_titleScreen->setMultiPlayerPort(configuration->getPath("title.multiPlayerPort").toString());
|
||||
m_titleScreen->setMultiPlayerAccount(configuration->getPath("title.multiPlayerAccount").toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (m_state > MainAppState::Title) {
|
||||
if (m_titleScreen->currentlySelectedPlayer()) {
|
||||
m_player = m_titleScreen->currentlySelectedPlayer();
|
||||
} else {
|
||||
if (auto uuid = m_playerStorage->playerUuidAt(0))
|
||||
m_player = m_playerStorage->loadPlayer(*uuid);
|
||||
|
||||
if (!m_player) {
|
||||
setError("Error loading player!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_universeClient->setMainPlayer(m_player);
|
||||
m_cinematicOverlay->setPlayer(m_player);
|
||||
|
||||
auto assets = m_root->assets();
|
||||
String loadingCinematic = assets->json("/client.config:loadingCinematic").toString();
|
||||
m_cinematicOverlay->load(assets->json(loadingCinematic));
|
||||
if (!m_player->log()->introComplete()) {
|
||||
String introCinematic = assets->json("/client.config:introCinematic").toString();
|
||||
introCinematic = introCinematic.replaceTags(StringMap<String>{{"species", m_player->species()}});
|
||||
m_player->setPendingCinematic(Json(introCinematic));
|
||||
} else {
|
||||
m_player->setPendingCinematic(Json());
|
||||
}
|
||||
|
||||
if (m_state == MainAppState::MultiPlayer) {
|
||||
PacketSocketUPtr packetSocket;
|
||||
|
||||
auto multiPlayerConnection = m_pendingMultiPlayerConnection.take();
|
||||
|
||||
if (auto address = multiPlayerConnection.server.ptr<HostAddressWithPort>()) {
|
||||
try {
|
||||
packetSocket = TcpPacketSocket::open(TcpSocket::connectTo(*address));
|
||||
} catch (StarException const& e) {
|
||||
setError(strf("Join failed! Error connecting to '%s'", *address), e);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
auto p2pPeerId = multiPlayerConnection.server.ptr<P2PNetworkingPeerId>();
|
||||
|
||||
if (auto p2pNetworkingService = appController()->p2pNetworkingService()) {
|
||||
auto result = p2pNetworkingService->connectToPeer(*p2pPeerId);
|
||||
if (result.isLeft()) {
|
||||
setError(strf("Cannot join peer: %s", result.left()));
|
||||
return;
|
||||
} else {
|
||||
packetSocket = P2PPacketSocket::open(move(result.right()));
|
||||
}
|
||||
} else {
|
||||
setError("Internal error, no p2p networking service when joining p2p networking peer");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool allowAssetsMismatch = m_root->configuration()->get("allowAssetsMismatch").toBool();
|
||||
if (auto errorMessage = m_universeClient->connect(UniverseConnection(move(packetSocket)), allowAssetsMismatch,
|
||||
multiPlayerConnection.account, multiPlayerConnection.password)) {
|
||||
setError(*errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto address = multiPlayerConnection.server.ptr<HostAddressWithPort>())
|
||||
m_currentRemoteJoin = *address;
|
||||
else
|
||||
m_currentRemoteJoin.reset();
|
||||
|
||||
} else {
|
||||
if (!m_universeServer) {
|
||||
try {
|
||||
m_universeServer = make_shared<UniverseServer>(m_root->toStoragePath("universe"));
|
||||
m_universeServer->start();
|
||||
} catch (StarException const& e) {
|
||||
setError("Unable to start local server", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto errorMessage = m_universeClient->connect(m_universeServer->addLocalClient(), "", "")) {
|
||||
setError(strf("Error connecting locally: %s", *errorMessage));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_titleScreen->stopMusic();
|
||||
|
||||
m_worldPainter = make_shared<WorldPainter>();
|
||||
m_mainInterface = make_shared<MainInterface>(m_universeClient, m_worldPainter, m_cinematicOverlay);
|
||||
|
||||
if (auto renderer = Application::renderer()) {
|
||||
m_worldPainter->renderInit(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientApplication::setError(String const& error) {
|
||||
Logger::error(error.utf8Ptr());
|
||||
m_errorScreen->setMessage(error);
|
||||
changeState(MainAppState::Error);
|
||||
}
|
||||
|
||||
void ClientApplication::setError(String const& error, std::exception const& e) {
|
||||
Logger::error("%s\n%s", error, outputException(e, true));
|
||||
m_errorScreen->setMessage(strf("%s\n%s", error, outputException(e, false)));
|
||||
changeState(MainAppState::Error);
|
||||
}
|
||||
|
||||
void ClientApplication::updateMods() {
|
||||
m_cinematicOverlay->update();
|
||||
auto ugcService = appController()->userGeneratedContentService();
|
||||
if (ugcService) {
|
||||
if (ugcService->triggerContentDownload()) {
|
||||
StringList modDirectories;
|
||||
for (auto contentId : ugcService->subscribedContentIds()) {
|
||||
if (auto contentDirectory = ugcService->contentDownloadDirectory(contentId)) {
|
||||
Logger::info("Loading mods from user generated content with id '%s' from directory '%s'", contentId, *contentDirectory);
|
||||
modDirectories.append(*contentDirectory);
|
||||
} else {
|
||||
Logger::warn("User generated content with id '%s' is not available", contentId);
|
||||
}
|
||||
}
|
||||
|
||||
if (modDirectories.empty()) {
|
||||
Logger::info("No subscribed user generated content");
|
||||
changeState(MainAppState::Splash);
|
||||
} else {
|
||||
Logger::info("Reloading to include all user generated content");
|
||||
Root::singleton().reloadWithMods(modDirectories);
|
||||
|
||||
auto configuration = m_root->configuration();
|
||||
auto assets = m_root->assets();
|
||||
|
||||
if (configuration->get("modsWarningShown").optBool().value()) {
|
||||
changeState(MainAppState::Splash);
|
||||
} else {
|
||||
configuration->set("modsWarningShown", true);
|
||||
m_errorScreen->setMessage(assets->json("/interface.config:modsWarningMessage").toString());
|
||||
changeState(MainAppState::ModsWarning);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
changeState(MainAppState::Splash);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientApplication::updateModsWarning() {
|
||||
m_errorScreen->update();
|
||||
|
||||
if (m_errorScreen->accepted())
|
||||
changeState(MainAppState::Splash);
|
||||
}
|
||||
|
||||
void ClientApplication::updateSplash() {
|
||||
m_cinematicOverlay->update();
|
||||
if (!m_rootLoader.isRunning() && (m_cinematicOverlay->completable() || m_cinematicOverlay->completed()))
|
||||
changeState(MainAppState::Title);
|
||||
}
|
||||
|
||||
void ClientApplication::updateError() {
|
||||
m_errorScreen->update();
|
||||
|
||||
if (m_errorScreen->accepted())
|
||||
changeState(MainAppState::Title);
|
||||
}
|
||||
|
||||
void ClientApplication::updateTitle() {
|
||||
m_cinematicOverlay->update();
|
||||
|
||||
m_titleScreen->update();
|
||||
m_mainMixer->update();
|
||||
|
||||
appController()->setAcceptingTextInput(m_titleScreen->textInputActive());
|
||||
|
||||
auto p2pNetworkingService = appController()->p2pNetworkingService();
|
||||
if (p2pNetworkingService)
|
||||
p2pNetworkingService->setActivityData("In Main Menu", {});
|
||||
|
||||
if (m_titleScreen->currentState() == TitleState::StartSinglePlayer) {
|
||||
changeState(MainAppState::SinglePlayer);
|
||||
|
||||
} else if (m_titleScreen->currentState() == TitleState::StartMultiPlayer) {
|
||||
if (!m_pendingMultiPlayerConnection || m_pendingMultiPlayerConnection->server.is<HostAddressWithPort>()) {
|
||||
auto addressString = m_titleScreen->multiPlayerAddress().trim();
|
||||
auto portString = m_titleScreen->multiPlayerPort().trim();
|
||||
portString = portString.empty() ? toString(m_root->configuration()->get("gameServerPort").toUInt()) : portString;
|
||||
if (auto port = maybeLexicalCast<uint16_t>(portString)) {
|
||||
auto address = HostAddressWithPort::lookup(addressString, *port);
|
||||
if (address.isLeft()) {
|
||||
setError(address.left());
|
||||
} else {
|
||||
m_pendingMultiPlayerConnection = PendingMultiPlayerConnection{
|
||||
address.right(),
|
||||
m_titleScreen->multiPlayerAccount(),
|
||||
m_titleScreen->multiPlayerPassword()
|
||||
};
|
||||
|
||||
auto configuration = m_root->configuration();
|
||||
configuration->setPath("title.multiPlayerAddress", m_titleScreen->multiPlayerAddress());
|
||||
configuration->setPath("title.multiPlayerPort", m_titleScreen->multiPlayerPort());
|
||||
configuration->setPath("title.multiPlayerAccount", m_titleScreen->multiPlayerAccount());
|
||||
|
||||
changeState(MainAppState::MultiPlayer);
|
||||
}
|
||||
} else {
|
||||
setError(strf("invalid port: %s", portString));
|
||||
}
|
||||
} else {
|
||||
changeState(MainAppState::MultiPlayer);
|
||||
}
|
||||
|
||||
} else if (m_titleScreen->currentState() == TitleState::Quit) {
|
||||
changeState(MainAppState::Quit);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientApplication::updateRunning() {
|
||||
try {
|
||||
auto p2pNetworkingService = appController()->p2pNetworkingService();
|
||||
bool clientIPJoinable = m_root->configuration()->get("clientIPJoinable").toBool();
|
||||
bool clientP2PJoinable = m_root->configuration()->get("clientP2PJoinable").toBool();
|
||||
Maybe<pair<uint16_t, uint16_t>> party = make_pair(m_universeClient->players(), m_universeClient->maxPlayers());
|
||||
|
||||
if (m_state == MainAppState::MultiPlayer) {
|
||||
if (p2pNetworkingService) {
|
||||
p2pNetworkingService->setAcceptingP2PConnections(false);
|
||||
if (clientP2PJoinable && m_currentRemoteJoin)
|
||||
p2pNetworkingService->setJoinRemote(*m_currentRemoteJoin);
|
||||
else
|
||||
p2pNetworkingService->setJoinUnavailable();
|
||||
}
|
||||
} else {
|
||||
m_universeServer->setListeningTcp(clientIPJoinable);
|
||||
if (p2pNetworkingService) {
|
||||
p2pNetworkingService->setAcceptingP2PConnections(clientP2PJoinable);
|
||||
if (clientP2PJoinable) {
|
||||
p2pNetworkingService->setJoinLocal(m_universeServer->maxClients());
|
||||
} else {
|
||||
p2pNetworkingService->setJoinUnavailable();
|
||||
party = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p2pNetworkingService)
|
||||
p2pNetworkingService->setActivityData("In Game", party);
|
||||
|
||||
if (!m_mainInterface->inputFocus() && !m_cinematicOverlay->suppressInput()) {
|
||||
m_player->setShifting(isActionTaken(InterfaceAction::PlayerShifting));
|
||||
|
||||
if (isActionTaken(InterfaceAction::PlayerRight))
|
||||
m_player->moveRight();
|
||||
if (isActionTaken(InterfaceAction::PlayerLeft))
|
||||
m_player->moveLeft();
|
||||
if (isActionTaken(InterfaceAction::PlayerUp))
|
||||
m_player->moveUp();
|
||||
if (isActionTaken(InterfaceAction::PlayerDown))
|
||||
m_player->moveDown();
|
||||
if (isActionTaken(InterfaceAction::PlayerJump))
|
||||
m_player->jump();
|
||||
|
||||
if (isActionTaken(InterfaceAction::PlayerTechAction1))
|
||||
m_player->special(1);
|
||||
if (isActionTaken(InterfaceAction::PlayerTechAction2))
|
||||
m_player->special(2);
|
||||
if (isActionTaken(InterfaceAction::PlayerTechAction3))
|
||||
m_player->special(3);
|
||||
|
||||
if (isActionTakenEdge(InterfaceAction::PlayerInteract))
|
||||
m_player->beginTrigger();
|
||||
else if (!isActionTaken(InterfaceAction::PlayerInteract))
|
||||
m_player->endTrigger();
|
||||
|
||||
if (isActionTakenEdge(InterfaceAction::PlayerDropItem))
|
||||
m_player->dropItem();
|
||||
|
||||
if (isActionTakenEdge(InterfaceAction::EmoteBlabbering))
|
||||
m_player->addEmote(HumanoidEmote::Blabbering);
|
||||
if (isActionTakenEdge(InterfaceAction::EmoteShouting))
|
||||
m_player->addEmote(HumanoidEmote::Shouting);
|
||||
if (isActionTakenEdge(InterfaceAction::EmoteHappy))
|
||||
m_player->addEmote(HumanoidEmote::Happy);
|
||||
if (isActionTakenEdge(InterfaceAction::EmoteSad))
|
||||
m_player->addEmote(HumanoidEmote::Sad);
|
||||
if (isActionTakenEdge(InterfaceAction::EmoteNeutral))
|
||||
m_player->addEmote(HumanoidEmote::NEUTRAL);
|
||||
if (isActionTakenEdge(InterfaceAction::EmoteLaugh))
|
||||
m_player->addEmote(HumanoidEmote::Laugh);
|
||||
if (isActionTakenEdge(InterfaceAction::EmoteAnnoyed))
|
||||
m_player->addEmote(HumanoidEmote::Annoyed);
|
||||
if (isActionTakenEdge(InterfaceAction::EmoteOh))
|
||||
m_player->addEmote(HumanoidEmote::Oh);
|
||||
if (isActionTakenEdge(InterfaceAction::EmoteOooh))
|
||||
m_player->addEmote(HumanoidEmote::OOOH);
|
||||
if (isActionTakenEdge(InterfaceAction::EmoteBlink))
|
||||
m_player->addEmote(HumanoidEmote::Blink);
|
||||
if (isActionTakenEdge(InterfaceAction::EmoteWink))
|
||||
m_player->addEmote(HumanoidEmote::Wink);
|
||||
if (isActionTakenEdge(InterfaceAction::EmoteEat))
|
||||
m_player->addEmote(HumanoidEmote::Eat);
|
||||
if (isActionTakenEdge(InterfaceAction::EmoteSleep))
|
||||
m_player->addEmote(HumanoidEmote::Sleep);
|
||||
}
|
||||
|
||||
auto checkDisconnection = [this]() {
|
||||
if (!m_universeClient->isConnected()) {
|
||||
m_cinematicOverlay->stop();
|
||||
String errMessage;
|
||||
if (auto disconnectReason = m_universeClient->disconnectReason())
|
||||
errMessage = strf("You were disconnected from the server for the following reason:\n%s", *disconnectReason);
|
||||
else
|
||||
errMessage = "Client-server connection no longer valid!";
|
||||
Logger::error(errMessage.utf8Ptr());
|
||||
m_errorScreen->setMessage(errMessage);
|
||||
changeState(MainAppState::Error);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if (checkDisconnection())
|
||||
return;
|
||||
|
||||
m_universeClient->update();
|
||||
|
||||
if (checkDisconnection())
|
||||
return;
|
||||
|
||||
if (auto worldClient = m_universeClient->worldClient())
|
||||
worldClient->setInteractiveHighlightMode(isActionTaken(InterfaceAction::ShowLabels));
|
||||
|
||||
updateCamera();
|
||||
|
||||
m_cinematicOverlay->update();
|
||||
m_mainInterface->update();
|
||||
m_mainMixer->update(m_cinematicOverlay->muteSfx(), m_cinematicOverlay->muteMusic());
|
||||
|
||||
appController()->setAcceptingTextInput(m_mainInterface->textInputActive());
|
||||
|
||||
for (auto const& interactAction : m_player->pullInteractActions())
|
||||
m_mainInterface->handleInteractAction(interactAction);
|
||||
|
||||
if (m_universeServer) {
|
||||
if (auto p2pNetworkingService = appController()->p2pNetworkingService()) {
|
||||
for (auto& p2pClient : p2pNetworkingService->acceptP2PConnections())
|
||||
m_universeServer->addClient(UniverseConnection(P2PPacketSocket::open(move(p2pClient))));
|
||||
}
|
||||
|
||||
m_universeServer->setPause(m_mainInterface->escapeDialogOpen());
|
||||
}
|
||||
|
||||
Vec2F aimPosition = m_player->aimPosition();
|
||||
LogMap::set("render_fps", appController()->renderFps());
|
||||
LogMap::set("update_rate", appController()->updateRate());
|
||||
LogMap::set("player_pos", strf("%4.2f %4.2f", m_player->position()[0], m_player->position()[1]));
|
||||
LogMap::set("player_vel", strf("%4.2f %4.2f", m_player->velocity()[0], m_player->velocity()[1]));
|
||||
LogMap::set("player_aim", strf("%4.2f %4.2f", aimPosition[0], aimPosition[1]));
|
||||
if (m_universeClient->worldClient()) {
|
||||
LogMap::set("liquid_level", strf("%d", m_universeClient->worldClient()->liquidLevel(Vec2I::floor(aimPosition)).level));
|
||||
LogMap::set("dungeonId", strf("%d", m_universeClient->worldClient()->dungeonId(Vec2I::floor(aimPosition))));
|
||||
}
|
||||
|
||||
if (m_mainInterface->currentState() == MainInterface::ReturnToTitle)
|
||||
changeState(MainAppState::Title);
|
||||
|
||||
} catch (std::exception& e) {
|
||||
setError("Exception caught in client main-loop", e);
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientApplication::isActionTaken(InterfaceAction action) const {
|
||||
for (auto keyEvent : m_heldKeyEvents) {
|
||||
if (m_guiContext->actions(keyEvent).contains(action))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ClientApplication::isActionTakenEdge(InterfaceAction action) const {
|
||||
for (auto keyEvent : m_edgeKeyEvents) {
|
||||
if (m_guiContext->actions(keyEvent).contains(action))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ClientApplication::updateCamera() {
|
||||
if (!m_universeClient->worldClient())
|
||||
return;
|
||||
|
||||
if (m_mainInterface->fixedCamera())
|
||||
return;
|
||||
|
||||
auto assets = m_root->assets();
|
||||
auto camera = m_worldPainter->camera();
|
||||
|
||||
const float triggerRadius = 100.0f;
|
||||
const float deadzone = 0.1f;
|
||||
const float smoothFactor = 30.0f;
|
||||
const float panFactor = 1.5f;
|
||||
|
||||
auto playerCameraPosition = m_player->cameraPosition();
|
||||
|
||||
if (isActionTaken(InterfaceAction::CameraShift)) {
|
||||
m_snapBackCameraOffset = false;
|
||||
m_cameraOffsetDownTicks++;
|
||||
Vec2F aim = m_universeClient->worldClient()->geometry().diff(m_mainInterface->cursorWorldPosition(), playerCameraPosition);
|
||||
|
||||
float magnitude = aim.magnitude() / (triggerRadius / camera.pixelRatio());
|
||||
if (magnitude > deadzone) {
|
||||
float cameraXOffset = aim.x() / magnitude;
|
||||
float cameraYOffset = aim.y() / magnitude;
|
||||
magnitude = (magnitude - deadzone) / (1.0 - deadzone);
|
||||
if (magnitude > 1)
|
||||
magnitude = 1;
|
||||
cameraXOffset *= magnitude * 0.5f * camera.pixelRatio() * panFactor;
|
||||
cameraYOffset *= magnitude * 0.5f * camera.pixelRatio() * panFactor;
|
||||
m_cameraXOffset = (m_cameraXOffset * (smoothFactor - 1.0) + cameraXOffset) / smoothFactor;
|
||||
m_cameraYOffset = (m_cameraYOffset * (smoothFactor - 1.0) + cameraYOffset) / smoothFactor;
|
||||
}
|
||||
} else {
|
||||
if ((m_cameraOffsetDownTicks > 0) && (m_cameraOffsetDownTicks < 20))
|
||||
m_snapBackCameraOffset = true;
|
||||
if (m_snapBackCameraOffset) {
|
||||
m_cameraXOffset = (m_cameraXOffset * (smoothFactor - 1.0)) / smoothFactor;
|
||||
m_cameraYOffset = (m_cameraYOffset * (smoothFactor - 1.0)) / smoothFactor;
|
||||
}
|
||||
m_cameraOffsetDownTicks = 0;
|
||||
}
|
||||
Vec2F newCameraPosition;
|
||||
|
||||
newCameraPosition.setX(playerCameraPosition.x());
|
||||
newCameraPosition.setY(playerCameraPosition.y());
|
||||
|
||||
auto baseCamera = newCameraPosition;
|
||||
|
||||
const float cameraSmoothRadius = assets->json("/interface.config:cameraSmoothRadius").toFloat();
|
||||
const float cameraSmoothFactor = assets->json("/interface.config:cameraSmoothFactor").toFloat();
|
||||
|
||||
auto cameraSmoothDistance = m_universeClient->worldClient()->geometry().diff(m_cameraPositionSmoother, newCameraPosition).magnitude();
|
||||
if (cameraSmoothDistance > cameraSmoothRadius) {
|
||||
auto cameraDelta = m_universeClient->worldClient()->geometry().diff(m_cameraPositionSmoother, newCameraPosition);
|
||||
m_cameraPositionSmoother = newCameraPosition + cameraDelta.normalized() * cameraSmoothRadius;
|
||||
m_cameraSmoothDelta = {};
|
||||
}
|
||||
|
||||
auto cameraDelta = m_universeClient->worldClient()->geometry().diff(m_cameraPositionSmoother, newCameraPosition);
|
||||
if (cameraDelta.magnitude() > assets->json("/interface.config:cameraSmoothDeadzone").toFloat())
|
||||
newCameraPosition = newCameraPosition + cameraDelta * (cameraSmoothFactor - 1.0) / cameraSmoothFactor;
|
||||
m_cameraPositionSmoother = newCameraPosition;
|
||||
|
||||
newCameraPosition.setX(newCameraPosition.x() + m_cameraXOffset / camera.pixelRatio());
|
||||
newCameraPosition.setY(newCameraPosition.y() + m_cameraYOffset / camera.pixelRatio());
|
||||
|
||||
auto smoothDelta = newCameraPosition - baseCamera;
|
||||
|
||||
m_worldPainter->setCameraPosition(m_universeClient->worldClient()->geometry(), baseCamera + (smoothDelta + m_cameraSmoothDelta) * 0.5f);
|
||||
m_cameraSmoothDelta = smoothDelta;
|
||||
camera = m_worldPainter->camera();
|
||||
|
||||
m_universeClient->worldClient()->setClientWindow(camera.worldTileRect());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
STAR_MAIN_APPLICATION(Star::ClientApplication);
|
||||
117
source/client/StarClientApplication.hpp
Normal file
117
source/client/StarClientApplication.hpp
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
#ifndef STAR_CLIENT_APPLICATION_HPP
|
||||
#define STAR_CLIENT_APPLICATION_HPP
|
||||
|
||||
#include "StarUniverseServer.hpp"
|
||||
#include "StarUniverseClient.hpp"
|
||||
#include "StarWorldPainter.hpp"
|
||||
#include "StarGameTypes.hpp"
|
||||
#include "StarMainInterface.hpp"
|
||||
#include "StarMainMixer.hpp"
|
||||
#include "StarTitleScreen.hpp"
|
||||
#include "StarErrorScreen.hpp"
|
||||
#include "StarCinematic.hpp"
|
||||
#include "StarKeyBindings.hpp"
|
||||
#include "StarMainApplication.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
class ClientApplication : public Application {
|
||||
protected:
|
||||
virtual void startup(StringList const& cmdLineArgs) override;
|
||||
virtual void shutdown() override;
|
||||
|
||||
virtual void applicationInit(ApplicationControllerPtr appController) override;
|
||||
virtual void renderInit(RendererPtr renderer) override;
|
||||
|
||||
virtual void windowChanged(WindowMode windowMode, Vec2U screenSize) override;
|
||||
|
||||
virtual void processInput(InputEvent const& event) override;
|
||||
|
||||
virtual void update() override;
|
||||
virtual void render() override;
|
||||
|
||||
virtual void getAudioData(int16_t* stream, size_t len) override;
|
||||
|
||||
private:
|
||||
enum class MainAppState {
|
||||
Quit,
|
||||
Startup,
|
||||
Mods,
|
||||
ModsWarning,
|
||||
Splash,
|
||||
Error,
|
||||
Title,
|
||||
SinglePlayer,
|
||||
MultiPlayer
|
||||
};
|
||||
|
||||
struct PendingMultiPlayerConnection {
|
||||
Variant<P2PNetworkingPeerId, HostAddressWithPort> server;
|
||||
String account;
|
||||
String password;
|
||||
};
|
||||
|
||||
void changeState(MainAppState newState);
|
||||
void setError(String const& error);
|
||||
void setError(String const& error, std::exception const& e);
|
||||
|
||||
void updateMods();
|
||||
void updateModsWarning();
|
||||
void updateSplash();
|
||||
void updateError();
|
||||
void updateTitle();
|
||||
void updateRunning();
|
||||
|
||||
bool isActionTaken(InterfaceAction action) const;
|
||||
bool isActionTakenEdge(InterfaceAction action) const;
|
||||
|
||||
void updateCamera();
|
||||
|
||||
RootUPtr m_root;
|
||||
ThreadFunction<void> m_rootLoader;
|
||||
MainAppState m_state = MainAppState::Startup;
|
||||
|
||||
// Valid after applicationInit is called
|
||||
MainMixerPtr m_mainMixer;
|
||||
GuiContextPtr m_guiContext;
|
||||
|
||||
// Valid after renderInit is called the first time
|
||||
CinematicPtr m_cinematicOverlay;
|
||||
ErrorScreenPtr m_errorScreen;
|
||||
|
||||
// Valid if main app state >= Title
|
||||
PlayerStoragePtr m_playerStorage;
|
||||
StatisticsPtr m_statistics;
|
||||
UniverseClientPtr m_universeClient;
|
||||
TitleScreenPtr m_titleScreen;
|
||||
|
||||
// Valid if main app state > Title
|
||||
PlayerPtr m_player;
|
||||
WorldPainterPtr m_worldPainter;
|
||||
WorldRenderData m_renderData;
|
||||
MainInterfacePtr m_mainInterface;
|
||||
|
||||
// Valid if main app state == SinglePlayer
|
||||
UniverseServerPtr m_universeServer;
|
||||
|
||||
float m_cameraXOffset = 0.0f;
|
||||
float m_cameraYOffset = 0.0f;
|
||||
bool m_snapBackCameraOffset = false;
|
||||
int m_cameraOffsetDownTicks = 0;
|
||||
Vec2F m_cameraPositionSmoother;
|
||||
Vec2F m_cameraSmoothDelta;
|
||||
|
||||
int m_minInterfaceScale = 2;
|
||||
int m_maxInterfaceScale = 3;
|
||||
Vec2F m_crossoverRes;
|
||||
|
||||
List<KeyDownEvent> m_heldKeyEvents;
|
||||
List<KeyDownEvent> m_edgeKeyEvents;
|
||||
|
||||
Maybe<PendingMultiPlayerConnection> m_pendingMultiPlayerConnection;
|
||||
Maybe<HostAddressWithPort> m_currentRemoteJoin;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
BIN
source/client/starbound-largelogo.ico
Normal file
BIN
source/client/starbound-largelogo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 361 KiB |
18
source/client/starbound.exe.manifest
Normal file
18
source/client/starbound.exe.manifest
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!--Windows 7-->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
<!--Windows Vista-->
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
BIN
source/client/starbound.ico
Normal file
BIN
source/client/starbound.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 361 KiB |
25
source/client/starbound.rc
Normal file
25
source/client/starbound.rc
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
1 VERSIONINFO
|
||||
FILEVERSION 0,9,0,0
|
||||
PRODUCTVERSION 0,9,0,0
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904E4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Chucklefish LTD"
|
||||
VALUE "FileDescription", "Starbound"
|
||||
VALUE "FileVersion", "0.9beta"
|
||||
VALUE "InternalName", "starbound"
|
||||
VALUE "LegalCopyright", "Chucklefish LTD"
|
||||
VALUE "OriginalFilename", "starbound.exe"
|
||||
VALUE "ProductName", "Starbound"
|
||||
VALUE "ProductVersion", "0.9beta"
|
||||
END
|
||||
END
|
||||
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1252
|
||||
END
|
||||
END
|
||||
icon ICON "starbound-largelogo.ico"
|
||||
201
source/core/CMakeLists.txt
Normal file
201
source/core/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
INCLUDE_DIRECTORIES (
|
||||
${STAR_EXTERN_INCLUDES}
|
||||
${STAR_CORE_INCLUDES}
|
||||
)
|
||||
|
||||
SET (star_core_HEADERS
|
||||
StarAStar.hpp
|
||||
StarAlgorithm.hpp
|
||||
StarArray.hpp
|
||||
StarAtomicSharedPtr.hpp
|
||||
StarAudio.hpp
|
||||
StarBTree.hpp
|
||||
StarBTreeDatabase.hpp
|
||||
StarBiMap.hpp
|
||||
StarBlockAllocator.hpp
|
||||
StarBuffer.hpp
|
||||
StarByteArray.hpp
|
||||
StarBytes.hpp
|
||||
StarCasting.hpp
|
||||
StarColor.hpp
|
||||
StarCompression.hpp
|
||||
StarConfig.hpp
|
||||
StarDataStream.hpp
|
||||
StarDataStreamDevices.hpp
|
||||
StarDataStreamExtra.hpp
|
||||
StarDynamicLib.hpp
|
||||
StarEither.hpp
|
||||
StarEncode.hpp
|
||||
StarException.hpp
|
||||
StarFile.hpp
|
||||
StarFlatHashMap.hpp
|
||||
StarFlatHashSet.hpp
|
||||
StarFont.hpp
|
||||
StarFormat.hpp
|
||||
StarHash.hpp
|
||||
StarHostAddress.hpp
|
||||
StarIODevice.hpp
|
||||
StarIdMap.hpp
|
||||
StarImage.hpp
|
||||
StarImageProcessing.hpp
|
||||
StarInterpolation.hpp
|
||||
StarRefPtr.hpp
|
||||
StarIterator.hpp
|
||||
StarJson.hpp
|
||||
StarJsonBuilder.hpp
|
||||
StarJsonExtra.hpp
|
||||
StarJsonParser.hpp
|
||||
StarJsonPath.hpp
|
||||
StarJsonPatch.hpp
|
||||
StarJsonRpc.hpp
|
||||
StarFormattedJson.hpp
|
||||
StarLexicalCast.hpp
|
||||
StarLine.hpp
|
||||
StarList.hpp
|
||||
StarListener.hpp
|
||||
StarLockFile.hpp
|
||||
StarLogging.hpp
|
||||
StarLruCache.hpp
|
||||
StarLua.hpp
|
||||
StarLuaConverters.hpp
|
||||
StarMap.hpp
|
||||
StarMathCommon.hpp
|
||||
StarMatrix3.hpp
|
||||
StarMaybe.hpp
|
||||
StarMemory.hpp
|
||||
StarMultiArray.hpp
|
||||
StarMultiArrayInterpolator.hpp
|
||||
StarMultiTable.hpp
|
||||
StarNetElement.hpp
|
||||
StarNetElementBasicFields.hpp
|
||||
StarNetElementContainers.hpp
|
||||
StarNetElementDynamicGroup.hpp
|
||||
StarNetElementFloatFields.hpp
|
||||
StarNetElementGroup.hpp
|
||||
StarNetElementSignal.hpp
|
||||
StarNetElementSyncGroup.hpp
|
||||
StarNetElementSystem.hpp
|
||||
StarNetElementTop.hpp
|
||||
StarNetImpl.hpp
|
||||
StarObserverStream.hpp
|
||||
StarOptionParser.hpp
|
||||
StarOrderedMap.hpp
|
||||
StarOrderedSet.hpp
|
||||
StarParametricFunction.hpp
|
||||
StarPeriodic.hpp
|
||||
StarPeriodicFunction.hpp
|
||||
StarPerlin.hpp
|
||||
StarPoly.hpp
|
||||
StarPythonic.hpp
|
||||
StarRandom.hpp
|
||||
StarRandomPoint.hpp
|
||||
StarRect.hpp
|
||||
StarRpcPromise.hpp
|
||||
StarSectorArray2D.hpp
|
||||
StarSecureRandom.hpp
|
||||
StarSet.hpp
|
||||
StarSha256.hpp
|
||||
StarShellParser.hpp
|
||||
StarSignalHandler.hpp
|
||||
StarSocket.hpp
|
||||
StarSpatialHash2D.hpp
|
||||
StarSpline.hpp
|
||||
StarStaticRandom.hpp
|
||||
StarStaticVector.hpp
|
||||
StarString.hpp
|
||||
StarStrongTypedef.hpp
|
||||
StarTcp.hpp
|
||||
StarThread.hpp
|
||||
StarTickRateMonitor.hpp
|
||||
StarTime.hpp
|
||||
StarTtlCache.hpp
|
||||
StarUdp.hpp
|
||||
StarUnicode.hpp
|
||||
StarUuid.hpp
|
||||
StarVector.hpp
|
||||
StarVlqEncoding.hpp
|
||||
StarWeightedPool.hpp
|
||||
StarWorkerPool.hpp
|
||||
StarXXHash.hpp
|
||||
)
|
||||
|
||||
SET (star_core_SOURCES
|
||||
StarAudio.cpp
|
||||
StarBTreeDatabase.cpp
|
||||
StarBuffer.cpp
|
||||
StarByteArray.cpp
|
||||
StarColor.cpp
|
||||
StarCompression.cpp
|
||||
StarDataStream.cpp
|
||||
StarDataStreamDevices.cpp
|
||||
StarEncode.cpp
|
||||
StarFile.cpp
|
||||
StarFont.cpp
|
||||
StarHostAddress.cpp
|
||||
StarIODevice.cpp
|
||||
StarImage.cpp
|
||||
StarImageProcessing.cpp
|
||||
StarJson.cpp
|
||||
StarJsonBuilder.cpp
|
||||
StarJsonExtra.cpp
|
||||
StarJsonPath.cpp
|
||||
StarJsonPatch.cpp
|
||||
StarJsonRpc.cpp
|
||||
StarFormattedJson.cpp
|
||||
StarListener.cpp
|
||||
StarLogging.cpp
|
||||
StarLua.cpp
|
||||
StarLuaConverters.cpp
|
||||
StarMemory.cpp
|
||||
StarNetElement.cpp
|
||||
StarNetElementBasicFields.cpp
|
||||
StarNetElementGroup.cpp
|
||||
StarNetElementSyncGroup.cpp
|
||||
StarOptionParser.cpp
|
||||
StarPerlin.cpp
|
||||
StarRandom.cpp
|
||||
StarSha256.cpp
|
||||
StarShellParser.cpp
|
||||
StarSocket.cpp
|
||||
StarString.cpp
|
||||
StarTcp.cpp
|
||||
StarThread.cpp
|
||||
StarTime.cpp
|
||||
StarTickRateMonitor.cpp
|
||||
StarUdp.cpp
|
||||
StarUnicode.cpp
|
||||
StarUuid.cpp
|
||||
StarWorkerPool.cpp
|
||||
)
|
||||
|
||||
IF (STAR_SYSTEM_FAMILY_UNIX)
|
||||
SET (star_core_SOURCES ${star_core_SOURCES}
|
||||
StarDynamicLib_unix.cpp
|
||||
StarException_unix.cpp
|
||||
StarFile_unix.cpp
|
||||
StarLockFile_unix.cpp
|
||||
StarSecureRandom_unix.cpp
|
||||
StarSignalHandler_unix.cpp
|
||||
StarThread_unix.cpp
|
||||
StarTime_unix.cpp
|
||||
)
|
||||
ELSEIF (STAR_SYSTEM_FAMILY_WINDOWS)
|
||||
SET (star_core_HEADERS ${star_core_HEADERS}
|
||||
StarString_windows.hpp
|
||||
)
|
||||
|
||||
SET (star_core_SOURCES ${star_core_SOURCES}
|
||||
StarDynamicLib_windows.cpp
|
||||
StarFile_windows.cpp
|
||||
StarLockFile_windows.cpp
|
||||
StarSignalHandler_windows.cpp
|
||||
StarString_windows.cpp
|
||||
StarThread_windows.cpp
|
||||
StarTime_windows.cpp
|
||||
StarException_windows.cpp
|
||||
StarSecureRandom_windows.cpp
|
||||
)
|
||||
|
||||
ENDIF ()
|
||||
|
||||
ADD_LIBRARY (star_core OBJECT ${star_core_SOURCES} ${star_core_HEADERS})
|
||||
276
source/core/StarAStar.hpp
Normal file
276
source/core/StarAStar.hpp
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
#ifndef STAR_A_STAR_HPP
|
||||
#define STAR_A_STAR_HPP
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "StarList.hpp"
|
||||
#include "StarMap.hpp"
|
||||
#include "StarSet.hpp"
|
||||
#include "StarLexicalCast.hpp"
|
||||
#include "StarMathCommon.hpp"
|
||||
#include "StarBlockAllocator.hpp"
|
||||
|
||||
namespace Star {
|
||||
namespace AStar {
|
||||
|
||||
struct Score {
|
||||
Score();
|
||||
|
||||
double gScore;
|
||||
double hScore;
|
||||
double fScore;
|
||||
};
|
||||
|
||||
// 'Edge' should be implemented as a class with public fields compatible with
|
||||
// these:
|
||||
// double cost;
|
||||
// Node source;
|
||||
// Node target;
|
||||
|
||||
template <class Edge>
|
||||
using Path = List<Edge>;
|
||||
|
||||
template <class Edge, class Node>
|
||||
class Search {
|
||||
public:
|
||||
typedef function<double(Node, Node)> HeuristicFunction;
|
||||
typedef function<void(Node, List<Edge>& neighbors)> NeighborFunction;
|
||||
typedef function<bool(Node)> GoalFunction;
|
||||
typedef function<bool(Node, Node)> CompareFunction;
|
||||
typedef function<bool(Edge)> ValidateEndFunction;
|
||||
|
||||
Search(HeuristicFunction heuristicCost,
|
||||
NeighborFunction getAdjacent,
|
||||
GoalFunction goalReached,
|
||||
bool returnBestIfFailed = false,
|
||||
// In returnBestIfFailed mode, validateEnd checks the end of the path
|
||||
// is valid, e.g. not floating in the air.
|
||||
Maybe<ValidateEndFunction> validateEnd = {},
|
||||
Maybe<double> maxFScore = {},
|
||||
Maybe<unsigned> maxNodesToSearch = {});
|
||||
|
||||
// Start a new exploration, resets result if it was found before.
|
||||
void start(Node startNode, Node goalNode);
|
||||
// Explore the given number of nodes in the search space. If
|
||||
// maxNodesToSearch is reached, or the search space is exhausted, will
|
||||
// return
|
||||
// false to signal failure. On success, will return true. If the given
|
||||
// maxExploreNodes is exhausted before success or failure, will return
|
||||
// nothing.
|
||||
Maybe<bool> explore(Maybe<unsigned> maxExploreNodes = {});
|
||||
// Returns the result if it was found.
|
||||
Maybe<Path<Edge>> const& result() const;
|
||||
|
||||
// Convenience, equivalent to calling start, then explore({}) and returns
|
||||
// result()
|
||||
Maybe<Path<Edge>> const& findPath(Node startNode, Node goalNode);
|
||||
|
||||
private:
|
||||
struct ScoredNode {
|
||||
bool operator<(ScoredNode const& other) const {
|
||||
return score.fScore > other.score.fScore;
|
||||
}
|
||||
|
||||
Score score;
|
||||
Node node;
|
||||
};
|
||||
|
||||
struct NodeMeta {
|
||||
Score score;
|
||||
Maybe<Edge> cameFrom;
|
||||
};
|
||||
|
||||
Path<Edge> reconstructPath(Node currentNode);
|
||||
|
||||
HeuristicFunction m_heuristicCost;
|
||||
NeighborFunction m_getAdjacent;
|
||||
GoalFunction m_goalReached;
|
||||
bool m_returnBestIfFailed;
|
||||
Maybe<ValidateEndFunction> m_validateEnd;
|
||||
Maybe<double> m_maxFScore;
|
||||
Maybe<unsigned> m_maxNodesToSearch;
|
||||
|
||||
Node m_goal;
|
||||
Map<Node, NodeMeta, std::less<Node>, BlockAllocator<pair<Node const, NodeMeta>, 1024>> m_nodeMeta;
|
||||
std::priority_queue<ScoredNode> m_openQueue;
|
||||
Set<Node, std::less<Node>, BlockAllocator<Node, 1024>> m_openSet;
|
||||
Set<Node, std::less<Node>, BlockAllocator<Node, 1024>> m_closedSet;
|
||||
Maybe<ScoredNode> m_earlyExploration;
|
||||
|
||||
bool m_finished;
|
||||
Maybe<Path<Edge>> m_result;
|
||||
};
|
||||
|
||||
inline Score::Score() : gScore(highest<double>()), hScore(0), fScore(highest<double>()) {}
|
||||
|
||||
template <class Edge, class Node>
|
||||
Search<Edge, Node>::Search(HeuristicFunction heuristicCost,
|
||||
NeighborFunction getAdjacent,
|
||||
GoalFunction goalReached,
|
||||
bool returnBestIfFailed,
|
||||
Maybe<ValidateEndFunction> validateEnd,
|
||||
Maybe<double> maxFScore,
|
||||
Maybe<unsigned> maxNodesToSearch)
|
||||
: m_heuristicCost(heuristicCost),
|
||||
m_getAdjacent(getAdjacent),
|
||||
m_goalReached(goalReached),
|
||||
m_returnBestIfFailed(returnBestIfFailed),
|
||||
m_validateEnd(validateEnd),
|
||||
m_maxFScore(maxFScore),
|
||||
m_maxNodesToSearch(maxNodesToSearch) {}
|
||||
|
||||
template <class Edge, class Node>
|
||||
void Search<Edge, Node>::start(Node startNode, Node goalNode) {
|
||||
m_goal = move(goalNode);
|
||||
m_nodeMeta.clear();
|
||||
m_openQueue = std::priority_queue<ScoredNode>();
|
||||
m_openSet.clear();
|
||||
m_closedSet.clear();
|
||||
m_earlyExploration = {};
|
||||
m_finished = false;
|
||||
m_result.reset();
|
||||
|
||||
Score startScore;
|
||||
startScore.gScore = 0;
|
||||
startScore.hScore = m_heuristicCost(startNode, m_goal);
|
||||
startScore.fScore = startScore.hScore;
|
||||
m_nodeMeta[startNode].score = startScore;
|
||||
|
||||
m_openSet.insert(startNode);
|
||||
m_openQueue.push(ScoredNode{startScore, move(startNode)});
|
||||
}
|
||||
|
||||
template <class Edge, class Node>
|
||||
Maybe<bool> Search<Edge, Node>::explore(Maybe<unsigned> maxExploreNodes) {
|
||||
if (m_finished)
|
||||
return m_result.isValid();
|
||||
|
||||
List<Edge> neighbors;
|
||||
while (true) {
|
||||
if ((m_maxNodesToSearch && m_closedSet.size() > *m_maxNodesToSearch)
|
||||
|| (m_openQueue.empty() && !m_earlyExploration)) {
|
||||
m_finished = true;
|
||||
// Search failed. Either return the path to the closest node to the
|
||||
// target,
|
||||
// or return nothing.
|
||||
if (m_returnBestIfFailed) {
|
||||
double bestScore = highest<double>();
|
||||
Maybe<Node> bestNode;
|
||||
for (Node node : m_closedSet) {
|
||||
NodeMeta const& nodeMeta = m_nodeMeta[node];
|
||||
if (m_validateEnd && nodeMeta.cameFrom && !(*m_validateEnd)(*nodeMeta.cameFrom))
|
||||
continue;
|
||||
if (nodeMeta.score.hScore < bestScore) {
|
||||
bestScore = nodeMeta.score.hScore;
|
||||
bestNode = node;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestNode)
|
||||
m_result = reconstructPath(*bestNode);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (maxExploreNodes) {
|
||||
if (*maxExploreNodes == 0)
|
||||
return {};
|
||||
--*maxExploreNodes;
|
||||
}
|
||||
|
||||
ScoredNode currentScoredNode;
|
||||
if (m_earlyExploration) {
|
||||
currentScoredNode = m_earlyExploration.take();
|
||||
} else {
|
||||
currentScoredNode = m_openQueue.top();
|
||||
m_openQueue.pop();
|
||||
if (!m_openSet.remove(currentScoredNode.node))
|
||||
// Duplicate entry in the queue due to this node's score being
|
||||
// updated.
|
||||
// Just ignore this node; we've already searched it.
|
||||
continue;
|
||||
}
|
||||
|
||||
Node const& current = currentScoredNode.node;
|
||||
Score const& currentScore = currentScoredNode.score;
|
||||
|
||||
if (m_goalReached(current)) {
|
||||
m_finished = true;
|
||||
m_result = reconstructPath(current);
|
||||
return true;
|
||||
}
|
||||
|
||||
m_closedSet.insert(current);
|
||||
|
||||
neighbors.clear();
|
||||
m_getAdjacent(current, neighbors);
|
||||
|
||||
for (Edge const& edge : neighbors) {
|
||||
if (m_closedSet.find(edge.target) != m_closedSet.end())
|
||||
// We've already visited this node.
|
||||
continue;
|
||||
|
||||
double newGScore = currentScore.gScore + edge.cost;
|
||||
NodeMeta& targetMeta = m_nodeMeta[edge.target];
|
||||
Score& targetScore = targetMeta.score;
|
||||
if (m_openSet.find(edge.target) == m_openSet.end() || newGScore < targetScore.gScore) {
|
||||
targetMeta.cameFrom = edge;
|
||||
targetScore.gScore = newGScore;
|
||||
targetScore.hScore = m_heuristicCost(edge.target, m_goal);
|
||||
targetScore.fScore = targetScore.gScore + targetScore.hScore;
|
||||
|
||||
if (m_maxFScore && targetScore.fScore > *m_maxFScore)
|
||||
continue;
|
||||
|
||||
// Early exploration optimization - no need to add things to the
|
||||
// openQueue/openSet
|
||||
// if they're at least as good as the current node.
|
||||
if (targetScore.fScore <= currentScore.fScore) {
|
||||
if (m_earlyExploration.isNothing()) {
|
||||
m_earlyExploration = ScoredNode{targetScore, edge.target};
|
||||
continue;
|
||||
} else if (m_earlyExploration->score.fScore > targetScore.fScore) {
|
||||
m_openSet.insert(m_earlyExploration->node);
|
||||
m_openQueue.push(*m_earlyExploration);
|
||||
m_earlyExploration = ScoredNode{targetScore, edge.target};
|
||||
continue;
|
||||
}
|
||||
}
|
||||
m_openSet.insert(edge.target);
|
||||
m_openQueue.push(ScoredNode{targetScore, edge.target});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Edge, class Node>
|
||||
Maybe<Path<Edge>> const& Search<Edge, Node>::result() const {
|
||||
return m_result;
|
||||
}
|
||||
|
||||
template <class Edge, class Node>
|
||||
Maybe<Path<Edge>> const& Search<Edge, Node>::findPath(Node startNode, Node goalNode) {
|
||||
start(move(startNode), move(goalNode));
|
||||
explore();
|
||||
return result();
|
||||
}
|
||||
|
||||
template <class Edge, class Node>
|
||||
Path<Edge> Search<Edge, Node>::reconstructPath(Node currentNode) {
|
||||
Path<Edge> res; // this will be backwards, we reverse it before returning it.
|
||||
while (m_nodeMeta.find(currentNode) != m_nodeMeta.end()) {
|
||||
Maybe<Edge> currentEdge = m_nodeMeta[currentNode].cameFrom;
|
||||
if (currentEdge.isNothing())
|
||||
break;
|
||||
res.append(*currentEdge);
|
||||
currentNode = currentEdge->source;
|
||||
}
|
||||
std::reverse(res.begin(), res.end());
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
667
source/core/StarAlgorithm.hpp
Normal file
667
source/core/StarAlgorithm.hpp
Normal file
|
|
@ -0,0 +1,667 @@
|
|||
#ifndef STAR_ALGORITHM_HPP
|
||||
#define STAR_ALGORITHM_HPP
|
||||
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
|
||||
#include "StarException.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// Function that does nothing and takes any number of arguments
|
||||
template <typename... T>
|
||||
void nothing(T&&...) {}
|
||||
|
||||
// Functional constructor call / casting.
|
||||
template <typename ToType>
|
||||
struct construct {
|
||||
template <typename... FromTypes>
|
||||
ToType operator()(FromTypes&&... fromTypes) const {
|
||||
return ToType(forward<FromTypes>(fromTypes)...);
|
||||
}
|
||||
};
|
||||
|
||||
struct identity {
|
||||
template <typename U>
|
||||
constexpr decltype(auto) operator()(U&& v) const {
|
||||
return std::forward<U>(v);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
struct SwallowReturn {
|
||||
template <typename... T>
|
||||
void operator()(T&&... args) {
|
||||
func(forward<T>(args)...);
|
||||
}
|
||||
|
||||
Func func;
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
SwallowReturn<Func> swallow(Func f) {
|
||||
return SwallowReturn<Func>{move(f)};
|
||||
}
|
||||
|
||||
struct Empty {
|
||||
bool operator==(Empty const) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator<(Empty const) const {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Compose arbitrary functions
|
||||
template <typename FirstFunction, typename SecondFunction>
|
||||
struct FunctionComposer {
|
||||
FirstFunction f1;
|
||||
SecondFunction f2;
|
||||
|
||||
template <typename... T>
|
||||
decltype(auto) operator()(T&&... args) {
|
||||
return f1(f2(forward<T>(args)...));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename FirstFunction, typename SecondFunction>
|
||||
decltype(auto) compose(FirstFunction&& firstFunction, SecondFunction&& secondFunction) {
|
||||
return FunctionComposer<FirstFunction, SecondFunction>{move(forward<FirstFunction>(firstFunction)), move(forward<SecondFunction>(secondFunction))};
|
||||
}
|
||||
|
||||
template <typename FirstFunction, typename SecondFunction, typename ThirdFunction, typename... RestFunctions>
|
||||
decltype(auto) compose(FirstFunction firstFunction, SecondFunction secondFunction, ThirdFunction thirdFunction, RestFunctions... restFunctions) {
|
||||
return compose(forward<FirstFunction>(firstFunction), compose(forward<SecondFunction>(secondFunction), compose(forward<ThirdFunction>(thirdFunction), forward<RestFunctions>(restFunctions)...)));
|
||||
}
|
||||
|
||||
template <typename Container, typename Value, typename Function>
|
||||
Value fold(Container const& l, Value v, Function f) {
|
||||
auto i = l.begin();
|
||||
auto e = l.end();
|
||||
while (i != e) {
|
||||
v = f(v, *i);
|
||||
++i;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// Like fold, but returns default value when container is empty.
|
||||
template <typename Container, typename Function>
|
||||
typename Container::value_type fold1(Container const& l, Function f) {
|
||||
typename Container::value_type res = {};
|
||||
typename Container::const_iterator i = l.begin();
|
||||
typename Container::const_iterator e = l.end();
|
||||
|
||||
if (i == e)
|
||||
return res;
|
||||
|
||||
res = *i;
|
||||
++i;
|
||||
while (i != e) {
|
||||
res = f(res, *i);
|
||||
++i;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Return intersection of sorted containers.
|
||||
template <typename Container>
|
||||
Container intersect(Container const& a, Container const& b) {
|
||||
Container r;
|
||||
std::set_intersection(a.begin(), a.end(), b.begin(), b.end(), std::inserter(r, r.end()));
|
||||
return r;
|
||||
}
|
||||
|
||||
template <typename MapType1, typename MapType2>
|
||||
bool mapMerge(MapType1& targetMap, MapType2 const& sourceMap, bool overwrite = false) {
|
||||
bool noCommonKeys = true;
|
||||
for (auto i = sourceMap.begin(); i != sourceMap.end(); ++i) {
|
||||
auto res = targetMap.insert(*i);
|
||||
if (!res.second) {
|
||||
noCommonKeys = false;
|
||||
if (overwrite)
|
||||
res.first->second = i->second;
|
||||
}
|
||||
}
|
||||
return noCommonKeys;
|
||||
}
|
||||
|
||||
template <typename MapType1, typename MapType2>
|
||||
bool mapsEqual(MapType1 const& m1, MapType2 const& m2) {
|
||||
if (&m1 == &m2)
|
||||
return true;
|
||||
|
||||
if (m1.size() != m2.size())
|
||||
return false;
|
||||
|
||||
for (auto const& m1pair : m1) {
|
||||
auto m2it = m2.find(m1pair.first);
|
||||
if (m2it == m2.end() || !(m2it->second == m1pair.second))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Container, typename Filter>
|
||||
void filter(Container& container, Filter&& filter) {
|
||||
auto p = std::begin(container);
|
||||
while (p != std::end(container)) {
|
||||
if (!filter(*p))
|
||||
p = container.erase(p);
|
||||
else
|
||||
++p;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutContainer, typename InContainer, typename Filter>
|
||||
OutContainer filtered(InContainer const& input, Filter&& filter) {
|
||||
OutContainer out;
|
||||
auto p = std::begin(input);
|
||||
while (p != std::end(input)) {
|
||||
if (filter(*p))
|
||||
out.insert(out.end(), *p);
|
||||
++p;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename Container, typename Cond>
|
||||
void eraseWhere(Container& container, Cond&& cond) {
|
||||
auto p = std::begin(container);
|
||||
while (p != std::end(container)) {
|
||||
if (cond(*p))
|
||||
p = container.erase(p);
|
||||
else
|
||||
++p;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Container, typename Compare>
|
||||
void sort(Container& c, Compare comp) {
|
||||
std::sort(c.begin(), c.end(), comp);
|
||||
}
|
||||
|
||||
template <typename Container, typename Compare>
|
||||
void stableSort(Container& c, Compare comp) {
|
||||
std::stable_sort(c.begin(), c.end(), comp);
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
void sort(Container& c) {
|
||||
std::sort(c.begin(), c.end(), std::less<typename Container::value_type>());
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
void stableSort(Container& c) {
|
||||
std::stable_sort(c.begin(), c.end(), std::less<typename Container::value_type>());
|
||||
}
|
||||
|
||||
template <typename Container, typename Compare>
|
||||
Container sorted(Container const& c, Compare comp) {
|
||||
auto c2 = c;
|
||||
sort(c2, comp);
|
||||
return c2;
|
||||
}
|
||||
|
||||
template <typename Container, typename Compare>
|
||||
Container stableSorted(Container const& c, Compare comp) {
|
||||
auto c2 = c;
|
||||
sort(c2, comp);
|
||||
return c2;
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
Container sorted(Container const& c) {
|
||||
auto c2 = c;
|
||||
sort(c2);
|
||||
return c2;
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
Container stableSorted(Container const& c) {
|
||||
auto c2 = c;
|
||||
sort(c2);
|
||||
return c2;
|
||||
}
|
||||
|
||||
// Sort a container by the output of a computed value. The computed value is
|
||||
// only computed *once* per item in the container, which is useful both for
|
||||
// when the computed value is costly, and to avoid sorting instability with
|
||||
// floating point values. Container must have size() and operator[], and also
|
||||
// must be constructable with Container(size_t).
|
||||
template <typename Container, typename Getter>
|
||||
void sortByComputedValue(Container& container, Getter&& valueGetter, bool stable = false) {
|
||||
typedef typename Container::value_type ContainerValue;
|
||||
typedef decltype(valueGetter(ContainerValue())) ComputedValue;
|
||||
typedef std::pair<ComputedValue, size_t> ComputedPair;
|
||||
|
||||
size_t containerSize = container.size();
|
||||
|
||||
if (containerSize <= 1)
|
||||
return;
|
||||
|
||||
std::vector<ComputedPair> work(containerSize);
|
||||
for (size_t i = 0; i < containerSize; ++i)
|
||||
work[i] = {valueGetter(container[i]), i};
|
||||
|
||||
auto compare = [](ComputedPair const& a, ComputedPair const& b) { return a.first < b.first; };
|
||||
|
||||
// Sort the comptued values and the associated indexes
|
||||
if (stable)
|
||||
stableSort(work, compare);
|
||||
else
|
||||
sort(work, compare);
|
||||
|
||||
Container result(containerSize);
|
||||
for (size_t i = 0; i < containerSize; ++i)
|
||||
swap(result[i], container[work[i].second]);
|
||||
|
||||
swap(container, result);
|
||||
}
|
||||
|
||||
template <typename Container, typename Getter>
|
||||
void stableSortByComputedValue(Container& container, Getter&& valueGetter) {
|
||||
return sortByComputedValue(container, forward<Getter>(valueGetter), true);
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
void shuffle(Container& c) {
|
||||
std::random_shuffle(c.begin(), c.end());
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
void reverse(Container& c) {
|
||||
std::reverse(c.begin(), c.end());
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
Container reverseCopy(Container c) {
|
||||
reverse(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T copy(T c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
typename Container::value_type sum(Container const& cont) {
|
||||
return fold1(cont, std::plus<typename Container::value_type>());
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
typename Container::value_type product(Container const& cont) {
|
||||
return fold1(cont, std::multiplies<typename Container::value_type>());
|
||||
}
|
||||
|
||||
template <typename OutContainer, typename InContainer, typename Function>
|
||||
void transformInto(OutContainer& outContainer, InContainer&& inContainer, Function&& function) {
|
||||
for (auto&& elem : inContainer) {
|
||||
if (std::is_rvalue_reference<InContainer&&>::value)
|
||||
outContainer.insert(outContainer.end(), function(move(elem)));
|
||||
else
|
||||
outContainer.insert(outContainer.end(), function(elem));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutContainer, typename InContainer, typename Function>
|
||||
OutContainer transform(InContainer&& container, Function&& function) {
|
||||
OutContainer res;
|
||||
transformInto(res, forward<InContainer>(container), forward<Function>(function));
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename OutputContainer, typename Function, typename Container1, typename Container2>
|
||||
OutputContainer zipWith(Function&& function, Container1 const& cont1, Container2 const& cont2) {
|
||||
auto it1 = cont1.begin();
|
||||
auto it2 = cont2.begin();
|
||||
|
||||
OutputContainer out;
|
||||
while (it1 != cont1.end() && it2 != cont2.end()) {
|
||||
out.insert(out.end(), function(*it1, *it2));
|
||||
++it1;
|
||||
++it2;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Moves the given value and into an rvalue. Works whether or not the type has
|
||||
// a valid move constructor or not. Always leaves the given value in its
|
||||
// default constructed state.
|
||||
template <typename T>
|
||||
T take(T& t) {
|
||||
T t2 = move(t);
|
||||
t = T();
|
||||
return t2;
|
||||
}
|
||||
|
||||
template <typename Container1, typename Container2>
|
||||
bool containersEqual(Container1 const& cont1, Container2 const& cont2) {
|
||||
if (cont1.size() != cont2.size())
|
||||
return false;
|
||||
else
|
||||
return std::equal(cont1.begin(), cont1.end(), cont2.begin());
|
||||
}
|
||||
|
||||
// Wraps a unary function to produce an output iterator
|
||||
template <typename UnaryFunction>
|
||||
class FunctionOutputIterator {
|
||||
public:
|
||||
typedef std::output_iterator_tag iterator_category;
|
||||
typedef void value_type;
|
||||
typedef void difference_type;
|
||||
typedef void pointer;
|
||||
typedef void reference;
|
||||
|
||||
class OutputProxy {
|
||||
public:
|
||||
OutputProxy(UnaryFunction& f)
|
||||
: m_function(f) {}
|
||||
|
||||
template <typename T>
|
||||
OutputProxy& operator=(T&& value) {
|
||||
m_function(forward<T>(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
UnaryFunction& m_function;
|
||||
};
|
||||
|
||||
explicit FunctionOutputIterator(UnaryFunction f = UnaryFunction())
|
||||
: m_function(move(f)) {}
|
||||
|
||||
OutputProxy operator*() {
|
||||
return OutputProxy(m_function);
|
||||
}
|
||||
|
||||
FunctionOutputIterator& operator++() {
|
||||
return *this;
|
||||
}
|
||||
|
||||
FunctionOutputIterator operator++(int) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
UnaryFunction m_function;
|
||||
};
|
||||
|
||||
template <typename UnaryFunction>
|
||||
FunctionOutputIterator<UnaryFunction> makeFunctionOutputIterator(UnaryFunction f) {
|
||||
return FunctionOutputIterator<UnaryFunction>(move(f));
|
||||
}
|
||||
|
||||
// Wraps a nullary function to produce an input iterator
|
||||
template <typename NullaryFunction>
|
||||
class FunctionInputIterator {
|
||||
public:
|
||||
typedef std::output_iterator_tag iterator_category;
|
||||
typedef void value_type;
|
||||
typedef void difference_type;
|
||||
typedef void pointer;
|
||||
typedef void reference;
|
||||
|
||||
typedef typename std::result_of<NullaryFunction()>::type FunctionReturnType;
|
||||
|
||||
explicit FunctionInputIterator(NullaryFunction f = {})
|
||||
: m_function(move(f)) {}
|
||||
|
||||
FunctionReturnType operator*() {
|
||||
return m_function();
|
||||
}
|
||||
|
||||
FunctionInputIterator& operator++() {
|
||||
return *this;
|
||||
}
|
||||
|
||||
FunctionInputIterator operator++(int) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
NullaryFunction m_function;
|
||||
};
|
||||
|
||||
template <typename NullaryFunction>
|
||||
FunctionInputIterator<NullaryFunction> makeFunctionInputIterator(NullaryFunction f) {
|
||||
return FunctionInputIterator<NullaryFunction>(move(f));
|
||||
}
|
||||
|
||||
template <typename Iterable>
|
||||
struct ReverseWrapper {
|
||||
private:
|
||||
Iterable& m_iterable;
|
||||
|
||||
public:
|
||||
ReverseWrapper(Iterable& iterable) : m_iterable(iterable) {}
|
||||
|
||||
decltype(auto) begin() const {
|
||||
return std::rbegin(m_iterable);
|
||||
}
|
||||
|
||||
decltype(auto) end() const {
|
||||
return std::rend(m_iterable);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Iterable>
|
||||
ReverseWrapper<Iterable> reverseIterate(Iterable& list) {
|
||||
return ReverseWrapper<Iterable>(list);
|
||||
}
|
||||
|
||||
template <typename Functor>
|
||||
class FinallyGuard {
|
||||
public:
|
||||
FinallyGuard(Functor functor) : functor(move(functor)), dismiss(false) {}
|
||||
|
||||
FinallyGuard(FinallyGuard&& o) : functor(move(o.functor)), dismiss(o.dismiss) {
|
||||
o.cancel();
|
||||
}
|
||||
|
||||
FinallyGuard& operator=(FinallyGuard&& o) {
|
||||
functor = move(o.functor);
|
||||
dismiss = o.dismiss;
|
||||
o.cancel();
|
||||
return *this;
|
||||
}
|
||||
|
||||
~FinallyGuard() {
|
||||
if (!dismiss)
|
||||
functor();
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
dismiss = true;
|
||||
}
|
||||
|
||||
private:
|
||||
Functor functor;
|
||||
bool dismiss;
|
||||
};
|
||||
|
||||
template <typename Functor>
|
||||
FinallyGuard<typename std::decay<Functor>::type> finally(Functor&& f) {
|
||||
return FinallyGuard<Functor>(forward<Functor>(f));
|
||||
}
|
||||
|
||||
// Generates compile time sequences of indexes from MinIndex to MaxIndex
|
||||
|
||||
template <size_t...>
|
||||
struct IndexSequence {};
|
||||
|
||||
template <size_t Min, size_t N, size_t... S>
|
||||
struct GenIndexSequence : GenIndexSequence<Min, N - 1, N - 1, S...> {};
|
||||
|
||||
template <size_t Min, size_t... S>
|
||||
struct GenIndexSequence<Min, Min, S...> {
|
||||
typedef IndexSequence<S...> type;
|
||||
};
|
||||
|
||||
// Apply a tuple as individual arguments to a function
|
||||
|
||||
template <typename Function, typename Tuple, size_t... Indexes>
|
||||
decltype(auto) tupleUnpackFunctionIndexes(Function&& function, Tuple&& args, IndexSequence<Indexes...> const&) {
|
||||
return function(get<Indexes>(forward<Tuple>(args))...);
|
||||
}
|
||||
|
||||
template <typename Function, typename Tuple>
|
||||
decltype(auto) tupleUnpackFunction(Function&& function, Tuple&& args) {
|
||||
return tupleUnpackFunctionIndexes<Function, Tuple>(forward<Function>(function), forward<Tuple>(args),
|
||||
typename GenIndexSequence<0, std::tuple_size<typename std::decay<Tuple>::type>::value>::type());
|
||||
}
|
||||
|
||||
// Apply a function to every element of a tuple. This will NOT happen in a
|
||||
// predictable order!
|
||||
|
||||
template <typename Function, typename Tuple, size_t... Indexes>
|
||||
decltype(auto) tupleApplyFunctionIndexes(Function&& function, Tuple&& args, IndexSequence<Indexes...> const&) {
|
||||
return make_tuple(function(get<Indexes>(forward<Tuple>(args)))...);
|
||||
}
|
||||
|
||||
template <typename Function, typename Tuple>
|
||||
decltype(auto) tupleApplyFunction(Function&& function, Tuple&& args) {
|
||||
return tupleApplyFunctionIndexes<Function, Tuple>(forward<Function>(function), forward<Tuple>(args),
|
||||
typename GenIndexSequence<0, std::tuple_size<typename std::decay<Tuple>::type>::value>::type());
|
||||
}
|
||||
|
||||
// Use this version if you do not care about the return value of the function
|
||||
// or your function returns void. This version DOES happen in a predictable
|
||||
// order, first argument first, last argument last.
|
||||
|
||||
template <typename Function, typename Tuple>
|
||||
void tupleCallFunctionCaller(Function&&, Tuple&&) {}
|
||||
|
||||
template <typename Tuple, typename Function, typename First, typename... Rest>
|
||||
void tupleCallFunctionCaller(Tuple&& t, Function&& function) {
|
||||
tupleCallFunctionCaller<Tuple, Function, Rest...>(forward<Tuple>(t), forward<Function>(function));
|
||||
function(get<sizeof...(Rest)>(forward<Tuple>(t)));
|
||||
}
|
||||
|
||||
template <typename Tuple, typename Function, typename... T>
|
||||
void tupleCallFunctionExpander(Tuple&& t, Function&& function, tuple<T...> const&) {
|
||||
tupleCallFunctionCaller<Tuple, Function, T...>(forward<Tuple>(t), forward<Function>(function));
|
||||
}
|
||||
|
||||
template <typename Tuple, typename Function>
|
||||
void tupleCallFunction(Tuple&& t, Function&& function) {
|
||||
tupleCallFunctionExpander<Tuple, Function>(forward<Tuple>(t), forward<Function>(function), forward<Tuple>(t));
|
||||
}
|
||||
|
||||
// Get a subset of a tuple
|
||||
|
||||
template <typename Tuple, size_t... Indexes>
|
||||
decltype(auto) subTupleIndexes(Tuple&& t, IndexSequence<Indexes...> const&) {
|
||||
return make_tuple(get<Indexes>(forward<Tuple>(t))...);
|
||||
}
|
||||
|
||||
template <size_t Min, size_t Size, typename Tuple>
|
||||
decltype(auto) subTuple(Tuple&& t) {
|
||||
return subTupleIndexes(forward<Tuple>(t), GenIndexSequence<Min, Size>::type());
|
||||
}
|
||||
|
||||
template <size_t Trim, typename Tuple>
|
||||
decltype(auto) trimTuple(Tuple&& t) {
|
||||
return subTupleIndexes(forward<Tuple>(t), typename GenIndexSequence<Trim, std::tuple_size<typename std::decay<Tuple>::type>::value>::type());
|
||||
}
|
||||
|
||||
// Unpack a parameter expansion into a container
|
||||
|
||||
template <typename Container>
|
||||
void unpackVariadicImpl(Container&) {}
|
||||
|
||||
template <typename Container, typename TFirst, typename... TRest>
|
||||
void unpackVariadicImpl(Container& container, TFirst&& tfirst, TRest&&... trest) {
|
||||
container.insert(container.end(), forward<TFirst>(tfirst));
|
||||
unpackVariadicImpl(container, forward<TRest>(trest)...);
|
||||
}
|
||||
|
||||
template <typename Container, typename... T>
|
||||
Container unpackVariadic(T&&... t) {
|
||||
Container c;
|
||||
unpackVariadicImpl(c, forward<T>(t)...);
|
||||
return c;
|
||||
}
|
||||
|
||||
// Call a function on each entry in a variadic parameter set
|
||||
|
||||
template <typename Function>
|
||||
void callFunctionVariadic(Function&&) {}
|
||||
|
||||
template <typename Function, typename Arg1, typename... ArgRest>
|
||||
void callFunctionVariadic(Function&& function, Arg1&& arg1, ArgRest&&... argRest) {
|
||||
function(arg1);
|
||||
callFunctionVariadic(forward<Function>(function), forward<ArgRest>(argRest)...);
|
||||
}
|
||||
|
||||
template <typename... Rest>
|
||||
struct VariadicTypedef;
|
||||
|
||||
template <>
|
||||
struct VariadicTypedef<> {};
|
||||
|
||||
template <typename FirstT, typename... RestT>
|
||||
struct VariadicTypedef<FirstT, RestT...> {
|
||||
typedef FirstT First;
|
||||
typedef VariadicTypedef<RestT...> Rest;
|
||||
};
|
||||
|
||||
// For generic types, directly use the result of the signature of its
|
||||
// 'operator()'
|
||||
template <typename T>
|
||||
struct FunctionTraits : public FunctionTraits<decltype(&T::operator())> {};
|
||||
|
||||
template <typename ReturnType, typename... ArgsTypes>
|
||||
struct FunctionTraits<ReturnType(ArgsTypes...)> {
|
||||
// arity is the number of arguments.
|
||||
static constexpr size_t Arity = sizeof...(ArgsTypes);
|
||||
|
||||
typedef ReturnType Return;
|
||||
|
||||
typedef VariadicTypedef<ArgsTypes...> Args;
|
||||
typedef tuple<ArgsTypes...> ArgTuple;
|
||||
|
||||
template <size_t i>
|
||||
struct Arg {
|
||||
// the i-th argument is equivalent to the i-th tuple element of a tuple
|
||||
// composed of those arguments.
|
||||
typedef typename tuple_element<i, ArgTuple>::type type;
|
||||
};
|
||||
};
|
||||
|
||||
template <typename ReturnType, typename... Args>
|
||||
struct FunctionTraits<ReturnType (*)(Args...)> : public FunctionTraits<ReturnType(Args...)> {};
|
||||
|
||||
template <typename FunctionType>
|
||||
struct FunctionTraits<std::function<FunctionType>> : public FunctionTraits<FunctionType> {};
|
||||
|
||||
template <typename ClassType, typename ReturnType, typename... Args>
|
||||
struct FunctionTraits<ReturnType (ClassType::*)(Args...)> : public FunctionTraits<ReturnType(Args...)> {
|
||||
typedef ClassType& OwnerType;
|
||||
};
|
||||
|
||||
template <typename ClassType, typename ReturnType, typename... Args>
|
||||
struct FunctionTraits<ReturnType (ClassType::*)(Args...) const> : public FunctionTraits<ReturnType(Args...)> {
|
||||
typedef const ClassType& OwnerType;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct FunctionTraits<T&> : public FunctionTraits<T> {};
|
||||
|
||||
template <typename T>
|
||||
struct FunctionTraits<T const&> : public FunctionTraits<T> {};
|
||||
|
||||
template <typename T>
|
||||
struct FunctionTraits<T&&> : public FunctionTraits<T> {};
|
||||
|
||||
template <typename T>
|
||||
struct FunctionTraits<T const&&> : public FunctionTraits<T> {};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
254
source/core/StarArray.hpp
Normal file
254
source/core/StarArray.hpp
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
#ifndef STAR_ARRAY_H
|
||||
#define STAR_ARRAY_H
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "StarHash.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// Somewhat nicer form of std::array, always initializes values, uses nicer
|
||||
// constructor pattern.
|
||||
template <typename ElementT, size_t SizeN>
|
||||
class Array : public std::array<ElementT, SizeN> {
|
||||
public:
|
||||
typedef std::array<ElementT, SizeN> Base;
|
||||
|
||||
typedef ElementT Element;
|
||||
static size_t const ArraySize = SizeN;
|
||||
|
||||
typedef Element* iterator;
|
||||
typedef Element const* const_iterator;
|
||||
|
||||
typedef Element& reference;
|
||||
typedef Element const& const_reference;
|
||||
|
||||
typedef Element value_type;
|
||||
|
||||
static Array filled(Element const& e);
|
||||
|
||||
template <typename Iterator>
|
||||
static Array copyFrom(Iterator p, size_t n = NPos);
|
||||
|
||||
Array();
|
||||
|
||||
explicit Array(Element const& e1);
|
||||
|
||||
template <typename... T>
|
||||
Array(Element const& e1, T const&... rest);
|
||||
|
||||
template <typename Element2>
|
||||
explicit Array(Array<Element2, SizeN> const& a);
|
||||
|
||||
template <size_t i>
|
||||
reference get();
|
||||
|
||||
template <size_t i>
|
||||
const_reference get() const;
|
||||
|
||||
template <typename T2>
|
||||
Array& operator=(Array<T2, SizeN> const& array);
|
||||
|
||||
Element* ptr();
|
||||
Element const* ptr() const;
|
||||
|
||||
bool operator==(Array const& a) const;
|
||||
bool operator!=(Array const& a) const;
|
||||
bool operator<(Array const& a) const;
|
||||
bool operator<=(Array const& a) const;
|
||||
bool operator>(Array const& a) const;
|
||||
bool operator>=(Array const& a) const;
|
||||
|
||||
template <size_t Size2>
|
||||
Array<ElementT, Size2> toSize() const;
|
||||
|
||||
private:
|
||||
// Instead of {} array initialization, use recursive assignment to mimic old
|
||||
// C++ style construction with less strict narrowing rules.
|
||||
template <typename T, typename... TL>
|
||||
void set(T const& e, TL const&... rest);
|
||||
void set();
|
||||
};
|
||||
|
||||
template <typename DataT, size_t SizeT>
|
||||
struct hash<Array<DataT, SizeT>> {
|
||||
size_t operator()(Array<DataT, SizeT> const& a) const;
|
||||
Star::hash<DataT> dataHasher;
|
||||
};
|
||||
|
||||
typedef Array<int, 2> Array2I;
|
||||
typedef Array<size_t, 2> Array2S;
|
||||
typedef Array<unsigned, 2> Array2U;
|
||||
typedef Array<float, 2> Array2F;
|
||||
typedef Array<double, 2> Array2D;
|
||||
|
||||
typedef Array<int, 3> Array3I;
|
||||
typedef Array<size_t, 3> Array3S;
|
||||
typedef Array<unsigned, 3> Array3U;
|
||||
typedef Array<float, 3> Array3F;
|
||||
typedef Array<double, 3> Array3D;
|
||||
|
||||
typedef Array<int, 4> Array4I;
|
||||
typedef Array<size_t, 4> Array4S;
|
||||
typedef Array<unsigned, 4> Array4U;
|
||||
typedef Array<float, 4> Array4F;
|
||||
typedef Array<double, 4> Array4D;
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
Array<Element, Size> Array<Element, Size>::filled(Element const& e) {
|
||||
Array a;
|
||||
a.fill(e);
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
template <typename Iterator>
|
||||
Array<Element, Size> Array<Element, Size>::copyFrom(Iterator p, size_t n) {
|
||||
Array a;
|
||||
for (size_t i = 0; i < n && i < Size; ++i)
|
||||
a[i] = *(p++);
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
Array<Element, Size>::Array()
|
||||
: Base() {}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
Array<Element, Size>::Array(Element const& e1) {
|
||||
static_assert(Size == 1, "Incorrect size in Array constructor");
|
||||
set(e1);
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
template <typename... T>
|
||||
Array<Element, Size>::Array(Element const& e1, T const&... rest) {
|
||||
static_assert(sizeof...(rest) == Size - 1, "Incorrect size in Array constructor");
|
||||
set(e1, rest...);
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
template <typename Element2>
|
||||
Array<Element, Size>::Array(Array<Element2, Size> const& a) {
|
||||
std::copy(a.begin(), a.end(), Base::begin());
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
template <size_t i>
|
||||
auto Array<Element, Size>::get() -> reference {
|
||||
static_assert(i < Size, "Incorrect size in Array::at");
|
||||
return Base::operator[](i);
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
template <size_t i>
|
||||
auto Array<Element, Size>::get() const -> const_reference {
|
||||
static_assert(i < Size, "Incorrect size in Array::at");
|
||||
return Base::operator[](i);
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
template <typename T2>
|
||||
Array<Element, Size>& Array<Element, Size>::operator=(Array<T2, Size> const& array) {
|
||||
std::copy(array.begin(), array.end(), Base::begin());
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
Element* Array<Element, Size>::ptr() {
|
||||
return Base::data();
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
Element const* Array<Element, Size>::ptr() const {
|
||||
return Base::data();
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
bool Array<Element, Size>::operator==(Array const& a) const {
|
||||
for (size_t i = 0; i < Size; ++i)
|
||||
if ((*this)[i] != a[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
bool Array<Element, Size>::operator!=(Array const& a) const {
|
||||
return !operator==(a);
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
bool Array<Element, Size>::operator<(Array const& a) const {
|
||||
for (size_t i = 0; i < Size; ++i) {
|
||||
if ((*this)[i] < a[i])
|
||||
return true;
|
||||
else if (a[i] < (*this)[i])
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
bool Array<Element, Size>::operator<=(Array const& a) const {
|
||||
for (size_t i = 0; i < Size; ++i) {
|
||||
if ((*this)[i] < a[i])
|
||||
return true;
|
||||
else if (a[i] < (*this)[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
bool Array<Element, Size>::operator>(Array const& a) const {
|
||||
return a < *this;
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
bool Array<Element, Size>::operator>=(Array const& a) const {
|
||||
return a <= *this;
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
template <size_t Size2>
|
||||
Array<Element, Size2> Array<Element, Size>::toSize() const {
|
||||
Array<Element, Size2> r;
|
||||
size_t ns = std::min(Size2, Size);
|
||||
for (size_t i = 0; i < ns; ++i)
|
||||
r[i] = (*this)[i];
|
||||
return r;
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
void Array<Element, Size>::set() {}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
template <typename T, typename... TL>
|
||||
void Array<Element, Size>::set(T const& e, TL const&... rest) {
|
||||
Base::operator[](Size - 1 - sizeof...(rest)) = e;
|
||||
set(rest...);
|
||||
}
|
||||
|
||||
template <typename Element, size_t Size>
|
||||
std::ostream& operator<<(std::ostream& os, Array<Element, Size> const& a) {
|
||||
os << '[';
|
||||
for (size_t i = 0; i < Size; ++i) {
|
||||
os << a[i];
|
||||
if (i != Size - 1)
|
||||
os << ", ";
|
||||
}
|
||||
os << ']';
|
||||
return os;
|
||||
}
|
||||
|
||||
template <typename DataT, size_t SizeT>
|
||||
size_t hash<Array<DataT, SizeT>>::operator()(Array<DataT, SizeT> const& a) const {
|
||||
size_t hashval = 0;
|
||||
for (size_t i = 0; i < SizeT; ++i)
|
||||
hashCombine(hashval, dataHasher(a[i]));
|
||||
return hashval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
121
source/core/StarAtomicSharedPtr.hpp
Normal file
121
source/core/StarAtomicSharedPtr.hpp
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#ifndef STAR_ATOMIC_SHARED_PTR_HPP
|
||||
#define STAR_ATOMIC_SHARED_PTR_HPP
|
||||
|
||||
#include "StarThread.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// Thread safe shared_ptr such that is is possible to safely access the
|
||||
// contents of the shared_ptr while other threads might be updating it. Makes
|
||||
// it possible to safely do Read Copy Update.
|
||||
template <typename T>
|
||||
class AtomicSharedPtr {
|
||||
public:
|
||||
typedef shared_ptr<T> SharedPtr;
|
||||
typedef weak_ptr<T> WeakPtr;
|
||||
|
||||
AtomicSharedPtr();
|
||||
AtomicSharedPtr(AtomicSharedPtr const& p);
|
||||
AtomicSharedPtr(AtomicSharedPtr&& p);
|
||||
AtomicSharedPtr(SharedPtr p);
|
||||
|
||||
SharedPtr load() const;
|
||||
WeakPtr weak() const;
|
||||
void store(SharedPtr p);
|
||||
void reset();
|
||||
|
||||
explicit operator bool() const;
|
||||
bool unique() const;
|
||||
|
||||
SharedPtr operator->() const;
|
||||
|
||||
AtomicSharedPtr& operator=(AtomicSharedPtr const& p);
|
||||
AtomicSharedPtr& operator=(AtomicSharedPtr&& p);
|
||||
AtomicSharedPtr& operator=(SharedPtr p);
|
||||
|
||||
private:
|
||||
SharedPtr m_ptr;
|
||||
mutable SpinLock m_lock;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
AtomicSharedPtr<T>::AtomicSharedPtr() {}
|
||||
|
||||
template <typename T>
|
||||
AtomicSharedPtr<T>::AtomicSharedPtr(AtomicSharedPtr const& p)
|
||||
: m_ptr(p.load()) {}
|
||||
|
||||
template <typename T>
|
||||
AtomicSharedPtr<T>::AtomicSharedPtr(AtomicSharedPtr&& p)
|
||||
: m_ptr(move(p.m_ptr)) {}
|
||||
|
||||
template <typename T>
|
||||
AtomicSharedPtr<T>::AtomicSharedPtr(SharedPtr p)
|
||||
: m_ptr(move(p)) {}
|
||||
|
||||
template <typename T>
|
||||
auto AtomicSharedPtr<T>::load() const -> SharedPtr {
|
||||
SpinLocker locker(m_lock);
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto AtomicSharedPtr<T>::weak() const -> WeakPtr {
|
||||
SpinLocker locker(m_lock);
|
||||
return WeakPtr(m_ptr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void AtomicSharedPtr<T>::store(SharedPtr p) {
|
||||
SpinLocker locker(m_lock);
|
||||
m_ptr = move(p);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void AtomicSharedPtr<T>::reset() {
|
||||
SpinLocker locker(m_lock);
|
||||
m_ptr.reset();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
AtomicSharedPtr<T>::operator bool() const {
|
||||
SpinLocker locker(m_lock);
|
||||
return (bool)m_ptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool AtomicSharedPtr<T>::unique() const {
|
||||
SpinLocker locker(m_lock);
|
||||
return m_ptr.unique();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto AtomicSharedPtr<T>::operator-> () const -> SharedPtr {
|
||||
SpinLocker locker(m_lock);
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
AtomicSharedPtr<T>& AtomicSharedPtr<T>::operator=(AtomicSharedPtr const& p) {
|
||||
SpinLocker locker(m_lock);
|
||||
m_ptr = p.load();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
AtomicSharedPtr<T>& AtomicSharedPtr<T>::operator=(AtomicSharedPtr&& p) {
|
||||
SpinLocker locker(m_lock);
|
||||
m_ptr = move(p.m_ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
AtomicSharedPtr<T>& AtomicSharedPtr<T>::operator=(SharedPtr p) {
|
||||
SpinLocker locker(m_lock);
|
||||
m_ptr = move(p);
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
562
source/core/StarAudio.cpp
Normal file
562
source/core/StarAudio.cpp
Normal file
|
|
@ -0,0 +1,562 @@
|
|||
// Fixes unused variable warning
|
||||
#define OV_EXCLUDE_STATIC_CALLBACKS
|
||||
|
||||
#include "vorbis/codec.h"
|
||||
#include "vorbis/vorbisfile.h"
|
||||
|
||||
#include "StarAudio.hpp"
|
||||
#include "StarBuffer.hpp"
|
||||
#include "StarFile.hpp"
|
||||
#include "StarFormat.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarDataStreamDevices.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
namespace {
|
||||
struct WaveData {
|
||||
ByteArrayPtr byteArray;
|
||||
unsigned channels;
|
||||
unsigned sampleRate;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
T readLEType(IODevicePtr const& device) {
|
||||
T t;
|
||||
device->readFull((char*)&t, sizeof(t));
|
||||
fromByteOrder(ByteOrder::LittleEndian, (char*)&t, sizeof(t));
|
||||
return t;
|
||||
}
|
||||
|
||||
bool isUncompressed(IODevicePtr device) {
|
||||
const size_t sigLength = 4;
|
||||
unique_ptr<char[]> riffSig(new char[sigLength + 1]()); // RIFF\0
|
||||
unique_ptr<char[]> waveSig(new char[sigLength + 1]()); // WAVE\0
|
||||
|
||||
StreamOffset previousOffset = device->pos();
|
||||
device->seek(0);
|
||||
device->readFull(riffSig.get(), sigLength);
|
||||
device->seek(4, IOSeek::Relative);
|
||||
device->readFull(waveSig.get(), sigLength);
|
||||
device->seek(previousOffset);
|
||||
if (strcmp(riffSig.get(), "RIFF") == 0 && strcmp(waveSig.get(), "WAVE") == 0) { // bytes are magic
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
WaveData parseWav(IODevicePtr device) {
|
||||
const size_t sigLength = 4;
|
||||
unique_ptr<char[]> riffSig(new char[sigLength + 1]()); // RIFF\0
|
||||
unique_ptr<char[]> waveSig(new char[sigLength + 1]()); // WAVE\0
|
||||
unique_ptr<char[]> fmtSig(new char[sigLength + 1]()); // fmt \0
|
||||
unique_ptr<char[]> dataSig(new char[sigLength + 1]()); // data\0
|
||||
|
||||
// RIFF Chunk Descriptor
|
||||
device->seek(0);
|
||||
device->readFull(riffSig.get(), sigLength);
|
||||
|
||||
uint32_t fileSize = readLEType<uint32_t>(device);
|
||||
fileSize += sigLength + sizeof(fileSize);
|
||||
if (fileSize != device->size())
|
||||
throw AudioException(strf("Wav file is wrong size, reports %d is actually %d", fileSize, device->size()));
|
||||
|
||||
device->readFull(waveSig.get(), sigLength);
|
||||
|
||||
if ((strcmp(riffSig.get(), "RIFF") != 0) || (strcmp(waveSig.get(), "WAVE") != 0)) { // bytes are not magic
|
||||
auto p = [](char a) { return isprint(a) ? a : '?'; };
|
||||
throw AudioException(strf("Wav file has wrong magic bytes, got `%c%c%c%c' and `%c%c%c%c' but expected `RIFF' and `WAVE'",
|
||||
p(riffSig[0]), p(riffSig[1]), p(riffSig[2]), p(riffSig[3]), p(waveSig[0]), p(waveSig[1]), p(waveSig[2]), p(waveSig[3])));
|
||||
}
|
||||
|
||||
// fmt subchunk
|
||||
|
||||
device->readFull(fmtSig.get(), sigLength);
|
||||
if (strcmp(fmtSig.get(), "fmt ") != 0) { // friendship is magic
|
||||
auto p = [](char a) { return isprint(a) ? a : '?'; };
|
||||
throw AudioException(strf("Wav file fmt subchunk has wrong magic bytes, got `%c%c%c%c' but expected `fmt '",
|
||||
p(fmtSig[0]),
|
||||
p(fmtSig[1]),
|
||||
p(fmtSig[2]),
|
||||
p(fmtSig[3])));
|
||||
}
|
||||
|
||||
uint32_t fmtSubchunkSize = readLEType<uint32_t>(device);
|
||||
fmtSubchunkSize += sigLength;
|
||||
if (fmtSubchunkSize < 20)
|
||||
throw AudioException(strf("fmt subchunk is sized wrong, expected 20 got %d. Is this wav file not PCM?", fmtSubchunkSize));
|
||||
|
||||
uint16_t audioFormat = readLEType<uint16_t>(device);
|
||||
if (audioFormat != 1)
|
||||
throw AudioException("audioFormat data indicates that wav file is something other than PCM format. Unsupported.");
|
||||
|
||||
uint16_t wavChannels = readLEType<uint16_t>(device);
|
||||
uint32_t wavSampleRate = readLEType<uint32_t>(device);
|
||||
uint32_t wavByteRate = readLEType<uint32_t>(device);
|
||||
uint16_t wavBlockAlign = readLEType<uint16_t>(device);
|
||||
uint16_t wavBitsPerSample = readLEType<uint16_t>(device);
|
||||
|
||||
if (wavBitsPerSample != 16)
|
||||
throw AudioException("Only 16-bit PCM wavs are supported.");
|
||||
if (wavByteRate * 8 != wavSampleRate * wavChannels * wavBitsPerSample)
|
||||
throw AudioException("Sanity check failed, ByteRate is wrong");
|
||||
if (wavBlockAlign * 8 != wavChannels * wavBitsPerSample)
|
||||
throw AudioException("Sanity check failed, BlockAlign is wrong");
|
||||
|
||||
device->seek(fmtSubchunkSize - 20, IOSeek::Relative);
|
||||
|
||||
// data subchunk
|
||||
|
||||
device->readFull(dataSig.get(), sigLength);
|
||||
if (strcmp(dataSig.get(), "data") != 0) { // magic or more magic?
|
||||
auto p = [](char a) { return isprint(a) ? a : '?'; };
|
||||
throw AudioException(strf("Wav file data subchunk has wrong magic bytes, got `%c%c%c%c' but expected `data'",
|
||||
p(dataSig[0]), p(dataSig[1]), p(dataSig[2]), p(dataSig[3])));
|
||||
}
|
||||
|
||||
uint32_t wavDataSize = readLEType<uint32_t>(device);
|
||||
size_t wavDataOffset = (size_t)device->pos();
|
||||
if (wavDataSize + wavDataOffset > (size_t)device->size()) {
|
||||
throw AudioException(strf("Wav file data size reported is inconsistent with file size, got %d but expected %d",
|
||||
device->size(), wavDataSize + wavDataOffset));
|
||||
}
|
||||
|
||||
ByteArrayPtr pcmData = make_shared<ByteArray>();
|
||||
pcmData->resize(wavDataSize);
|
||||
|
||||
// Copy across data and perform and endianess conversion if needed
|
||||
device->readFull(pcmData->ptr(), pcmData->size());
|
||||
for (size_t i = 0; i < pcmData->size() / 2; ++i)
|
||||
fromByteOrder(ByteOrder::LittleEndian, pcmData->ptr() + i * 2, 2);
|
||||
|
||||
return WaveData{move(pcmData), wavChannels, wavSampleRate};
|
||||
}
|
||||
}
|
||||
|
||||
class CompressedAudioImpl {
|
||||
public:
|
||||
static size_t readFunc(void* ptr, size_t size, size_t nmemb, void* datasource) {
|
||||
return static_cast<ExternalBuffer*>(datasource)->read((char*)ptr, size * nmemb) / size;
|
||||
}
|
||||
|
||||
static int seekFunc(void* datasource, ogg_int64_t offset, int whence) {
|
||||
static_cast<ExternalBuffer*>(datasource)->seek(offset, (IOSeek)whence);
|
||||
return 0;
|
||||
};
|
||||
|
||||
static long int tellFunc(void* datasource) {
|
||||
return (long int)static_cast<ExternalBuffer*>(datasource)->pos();
|
||||
};
|
||||
|
||||
CompressedAudioImpl(CompressedAudioImpl const& impl) {
|
||||
m_audioData = impl.m_audioData;
|
||||
m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
|
||||
m_vorbisInfo = nullptr;
|
||||
}
|
||||
|
||||
CompressedAudioImpl(IODevicePtr audioData) {
|
||||
audioData->open(IOMode::Read);
|
||||
audioData->seek(0);
|
||||
m_audioData = make_shared<ByteArray>(audioData->readBytes((size_t)audioData->size()));
|
||||
m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
|
||||
m_vorbisInfo = nullptr;
|
||||
}
|
||||
|
||||
~CompressedAudioImpl() {
|
||||
ov_clear(&m_vorbisFile);
|
||||
}
|
||||
|
||||
bool open() {
|
||||
m_callbacks.read_func = readFunc;
|
||||
m_callbacks.seek_func = seekFunc;
|
||||
m_callbacks.tell_func = tellFunc;
|
||||
m_callbacks.close_func = NULL;
|
||||
|
||||
if (ov_open_callbacks(&m_memoryFile, &m_vorbisFile, NULL, 0, m_callbacks) < 0)
|
||||
return false;
|
||||
|
||||
m_vorbisInfo = ov_info(&m_vorbisFile, -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned channels() {
|
||||
return m_vorbisInfo->channels;
|
||||
}
|
||||
|
||||
unsigned sampleRate() {
|
||||
return m_vorbisInfo->rate;
|
||||
}
|
||||
|
||||
double totalTime() {
|
||||
return ov_time_total(&m_vorbisFile, -1);
|
||||
}
|
||||
|
||||
uint64_t totalSamples() {
|
||||
return ov_pcm_total(&m_vorbisFile, -1);
|
||||
}
|
||||
|
||||
void seekTime(double time) {
|
||||
int ret = ov_time_seek(&m_vorbisFile, time);
|
||||
|
||||
if (ret != 0)
|
||||
throw StarException("Cannot seek ogg stream Audio::seekTime");
|
||||
}
|
||||
|
||||
void seekSample(uint64_t pos) {
|
||||
int ret = ov_pcm_seek(&m_vorbisFile, pos);
|
||||
|
||||
if (ret != 0)
|
||||
throw StarException("Cannot seek ogg stream in Audio::seekSample");
|
||||
}
|
||||
|
||||
double currentTime() {
|
||||
return ov_time_tell(&m_vorbisFile);
|
||||
}
|
||||
|
||||
uint64_t currentSample() {
|
||||
return ov_pcm_tell(&m_vorbisFile);
|
||||
}
|
||||
|
||||
size_t readPartial(int16_t* buffer, size_t bufferSize) {
|
||||
int bitstream;
|
||||
int read;
|
||||
// ov_read takes int parameter, so do some magic here to make sure we don't
|
||||
// overflow
|
||||
bufferSize *= 2;
|
||||
#if STAR_LITTLE_ENDIAN
|
||||
read = ov_read(&m_vorbisFile, (char*)buffer, bufferSize, 0, 2, 1, &bitstream);
|
||||
#else
|
||||
read = ov_read(&m_vorbisFile, (char*)buffer, bufferSize, 1, 2, 1, &bitstream);
|
||||
#endif
|
||||
if (read < 0)
|
||||
throw AudioException("Error in Audio::read");
|
||||
|
||||
// read in bytes, returning number of int16_t samples.
|
||||
return read / 2;
|
||||
}
|
||||
|
||||
private:
|
||||
ByteArrayConstPtr m_audioData;
|
||||
ExternalBuffer m_memoryFile;
|
||||
ov_callbacks m_callbacks;
|
||||
OggVorbis_File m_vorbisFile;
|
||||
vorbis_info* m_vorbisInfo;
|
||||
};
|
||||
|
||||
class UncompressedAudioImpl {
|
||||
public:
|
||||
UncompressedAudioImpl(UncompressedAudioImpl const& impl) {
|
||||
m_channels = impl.m_channels;
|
||||
m_sampleRate = impl.m_sampleRate;
|
||||
m_audioData = impl.m_audioData;
|
||||
m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
|
||||
}
|
||||
|
||||
UncompressedAudioImpl(CompressedAudioImpl& impl) {
|
||||
m_channels = impl.channels();
|
||||
m_sampleRate = impl.sampleRate();
|
||||
|
||||
int16_t buffer[1024];
|
||||
Buffer uncompressBuffer;
|
||||
while (true) {
|
||||
size_t ramt = impl.readPartial(buffer, 1024);
|
||||
|
||||
if (ramt == 0) {
|
||||
// End of stream reached
|
||||
break;
|
||||
} else {
|
||||
uncompressBuffer.writeFull((char*)buffer, ramt * 2);
|
||||
}
|
||||
}
|
||||
|
||||
m_audioData = make_shared<ByteArray>(uncompressBuffer.takeData());
|
||||
m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
|
||||
}
|
||||
|
||||
UncompressedAudioImpl(ByteArrayConstPtr data, unsigned channels, unsigned sampleRate) {
|
||||
m_channels = channels;
|
||||
m_sampleRate = sampleRate;
|
||||
m_audioData = move(data);
|
||||
m_memoryFile.reset(m_audioData->ptr(), m_audioData->size());
|
||||
}
|
||||
|
||||
bool open() {
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned channels() {
|
||||
return m_channels;
|
||||
}
|
||||
|
||||
unsigned sampleRate() {
|
||||
return m_sampleRate;
|
||||
}
|
||||
|
||||
double totalTime() {
|
||||
return (double)totalSamples() / m_sampleRate;
|
||||
}
|
||||
|
||||
uint64_t totalSamples() {
|
||||
return m_memoryFile.dataSize() / 2 / m_channels;
|
||||
}
|
||||
|
||||
void seekTime(double time) {
|
||||
seekSample((uint64_t)(time * m_sampleRate));
|
||||
}
|
||||
|
||||
void seekSample(uint64_t pos) {
|
||||
m_memoryFile.seek(pos * 2 * m_channels);
|
||||
}
|
||||
|
||||
double currentTime() {
|
||||
return (double)currentSample() / m_sampleRate;
|
||||
}
|
||||
|
||||
uint64_t currentSample() {
|
||||
return m_memoryFile.pos() / 2 / m_channels;
|
||||
}
|
||||
|
||||
size_t readPartial(int16_t* buffer, size_t bufferSize) {
|
||||
if (bufferSize != NPos)
|
||||
bufferSize = bufferSize * 2;
|
||||
return m_memoryFile.read((char*)buffer, bufferSize) / 2;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned m_channels;
|
||||
unsigned m_sampleRate;
|
||||
ByteArrayConstPtr m_audioData;
|
||||
ExternalBuffer m_memoryFile;
|
||||
};
|
||||
|
||||
Audio::Audio(IODevicePtr device) {
|
||||
if (!device->isOpen())
|
||||
device->open(IOMode::Read);
|
||||
|
||||
if (isUncompressed(device)) {
|
||||
WaveData data = parseWav(device);
|
||||
m_uncompressed = make_shared<UncompressedAudioImpl>(move(data.byteArray), data.channels, data.sampleRate);
|
||||
} else {
|
||||
m_compressed = make_shared<CompressedAudioImpl>(device);
|
||||
if (!m_compressed->open())
|
||||
throw AudioException("File does not appear to be a valid ogg bitstream");
|
||||
}
|
||||
}
|
||||
|
||||
Audio::Audio(Audio const& audio) {
|
||||
*this = audio;
|
||||
}
|
||||
|
||||
Audio::Audio(Audio&& audio) {
|
||||
operator=(move(audio));
|
||||
}
|
||||
|
||||
Audio& Audio::operator=(Audio const& audio) {
|
||||
if (audio.m_uncompressed) {
|
||||
m_uncompressed = make_shared<UncompressedAudioImpl>(*audio.m_uncompressed);
|
||||
m_uncompressed->open();
|
||||
} else {
|
||||
m_compressed = make_shared<CompressedAudioImpl>(*audio.m_compressed);
|
||||
m_compressed->open();
|
||||
}
|
||||
|
||||
seekSample(audio.currentSample());
|
||||
return *this;
|
||||
}
|
||||
|
||||
Audio& Audio::operator=(Audio&& audio) {
|
||||
m_compressed = move(audio.m_compressed);
|
||||
m_uncompressed = move(audio.m_uncompressed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
unsigned Audio::channels() const {
|
||||
if (m_uncompressed)
|
||||
return m_uncompressed->channels();
|
||||
else
|
||||
return m_compressed->channels();
|
||||
}
|
||||
|
||||
unsigned Audio::sampleRate() const {
|
||||
if (m_uncompressed)
|
||||
return m_uncompressed->sampleRate();
|
||||
else
|
||||
return m_compressed->sampleRate();
|
||||
}
|
||||
|
||||
double Audio::totalTime() const {
|
||||
if (m_uncompressed)
|
||||
return m_uncompressed->totalTime();
|
||||
else
|
||||
return m_compressed->totalTime();
|
||||
}
|
||||
|
||||
uint64_t Audio::totalSamples() const {
|
||||
if (m_uncompressed)
|
||||
return m_uncompressed->totalSamples();
|
||||
else
|
||||
return m_compressed->totalSamples();
|
||||
}
|
||||
|
||||
bool Audio::compressed() const {
|
||||
return (bool)m_compressed;
|
||||
}
|
||||
|
||||
void Audio::uncompress() {
|
||||
if (m_compressed) {
|
||||
m_uncompressed = make_shared<UncompressedAudioImpl>(*m_compressed);
|
||||
m_compressed.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Audio::seekTime(double time) {
|
||||
if (m_uncompressed)
|
||||
m_uncompressed->seekTime(time);
|
||||
else
|
||||
m_compressed->seekTime(time);
|
||||
}
|
||||
|
||||
void Audio::seekSample(uint64_t pos) {
|
||||
if (m_uncompressed)
|
||||
m_uncompressed->seekSample(pos);
|
||||
else
|
||||
m_compressed->seekSample(pos);
|
||||
}
|
||||
|
||||
double Audio::currentTime() const {
|
||||
if (m_uncompressed)
|
||||
return m_uncompressed->currentTime();
|
||||
else
|
||||
return m_compressed->currentTime();
|
||||
}
|
||||
|
||||
uint64_t Audio::currentSample() const {
|
||||
if (m_uncompressed)
|
||||
return m_uncompressed->currentSample();
|
||||
else
|
||||
return m_compressed->currentSample();
|
||||
}
|
||||
|
||||
size_t Audio::readPartial(int16_t* buffer, size_t bufferSize) {
|
||||
if (bufferSize == 0)
|
||||
return 0;
|
||||
|
||||
if (m_uncompressed)
|
||||
return m_uncompressed->readPartial(buffer, bufferSize);
|
||||
else
|
||||
return m_compressed->readPartial(buffer, bufferSize);
|
||||
}
|
||||
|
||||
size_t Audio::read(int16_t* buffer, size_t bufferSize) {
|
||||
if (bufferSize == 0)
|
||||
return 0;
|
||||
|
||||
size_t readTotal = 0;
|
||||
while (readTotal < bufferSize) {
|
||||
size_t toGo = bufferSize - readTotal;
|
||||
size_t ramt = readPartial(buffer + readTotal, toGo);
|
||||
readTotal += ramt;
|
||||
// End of stream reached
|
||||
if (ramt == 0)
|
||||
break;
|
||||
}
|
||||
return readTotal;
|
||||
}
|
||||
|
||||
size_t Audio::resample(unsigned destinationChannels, unsigned destinationSampleRate, int16_t* destinationBuffer, size_t destinationBufferSize, double velocity) {
|
||||
unsigned destinationSamples = destinationBufferSize / destinationChannels;
|
||||
if (destinationSamples == 0)
|
||||
return 0;
|
||||
|
||||
unsigned sourceChannels = channels();
|
||||
unsigned sourceSampleRate = sampleRate();
|
||||
|
||||
if (velocity != 1.0)
|
||||
sourceSampleRate = (unsigned)(sourceSampleRate * velocity);
|
||||
|
||||
if (destinationChannels == sourceChannels && destinationSampleRate == sourceSampleRate) {
|
||||
// If the destination and source channel count and sample rate are the
|
||||
// same, this is the same as a read.
|
||||
|
||||
return read(destinationBuffer, destinationBufferSize);
|
||||
|
||||
} else if (destinationSampleRate == sourceSampleRate) {
|
||||
// If the destination and source sample rate are the same, then we can skip
|
||||
// the super-sampling math.
|
||||
|
||||
unsigned sourceBufferSize = destinationSamples * sourceChannels;
|
||||
|
||||
m_workingBuffer.resize(sourceBufferSize * sizeof(int16_t));
|
||||
int16_t* sourceBuffer = (int16_t*)m_workingBuffer.ptr();
|
||||
|
||||
unsigned readSamples = read(sourceBuffer, sourceBufferSize) / sourceChannels;
|
||||
|
||||
for (unsigned sample = 0; sample < readSamples; ++sample) {
|
||||
unsigned sourceBufferIndex = sample * sourceChannels;
|
||||
unsigned destinationBufferIndex = sample * destinationChannels;
|
||||
|
||||
for (unsigned destinationChannel = 0; destinationChannel < destinationChannels; ++destinationChannel) {
|
||||
// If the destination channel count is greater than the source
|
||||
// channels, simply copy the last channel
|
||||
unsigned sourceChannel = min(destinationChannel, sourceChannels - 1);
|
||||
destinationBuffer[destinationBufferIndex + destinationChannel] =
|
||||
sourceBuffer[sourceBufferIndex + sourceChannel];
|
||||
}
|
||||
}
|
||||
|
||||
return readSamples * destinationChannels;
|
||||
|
||||
} else {
|
||||
// Otherwise, we have to do a full resample.
|
||||
|
||||
unsigned sourceSamples = ((uint64_t)sourceSampleRate * destinationSamples + destinationSampleRate - 1) / destinationSampleRate;
|
||||
unsigned sourceBufferSize = sourceSamples * sourceChannels;
|
||||
|
||||
m_workingBuffer.resize(sourceBufferSize * sizeof(int16_t));
|
||||
int16_t* sourceBuffer = (int16_t*)m_workingBuffer.ptr();
|
||||
|
||||
unsigned readSamples = read(sourceBuffer, sourceBufferSize) / sourceChannels;
|
||||
|
||||
if (readSamples == 0)
|
||||
return 0;
|
||||
|
||||
unsigned writtenSamples = 0;
|
||||
|
||||
for (unsigned destinationSample = 0; destinationSample < destinationSamples; ++destinationSample) {
|
||||
unsigned destinationBufferIndex = destinationSample * destinationChannels;
|
||||
|
||||
for (unsigned destinationChannel = 0; destinationChannel < destinationChannels; ++destinationChannel) {
|
||||
static int const SuperSampleFactor = 8;
|
||||
|
||||
// If the destination channel count is greater than the source
|
||||
// channels, simply copy the last channel
|
||||
unsigned sourceChannel = min(destinationChannel, sourceChannels - 1);
|
||||
|
||||
int sample = 0;
|
||||
int sampleCount = 0;
|
||||
for (int superSample = 0; superSample < SuperSampleFactor; ++superSample) {
|
||||
unsigned sourceSample = (unsigned)((destinationSample * SuperSampleFactor + superSample) * sourceSamples / destinationSamples) / SuperSampleFactor;
|
||||
if (sourceSample < readSamples) {
|
||||
unsigned sourceBufferIndex = sourceSample * sourceChannels;
|
||||
starAssert(sourceBufferIndex + sourceChannel < sourceBufferSize);
|
||||
sample += sourceBuffer[sourceBufferIndex + sourceChannel];
|
||||
++sampleCount;
|
||||
}
|
||||
}
|
||||
|
||||
// If sampleCount is zero, then we are past the end of our read data
|
||||
// completely, and can stop
|
||||
if (sampleCount == 0)
|
||||
return writtenSamples * destinationChannels;
|
||||
|
||||
sample /= sampleCount;
|
||||
destinationBuffer[destinationBufferIndex + destinationChannel] = (int16_t)sample;
|
||||
writtenSamples = destinationSample + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return writtenSamples * destinationChannels;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
95
source/core/StarAudio.hpp
Normal file
95
source/core/StarAudio.hpp
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
#ifndef STAR_AUDIO_HPP
|
||||
#define STAR_AUDIO_HPP
|
||||
|
||||
#include "StarIODevice.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(CompressedAudioImpl);
|
||||
STAR_CLASS(UncompressedAudioImpl);
|
||||
STAR_CLASS(Audio);
|
||||
|
||||
STAR_EXCEPTION(AudioException, StarException);
|
||||
|
||||
// Simple class for reading audio files in ogg/vorbis and wav format.
|
||||
// Reads and allows for decompression of a limited subset of ogg/vorbis. Does
|
||||
// not handle multiple bitstreams, sample rate or channel number changes.
|
||||
// Entire stream is kept in memory, and is implicitly shared so copying Audio
|
||||
// instances is not expensive.
|
||||
class Audio {
|
||||
public:
|
||||
explicit Audio(IODevicePtr device);
|
||||
Audio(Audio const& audio);
|
||||
Audio(Audio&& audio);
|
||||
|
||||
Audio& operator=(Audio const& audio);
|
||||
Audio& operator=(Audio&& audio);
|
||||
|
||||
// This function returns the number of channels that this file has. Channels
|
||||
// are static throughout file.
|
||||
unsigned channels() const;
|
||||
|
||||
// This function returns the sample rate that this file has. Sample rates
|
||||
// are static throughout file.
|
||||
unsigned sampleRate() const;
|
||||
|
||||
// This function returns the playtime duration of the file.
|
||||
double totalTime() const;
|
||||
|
||||
// This function returns total number of samples in this file.
|
||||
uint64_t totalSamples() const;
|
||||
|
||||
// This function returns true when the datastream or file being read from is
|
||||
// a vorbis compressed file. False otherwise.
|
||||
bool compressed() const;
|
||||
|
||||
// If compressed, permanently uncompresses audio for faster reading. The
|
||||
// uncompressed buffer is shared with all further copies of Audio, and this
|
||||
// is irreversible.
|
||||
void uncompress();
|
||||
|
||||
// This function seeks the data stream to the given time in seconds.
|
||||
void seekTime(double time);
|
||||
|
||||
// This function seeks the data stream to the given sample number
|
||||
void seekSample(uint64_t sample);
|
||||
|
||||
// This function converts the current offset of the file to the time value of
|
||||
// that offset in seconds.
|
||||
double currentTime() const;
|
||||
|
||||
// This function converts the current offset of the file to the current
|
||||
// sample number.
|
||||
uint64_t currentSample() const;
|
||||
|
||||
// Reads into 16 bit signed buffer with channels interleaved. Returns total
|
||||
// number of samples read (counting each channel individually). 0 indicates
|
||||
// end of stream.
|
||||
size_t readPartial(int16_t* buffer, size_t bufferSize);
|
||||
|
||||
// Same as readPartial, but repeats read attempting to fill buffer as much as
|
||||
// possible
|
||||
size_t read(int16_t* buffer, size_t bufferSize);
|
||||
|
||||
// Read into a given buffer, while also converting into the given number of
|
||||
// channels at the given sample rate and playback velocity. If the number of
|
||||
// channels in the file is higher, only populates lower channels, if it is
|
||||
// lower, the last channel is copied to the remaining channels. Attempts to
|
||||
// fill the buffer as much as possible up to end of stream. May fail to fill
|
||||
// an entire buffer depending on the destinationSampleRate / velocity /
|
||||
// available samples.
|
||||
size_t resample(unsigned destinationChannels, unsigned destinationSampleRate,
|
||||
int16_t* destinationBuffer, size_t destinationBufferSize,
|
||||
double velocity = 1.0);
|
||||
|
||||
private:
|
||||
// If audio is uncompressed, this will be null.
|
||||
CompressedAudioImplPtr m_compressed;
|
||||
UncompressedAudioImplPtr m_uncompressed;
|
||||
|
||||
ByteArray m_workingBuffer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
937
source/core/StarBTree.hpp
Normal file
937
source/core/StarBTree.hpp
Normal file
|
|
@ -0,0 +1,937 @@
|
|||
#ifndef STAR_B_TREE_HPP
|
||||
#define STAR_B_TREE_HPP
|
||||
|
||||
#include "StarList.hpp"
|
||||
#include "StarMaybe.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// Mixin class for implementing a simple B+ Tree style database. LOTS of
|
||||
// possibilities for improvement, especially in batch deletes / inserts.
|
||||
//
|
||||
// The Base class itself must have the following interface:
|
||||
//
|
||||
// struct Base {
|
||||
// typedef KeyT Key;
|
||||
// typedef DataT Data;
|
||||
// typedef PointerT Pointer;
|
||||
//
|
||||
// // Index and Leaf types may either be a literal struct, or a pointer, or a
|
||||
// // handle or whatever. They are meant to be opaque.
|
||||
// typedef IndexT Index;
|
||||
// typedef LeafT Leaf;
|
||||
//
|
||||
// Pointer rootPointer();
|
||||
// bool rootIsLeaf();
|
||||
// void setNewRoot(Pointer pointer, bool isLeaf);
|
||||
//
|
||||
// Index createIndex(Pointer beginPointer);
|
||||
//
|
||||
// // Load an existing index.
|
||||
// Index loadIndex(Pointer pointer);
|
||||
//
|
||||
// size_t indexPointerCount(Index const& index);
|
||||
// Pointer indexPointer(Index const& index, size_t i);
|
||||
// void indexUpdatePointer(Index& index, size_t i, Pointer p);
|
||||
//
|
||||
// Key indexKeyBefore(Index const& index, size_t i);
|
||||
// void indexUpdateKeyBefore(Index& index, size_t i, Key k);
|
||||
//
|
||||
// void indexRemoveBefore(Index& index, size_t i);
|
||||
// void indexInsertAfter(Index& index, size_t i, Key k, Pointer p);
|
||||
//
|
||||
// size_t indexLevel(Index const& index);
|
||||
// void setIndexLevel(Index& index, size_t indexLevel);
|
||||
//
|
||||
// // Should return true if index should try to shift elements into this index
|
||||
// // from sibling index.
|
||||
// bool indexNeedsShift(Index const& index);
|
||||
//
|
||||
// // Should return false if no shift done. If merging, always merge to the
|
||||
// // left.
|
||||
// bool indexShift(Index& left, Key const& mid, Index& right);
|
||||
//
|
||||
// // If a split has occurred, split right and return the mid-key and new
|
||||
// // right node.
|
||||
// Maybe<pair<Key, Index>> indexSplit(Index& index);
|
||||
//
|
||||
// // Index updated, needs storing. Return pointer to stored index (may
|
||||
// // change). Index will not be used after store.
|
||||
// Pointer storeIndex(Index index);
|
||||
//
|
||||
// // Index no longer part of BTree. Index will not be used after delete.
|
||||
// void deleteIndex(Index index);
|
||||
//
|
||||
// // Should create new empty leaf.
|
||||
// Leaf createLeaf();
|
||||
//
|
||||
// Leaf loadLeaf(Pointer pointer);
|
||||
//
|
||||
// size_t leafElementCount(Leaf const& leaf);
|
||||
// Key leafKey(Leaf const& leaf, size_t i);
|
||||
// Data leafData(Leaf const& leaf, size_t i);
|
||||
//
|
||||
// void leafInsert(Leaf& leaf, size_t i, Key k, Data d);
|
||||
// void leafRemove(Leaf& leaf, size_t i);
|
||||
//
|
||||
// // Set and get next-leaf pointers. It is not required that next-leaf
|
||||
// // pointers be kept or that they be valid, so nextLeaf may return nothing.
|
||||
// void setNextLeaf(Leaf& leaf, Maybe<Pointer> n);
|
||||
// Maybe<Pointer> nextLeaf(Leaf const& leaf);
|
||||
//
|
||||
// // Should return true if leaf should try to shift elements into this leaf
|
||||
// // from sibling leaf.
|
||||
// bool leafNeedsShift(Leaf const& l);
|
||||
//
|
||||
// // Should return false if no change necessary. If merging, always merge to
|
||||
// // the left.
|
||||
// bool leafShift(Leaf& left, Leaf& right);
|
||||
//
|
||||
// // Always split right and return new right node if split occurs.
|
||||
// Maybe<Leaf> leafSplit(Leaf& leaf);
|
||||
//
|
||||
// // Leaf has been updated, and needs to be written to storage. Return new
|
||||
// // pointer (may be different). Leaf will not be used after store.
|
||||
// Pointer storeLeaf(Leaf leaf);
|
||||
//
|
||||
// // Leaf is no longer part of this BTree. Leaf will not be used after
|
||||
// // delete.
|
||||
// void deleteLeaf(Leaf leaf);
|
||||
// };
|
||||
template <typename Base>
|
||||
class BTreeMixin : public Base {
|
||||
public:
|
||||
typedef typename Base::Key Key;
|
||||
typedef typename Base::Data Data;
|
||||
typedef typename Base::Pointer Pointer;
|
||||
|
||||
typedef typename Base::Index Index;
|
||||
typedef typename Base::Leaf Leaf;
|
||||
|
||||
bool contains(Key const& k);
|
||||
|
||||
Maybe<Data> find(Key const& k);
|
||||
|
||||
// Range is inclusve on lower bound and exclusive on upper bound.
|
||||
List<pair<Key, Data>> find(Key const& lower, Key const& upper);
|
||||
|
||||
// Visitor is called as visitor(key, data).
|
||||
template <typename Visitor>
|
||||
void forEach(Key const& lower, Key const& upper, Visitor&& visitor);
|
||||
|
||||
// Visitor is called as visitor(key, data).
|
||||
template <typename Visitor>
|
||||
void forAll(Visitor&& visitor);
|
||||
|
||||
// Recover all key value pairs possible, catching exceptions during scan and
|
||||
// reading as much data as possible. Visitor is called as visitor(key, data),
|
||||
// ErrorHandler is called as error(char const*, std::exception const&)
|
||||
template <typename Visitor, typename ErrorHandler>
|
||||
void recoverAll(Visitor&& visitor, ErrorHandler&& error);
|
||||
|
||||
// Visitor is called either as visitor(Index const&) or visitor(Leaf const&).
|
||||
// Return false to halt traversal, true to continue.
|
||||
template <typename Visitor>
|
||||
void forAllNodes(Visitor&& visitor);
|
||||
|
||||
// returns true if old value overwritten.
|
||||
bool insert(Key k, Data data);
|
||||
|
||||
// returns true if key was found.
|
||||
bool remove(Key k);
|
||||
|
||||
// Removes list of keys in the given range, returns count removed.
|
||||
// TODO: SLOW, right now does lots of different removes separately. Need to
|
||||
// implement batch inserts and deletes.
|
||||
List<pair<Key, Data>> remove(Key const& lower, Key const& upper);
|
||||
|
||||
uint64_t indexCount();
|
||||
uint64_t leafCount();
|
||||
uint64_t recordCount();
|
||||
|
||||
uint32_t indexLevels();
|
||||
|
||||
void createNewRoot();
|
||||
|
||||
private:
|
||||
struct DataElement {
|
||||
Key key;
|
||||
Data data;
|
||||
};
|
||||
typedef List<DataElement> DataList;
|
||||
|
||||
struct DataCollector {
|
||||
void operator()(Key const& k, Data const& d);
|
||||
|
||||
List<pair<Key, Data>> list;
|
||||
};
|
||||
|
||||
struct RecordCounter {
|
||||
bool operator()(Index const& index);
|
||||
bool operator()(Leaf const& leaf);
|
||||
|
||||
BTreeMixin* parent;
|
||||
uint64_t count;
|
||||
};
|
||||
|
||||
struct IndexCounter {
|
||||
bool operator()(Index const& index);
|
||||
bool operator()(Leaf const&);
|
||||
|
||||
BTreeMixin* parent;
|
||||
uint64_t count;
|
||||
};
|
||||
|
||||
struct LeafCounter {
|
||||
bool operator()(Index const& index);
|
||||
bool operator()(Leaf const&);
|
||||
|
||||
BTreeMixin* parent;
|
||||
uint64_t count;
|
||||
};
|
||||
|
||||
enum ModifyAction {
|
||||
InsertAction,
|
||||
RemoveAction
|
||||
};
|
||||
|
||||
enum ModifyState {
|
||||
LeafNeedsJoin,
|
||||
IndexNeedsJoin,
|
||||
LeafSplit,
|
||||
IndexSplit,
|
||||
LeafNeedsUpdate,
|
||||
IndexNeedsUpdate,
|
||||
Done
|
||||
};
|
||||
|
||||
struct ModifyInfo {
|
||||
ModifyInfo(ModifyAction a, DataElement e);
|
||||
|
||||
DataElement targetElement;
|
||||
ModifyAction action;
|
||||
bool found;
|
||||
ModifyState state;
|
||||
|
||||
Key newKey;
|
||||
Pointer newPointer;
|
||||
};
|
||||
|
||||
bool contains(Index const& index, Key const& k);
|
||||
bool contains(Leaf const& leaf, Key const& k);
|
||||
|
||||
Maybe<Data> find(Index const& index, Key const& k);
|
||||
Maybe<Data> find(Leaf const& leaf, Key const& k);
|
||||
|
||||
// Returns the highest key for the last leaf we have searched
|
||||
template <typename Visitor>
|
||||
Key forEach(Index const& index, Key const& lower, Key const& upper, Visitor&& o);
|
||||
template <typename Visitor>
|
||||
Key forEach(Leaf const& leaf, Key const& lower, Key const& upper, Visitor&& o);
|
||||
|
||||
// Returns the highest key for the last leaf we have searched
|
||||
template <typename Visitor>
|
||||
Key forAll(Index const& index, Visitor&& o);
|
||||
template <typename Visitor>
|
||||
Key forAll(Leaf const& leaf, Visitor&& o);
|
||||
|
||||
template <typename Visitor, typename ErrorHandler>
|
||||
void recoverAll(Index const& index, Visitor&& o, ErrorHandler&& error);
|
||||
template <typename Visitor, typename ErrorHandler>
|
||||
void recoverAll(Leaf const& leaf, Visitor&& o, ErrorHandler&& error);
|
||||
|
||||
// Variable size values mean that merges can happen on inserts, so can't
|
||||
// split up into insert / remove methods
|
||||
void modify(Leaf& leafNode, ModifyInfo& info);
|
||||
void modify(Index& indexNode, ModifyInfo& info);
|
||||
bool modify(DataElement e, ModifyAction action);
|
||||
|
||||
// Traverses Indexes down the tree on the left side to get the least valued
|
||||
// key that is pointed to by any leaf under this index. Needed when joining.
|
||||
Key getLeftKey(Index const& index);
|
||||
|
||||
template <typename Visitor>
|
||||
void forAllNodes(Index const& index, Visitor&& visitor);
|
||||
|
||||
pair<size_t, bool> leafFind(Leaf const& leaf, Key const& key);
|
||||
size_t indexFind(Index const& index, Key const& key);
|
||||
};
|
||||
|
||||
template <typename Base>
|
||||
bool BTreeMixin<Base>::contains(Key const& k) {
|
||||
if (Base::rootIsLeaf())
|
||||
return contains(Base::loadLeaf(Base::rootPointer()), k);
|
||||
else
|
||||
return contains(Base::loadIndex(Base::rootPointer()), k);
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
auto BTreeMixin<Base>::find(Key const& k) -> Maybe<Data> {
|
||||
if (Base::rootIsLeaf())
|
||||
return find(Base::loadLeaf(Base::rootPointer()), k);
|
||||
else
|
||||
return find(Base::loadIndex(Base::rootPointer()), k);
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
auto BTreeMixin<Base>::find(Key const& lower, Key const& upper) -> List<pair<Key, Data>> {
|
||||
DataCollector collector;
|
||||
forEach(lower, upper, collector);
|
||||
return collector.list;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
template <typename Visitor>
|
||||
void BTreeMixin<Base>::forEach(Key const& lower, Key const& upper, Visitor&& visitor) {
|
||||
if (Base::rootIsLeaf())
|
||||
forEach(Base::loadLeaf(Base::rootPointer()), lower, upper, forward<Visitor>(visitor));
|
||||
else
|
||||
forEach(Base::loadIndex(Base::rootPointer()), lower, upper, forward<Visitor>(visitor));
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
template <typename Visitor>
|
||||
void BTreeMixin<Base>::forAll(Visitor&& visitor) {
|
||||
if (Base::rootIsLeaf())
|
||||
forAll(Base::loadLeaf(Base::rootPointer()), forward<Visitor>(visitor));
|
||||
else
|
||||
forAll(Base::loadIndex(Base::rootPointer()), forward<Visitor>(visitor));
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
template <typename Visitor, typename ErrorHandler>
|
||||
void BTreeMixin<Base>::recoverAll(Visitor&& visitor, ErrorHandler&& error) {
|
||||
try {
|
||||
if (Base::rootIsLeaf())
|
||||
recoverAll(Base::loadLeaf(Base::rootPointer()), forward<Visitor>(visitor), forward<ErrorHandler>(error));
|
||||
else
|
||||
recoverAll(Base::loadIndex(Base::rootPointer()), forward<Visitor>(visitor), forward<ErrorHandler>(error));
|
||||
} catch (std::exception const& e) {
|
||||
error("Error loading root index or leaf node", e);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
template <typename Visitor>
|
||||
void BTreeMixin<Base>::forAllNodes(Visitor&& visitor) {
|
||||
if (Base::rootIsLeaf())
|
||||
visitor(Base::loadLeaf(Base::rootPointer()));
|
||||
else
|
||||
forAllNodes(Base::loadIndex(Base::rootPointer()), forward<Visitor>(visitor));
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
bool BTreeMixin<Base>::insert(Key k, Data data) {
|
||||
return modify(DataElement{move(k), move(data)}, InsertAction);
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
bool BTreeMixin<Base>::remove(Key k) {
|
||||
return modify(DataElement{move(k), Data()}, RemoveAction);
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
auto BTreeMixin<Base>::remove(Key const& lower, Key const& upper) -> List<pair<Key, Data>> {
|
||||
DataCollector collector;
|
||||
forEach(lower, upper, collector);
|
||||
|
||||
for (auto const& elem : collector.list)
|
||||
remove(elem.first);
|
||||
|
||||
return collector.list;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
uint64_t BTreeMixin<Base>::indexCount() {
|
||||
IndexCounter counter = {this, 0};
|
||||
forAllNodes(counter);
|
||||
return counter.count;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
uint64_t BTreeMixin<Base>::leafCount() {
|
||||
LeafCounter counter = {this, 0};
|
||||
forAllNodes(counter);
|
||||
return counter.count;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
uint64_t BTreeMixin<Base>::recordCount() {
|
||||
RecordCounter counter = {this, 0};
|
||||
forAllNodes(counter);
|
||||
return counter.count;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
uint32_t BTreeMixin<Base>::indexLevels() {
|
||||
if (Base::rootIsLeaf())
|
||||
return 0;
|
||||
else
|
||||
return Base::indexLevel(Base::loadIndex(Base::rootPointer())) + 1;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
void BTreeMixin<Base>::createNewRoot() {
|
||||
Base::setNewRoot(Base::storeLeaf(Base::createLeaf()), true);
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
void BTreeMixin<Base>::DataCollector::operator()(Key const& k, Data const& d) {
|
||||
list.push_back({k, d});
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
bool BTreeMixin<Base>::RecordCounter::operator()(Index const&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
bool BTreeMixin<Base>::RecordCounter::operator()(Leaf const& leaf) {
|
||||
count += parent->leafElementCount(leaf);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
bool BTreeMixin<Base>::IndexCounter::operator()(Index const& index) {
|
||||
++count;
|
||||
if (parent->indexLevel(index) == 0)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
bool BTreeMixin<Base>::IndexCounter::operator()(Leaf const&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
bool BTreeMixin<Base>::LeafCounter::operator()(Index const& index) {
|
||||
if (parent->indexLevel(index) == 0) {
|
||||
count += parent->indexPointerCount(index);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
bool BTreeMixin<Base>::LeafCounter::operator()(Leaf const&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
BTreeMixin<Base>::ModifyInfo::ModifyInfo(ModifyAction a, DataElement e)
|
||||
: targetElement(move(e)), action(a) {
|
||||
found = false;
|
||||
state = Done;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
bool BTreeMixin<Base>::contains(Index const& index, Key const& k) {
|
||||
size_t i = indexFind(index, k);
|
||||
if (Base::indexLevel(index) == 0)
|
||||
return contains(Base::loadLeaf(Base::indexPointer(index, i)), k);
|
||||
else
|
||||
return contains(Base::loadIndex(Base::indexPointer(index, i)), k);
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
bool BTreeMixin<Base>::contains(Leaf const& leaf, Key const& k) {
|
||||
return leafFind(leaf, k).second;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
auto BTreeMixin<Base>::find(Index const& index, Key const& k) -> Maybe<Data> {
|
||||
size_t i = indexFind(index, k);
|
||||
if (Base::indexLevel(index) == 0)
|
||||
return find(Base::loadLeaf(Base::indexPointer(index, i)), k);
|
||||
else
|
||||
return find(Base::loadIndex(Base::indexPointer(index, i)), k);
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
auto BTreeMixin<Base>::find(Leaf const& leaf, Key const& k) -> Maybe<Data> {
|
||||
pair<size_t, bool> res = leafFind(leaf, k);
|
||||
if (res.second)
|
||||
return Base::leafData(leaf, res.first);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
template <typename Visitor>
|
||||
auto BTreeMixin<Base>::forEach(Index const& index, Key const& lower, Key const& upper, Visitor&& o) -> Key {
|
||||
size_t i = indexFind(index, lower);
|
||||
Key lastKey;
|
||||
|
||||
if (Base::indexLevel(index) == 0)
|
||||
lastKey = forEach(Base::loadLeaf(Base::indexPointer(index, i)), lower, upper, forward<Visitor>(o));
|
||||
else
|
||||
lastKey = forEach(Base::loadIndex(Base::indexPointer(index, i)), lower, upper, forward<Visitor>(o));
|
||||
|
||||
if (!(lastKey < upper))
|
||||
return lastKey;
|
||||
|
||||
while (i < Base::indexPointerCount(index) - 1) {
|
||||
++i;
|
||||
|
||||
// We're visiting the right side of the key, so if lastKey >=
|
||||
// indexKeyBefore(index, i), we have already visited this node via nextLeaf
|
||||
// pointers, so skip it.
|
||||
if (!(lastKey < Base::indexKeyBefore(index, i)))
|
||||
continue;
|
||||
|
||||
if (Base::indexLevel(index) == 0)
|
||||
lastKey = forEach(Base::loadLeaf(Base::indexPointer(index, i)), lower, upper, forward<Visitor>(o));
|
||||
else
|
||||
lastKey = forEach(Base::loadIndex(Base::indexPointer(index, i)), lower, upper, forward<Visitor>(o));
|
||||
|
||||
if (!(lastKey < upper))
|
||||
break;
|
||||
}
|
||||
|
||||
return lastKey;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
template <typename Visitor>
|
||||
auto BTreeMixin<Base>::forEach(Leaf const& leaf, Key const& lower, Key const& upper, Visitor&& o) -> Key {
|
||||
if (Base::leafElementCount(leaf) == 0)
|
||||
return Key();
|
||||
|
||||
size_t lowerIndex = leafFind(leaf, lower).first;
|
||||
|
||||
for (size_t i = lowerIndex; i != Base::leafElementCount(leaf); ++i) {
|
||||
Key currentKey = Base::leafKey(leaf, i);
|
||||
if (!(currentKey < lower)) {
|
||||
if (currentKey < upper)
|
||||
o(currentKey, Base::leafData(leaf, i));
|
||||
else
|
||||
return currentKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto nextLeafPointer = Base::nextLeaf(leaf))
|
||||
return forEach(Base::loadLeaf(*nextLeafPointer), lower, upper, o);
|
||||
else
|
||||
return Base::leafKey(leaf, Base::leafElementCount(leaf) - 1);
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
template <typename Visitor>
|
||||
auto BTreeMixin<Base>::forAll(Index const& index, Visitor&& o) -> Key {
|
||||
Key lastKey;
|
||||
for (size_t i = 0; i < Base::indexPointerCount(index); ++i) {
|
||||
// If we're to the right of a given key, but lastKey >= this key, then we
|
||||
// must have already visited this node via nextLeaf pointers, so we can
|
||||
// skip it.
|
||||
if (i > 0 && !(lastKey < Base::indexKeyBefore(index, i)))
|
||||
continue;
|
||||
|
||||
if (Base::indexLevel(index) == 0)
|
||||
lastKey = forAll(Base::loadLeaf(Base::indexPointer(index, i)), forward<Visitor>(o));
|
||||
else
|
||||
lastKey = forAll(Base::loadIndex(Base::indexPointer(index, i)), forward<Visitor>(o));
|
||||
}
|
||||
|
||||
return lastKey;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
template <typename Visitor>
|
||||
auto BTreeMixin<Base>::forAll(Leaf const& leaf, Visitor&& o) -> Key {
|
||||
if (Base::leafElementCount(leaf) == 0)
|
||||
return Key();
|
||||
|
||||
for (size_t i = 0; i != Base::leafElementCount(leaf); ++i) {
|
||||
Key currentKey = Base::leafKey(leaf, i);
|
||||
o(Base::leafKey(leaf, i), Base::leafData(leaf, i));
|
||||
}
|
||||
|
||||
if (auto nextLeafPointer = Base::nextLeaf(leaf))
|
||||
return forAll(Base::loadLeaf(*nextLeafPointer), forward<Visitor>(o));
|
||||
else
|
||||
return Base::leafKey(leaf, Base::leafElementCount(leaf) - 1);
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
template <typename Visitor, typename ErrorHandler>
|
||||
void BTreeMixin<Base>::recoverAll(Index const& index, Visitor&& visitor, ErrorHandler&& error) {
|
||||
try {
|
||||
for (size_t i = 0; i < Base::indexPointerCount(index); ++i) {
|
||||
if (Base::indexLevel(index) == 0) {
|
||||
try {
|
||||
recoverAll(Base::loadLeaf(Base::indexPointer(index, i)), forward<Visitor>(visitor), forward<ErrorHandler>(error));
|
||||
} catch (std::exception const& e) {
|
||||
error("Error loading leaf node", e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
recoverAll(Base::loadIndex(Base::indexPointer(index, i)), forward<Visitor>(visitor), forward<ErrorHandler>(error));
|
||||
} catch (std::exception const& e) {
|
||||
error("Error loading index node", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (std::exception const& e) {
|
||||
error("Error reading index node", e);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
template <typename Visitor, typename ErrorHandler>
|
||||
void BTreeMixin<Base>::recoverAll(Leaf const& leaf, Visitor&& visitor, ErrorHandler&& error) {
|
||||
try {
|
||||
for (size_t i = 0; i != Base::leafElementCount(leaf); ++i) {
|
||||
Key currentKey = Base::leafKey(leaf, i);
|
||||
visitor(Base::leafKey(leaf, i), Base::leafData(leaf, i));
|
||||
}
|
||||
} catch (std::exception const& e) {
|
||||
error("Error reading leaf node", e);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
void BTreeMixin<Base>::modify(Leaf& leafNode, ModifyInfo& info) {
|
||||
info.state = Done;
|
||||
|
||||
pair<size_t, bool> res = leafFind(leafNode, info.targetElement.key);
|
||||
size_t i = res.first;
|
||||
if (res.second) {
|
||||
info.found = true;
|
||||
Base::leafRemove(leafNode, i);
|
||||
}
|
||||
|
||||
// No change necessary.
|
||||
if (info.action == RemoveAction && !info.found)
|
||||
return;
|
||||
|
||||
if (info.action == InsertAction)
|
||||
Base::leafInsert(leafNode, i, info.targetElement.key, move(info.targetElement.data));
|
||||
|
||||
auto splitResult = Base::leafSplit(leafNode);
|
||||
if (splitResult) {
|
||||
Base::setNextLeaf(*splitResult, Base::nextLeaf(leafNode));
|
||||
info.newKey = Base::leafKey(*splitResult, 0);
|
||||
info.newPointer = Base::storeLeaf(splitResult.take());
|
||||
|
||||
Base::setNextLeaf(leafNode, info.newPointer);
|
||||
info.state = LeafSplit;
|
||||
} else if (Base::leafNeedsShift(leafNode)) {
|
||||
info.state = LeafNeedsJoin;
|
||||
} else {
|
||||
info.state = LeafNeedsUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
void BTreeMixin<Base>::modify(Index& indexNode, ModifyInfo& info) {
|
||||
size_t i = indexFind(indexNode, info.targetElement.key);
|
||||
Pointer nextPointer = Base::indexPointer(indexNode, i);
|
||||
|
||||
Leaf lowerLeaf;
|
||||
Index lowerIndex;
|
||||
if (Base::indexLevel(indexNode) == 0) {
|
||||
lowerLeaf = Base::loadLeaf(nextPointer);
|
||||
modify(lowerLeaf, info);
|
||||
} else {
|
||||
lowerIndex = Base::loadIndex(nextPointer);
|
||||
modify(lowerIndex, info);
|
||||
}
|
||||
|
||||
if (info.state == Done)
|
||||
return;
|
||||
|
||||
bool selfUpdated = false;
|
||||
|
||||
size_t left = 0;
|
||||
size_t right = 0;
|
||||
if (i != 0 && i == Base::indexPointerCount(indexNode) - 1) {
|
||||
left = i - 1;
|
||||
right = i;
|
||||
} else {
|
||||
left = i;
|
||||
right = i + 1;
|
||||
}
|
||||
|
||||
if (info.state == LeafNeedsJoin) {
|
||||
if (Base::indexPointerCount(indexNode) < 2) {
|
||||
// Don't have enough leaves to join, just do the pending update.
|
||||
info.state = LeafNeedsUpdate;
|
||||
} else {
|
||||
Leaf leftLeaf;
|
||||
Leaf rightLeaf;
|
||||
|
||||
if (left == i) {
|
||||
leftLeaf = lowerLeaf;
|
||||
rightLeaf = Base::loadLeaf(Base::indexPointer(indexNode, right));
|
||||
} else {
|
||||
leftLeaf = Base::loadLeaf(Base::indexPointer(indexNode, left));
|
||||
rightLeaf = lowerLeaf;
|
||||
}
|
||||
|
||||
if (!Base::leafShift(leftLeaf, rightLeaf)) {
|
||||
// Leaves not modified, just do the pending update.
|
||||
info.state = LeafNeedsUpdate;
|
||||
} else if (Base::leafElementCount(rightLeaf) == 0) {
|
||||
// Leaves merged.
|
||||
Base::setNextLeaf(leftLeaf, Base::nextLeaf(rightLeaf));
|
||||
Base::deleteLeaf(move(rightLeaf));
|
||||
|
||||
// Replace two sibling pointer elements with one pointing to merged
|
||||
// leaf.
|
||||
if (left != 0)
|
||||
Base::indexUpdateKeyBefore(indexNode, left, Base::leafKey(leftLeaf, 0));
|
||||
|
||||
Base::indexUpdatePointer(indexNode, left, Base::storeLeaf(move(leftLeaf)));
|
||||
Base::indexRemoveBefore(indexNode, right);
|
||||
|
||||
selfUpdated = true;
|
||||
} else {
|
||||
// Leaves shifted.
|
||||
Base::indexUpdatePointer(indexNode, left, Base::storeLeaf(move(leftLeaf)));
|
||||
|
||||
// Right leaf first key changes on shift, so always need to update
|
||||
// left index node.
|
||||
Base::indexUpdateKeyBefore(indexNode, right, Base::leafKey(rightLeaf, 0));
|
||||
|
||||
Base::indexUpdatePointer(indexNode, right, Base::storeLeaf(move(rightLeaf)));
|
||||
|
||||
selfUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (info.state == IndexNeedsJoin) {
|
||||
if (Base::indexPointerCount(indexNode) < 2) {
|
||||
// Don't have enough indexes to join, just do the pending update.
|
||||
info.state = IndexNeedsUpdate;
|
||||
} else {
|
||||
Index leftIndex;
|
||||
Index rightIndex;
|
||||
|
||||
if (left == i) {
|
||||
leftIndex = lowerIndex;
|
||||
rightIndex = Base::loadIndex(Base::indexPointer(indexNode, right));
|
||||
} else {
|
||||
leftIndex = Base::loadIndex(Base::indexPointer(indexNode, left));
|
||||
rightIndex = lowerIndex;
|
||||
}
|
||||
|
||||
if (!Base::indexShift(leftIndex, getLeftKey(rightIndex), rightIndex)) {
|
||||
// Indexes not modified, just do the pending update.
|
||||
info.state = IndexNeedsUpdate;
|
||||
|
||||
} else if (Base::indexPointerCount(rightIndex) == 0) {
|
||||
// Indexes merged.
|
||||
Base::deleteIndex(move(rightIndex));
|
||||
|
||||
// Replace two sibling pointer elements with one pointing to merged
|
||||
// index.
|
||||
if (left != 0)
|
||||
Base::indexUpdateKeyBefore(indexNode, left, getLeftKey(leftIndex));
|
||||
|
||||
Base::indexUpdatePointer(indexNode, left, Base::storeIndex(move(leftIndex)));
|
||||
Base::indexRemoveBefore(indexNode, right);
|
||||
|
||||
selfUpdated = true;
|
||||
} else {
|
||||
// Indexes shifted.
|
||||
Base::indexUpdatePointer(indexNode, left, Base::storeIndex(move(leftIndex)));
|
||||
|
||||
// Right index first key changes on shift, so always need to update
|
||||
// right index node.
|
||||
Key keyForRight = getLeftKey(rightIndex);
|
||||
Base::indexUpdatePointer(indexNode, right, Base::storeIndex(move(rightIndex)));
|
||||
Base::indexUpdateKeyBefore(indexNode, right, keyForRight);
|
||||
|
||||
selfUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (info.state == LeafSplit) {
|
||||
Base::indexUpdatePointer(indexNode, i, Base::storeLeaf(move(lowerLeaf)));
|
||||
Base::indexInsertAfter(indexNode, i, info.newKey, info.newPointer);
|
||||
selfUpdated = true;
|
||||
}
|
||||
|
||||
if (info.state == IndexSplit) {
|
||||
Base::indexUpdatePointer(indexNode, i, Base::storeIndex(move(lowerIndex)));
|
||||
Base::indexInsertAfter(indexNode, i, info.newKey, info.newPointer);
|
||||
selfUpdated = true;
|
||||
}
|
||||
|
||||
if (info.state == LeafNeedsUpdate) {
|
||||
Pointer lowerLeafPointer = Base::storeLeaf(move(lowerLeaf));
|
||||
if (lowerLeafPointer != Base::indexPointer(indexNode, i)) {
|
||||
Base::indexUpdatePointer(indexNode, i, lowerLeafPointer);
|
||||
selfUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (info.state == IndexNeedsUpdate) {
|
||||
Pointer lowerIndexPointer = Base::storeIndex(move(lowerIndex));
|
||||
if (lowerIndexPointer != Base::indexPointer(indexNode, i)) {
|
||||
Base::indexUpdatePointer(indexNode, i, lowerIndexPointer);
|
||||
selfUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto splitResult = Base::indexSplit(indexNode);
|
||||
if (splitResult) {
|
||||
info.newKey = splitResult->first;
|
||||
info.newPointer = Base::storeIndex(splitResult.take().second);
|
||||
info.state = IndexSplit;
|
||||
selfUpdated = true;
|
||||
} else if (Base::indexNeedsShift(indexNode)) {
|
||||
info.state = IndexNeedsJoin;
|
||||
} else if (selfUpdated) {
|
||||
info.state = IndexNeedsUpdate;
|
||||
} else {
|
||||
info.state = Done;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
bool BTreeMixin<Base>::modify(DataElement e, ModifyAction action) {
|
||||
ModifyInfo info(action, move(e));
|
||||
|
||||
Leaf lowerLeaf;
|
||||
Index lowerIndex;
|
||||
if (Base::rootIsLeaf()) {
|
||||
lowerLeaf = Base::loadLeaf(Base::rootPointer());
|
||||
modify(lowerLeaf, info);
|
||||
} else {
|
||||
lowerIndex = Base::loadIndex(Base::rootPointer());
|
||||
modify(lowerIndex, info);
|
||||
}
|
||||
|
||||
if (info.state == IndexNeedsJoin) {
|
||||
if (Base::indexPointerCount(lowerIndex) == 1) {
|
||||
// If root index has single pointer, then make that the new root.
|
||||
|
||||
// release index first (to support the common use case of delaying
|
||||
// removes until setNewRoot)
|
||||
Pointer pointer = Base::indexPointer(lowerIndex, 0);
|
||||
size_t level = Base::indexLevel(lowerIndex);
|
||||
Base::deleteIndex(move(lowerIndex));
|
||||
Base::setNewRoot(pointer, level == 0);
|
||||
} else {
|
||||
// Else just update.
|
||||
info.state = IndexNeedsUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
if (info.state == LeafNeedsJoin) {
|
||||
// Ignore NeedsJoin on LeafNode root, just update.
|
||||
info.state = LeafNeedsUpdate;
|
||||
}
|
||||
|
||||
if (info.state == LeafSplit || info.state == IndexSplit) {
|
||||
Index newRoot;
|
||||
if (info.state == IndexSplit) {
|
||||
auto rootIndexLevel = Base::indexLevel(lowerIndex) + 1;
|
||||
newRoot = Base::createIndex(Base::storeIndex(move(lowerIndex)));
|
||||
Base::setIndexLevel(newRoot, rootIndexLevel);
|
||||
} else {
|
||||
newRoot = Base::createIndex(Base::storeLeaf(move(lowerLeaf)));
|
||||
Base::setIndexLevel(newRoot, 0);
|
||||
}
|
||||
Base::indexInsertAfter(newRoot, 0, info.newKey, info.newPointer);
|
||||
Base::setNewRoot(Base::storeIndex(move(newRoot)), false);
|
||||
}
|
||||
|
||||
if (info.state == IndexNeedsUpdate) {
|
||||
Pointer newRootPointer = Base::storeIndex(move(lowerIndex));
|
||||
if (newRootPointer != Base::rootPointer())
|
||||
Base::setNewRoot(newRootPointer, false);
|
||||
}
|
||||
|
||||
if (info.state == LeafNeedsUpdate) {
|
||||
Pointer newRootPointer = Base::storeLeaf(move(lowerLeaf));
|
||||
if (newRootPointer != Base::rootPointer())
|
||||
Base::setNewRoot(newRootPointer, true);
|
||||
}
|
||||
|
||||
return info.found;
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
auto BTreeMixin<Base>::getLeftKey(Index const& index) -> Key {
|
||||
if (Base::indexLevel(index) == 0) {
|
||||
Leaf leaf = Base::loadLeaf(Base::indexPointer(index, 0));
|
||||
return Base::leafKey(leaf, 0);
|
||||
} else {
|
||||
return getLeftKey(Base::loadIndex(Base::indexPointer(index, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
template <typename Visitor>
|
||||
void BTreeMixin<Base>::forAllNodes(Index const& index, Visitor&& visitor) {
|
||||
if (!visitor(index))
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < Base::indexPointerCount(index); ++i) {
|
||||
if (Base::indexLevel(index) != 0) {
|
||||
forAllNodes(Base::loadIndex(Base::indexPointer(index, i)), forward<Visitor>(visitor));
|
||||
} else {
|
||||
if (!visitor(Base::loadLeaf(Base::indexPointer(index, i))))
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
pair<size_t, bool> BTreeMixin<Base>::leafFind(Leaf const& leaf, Key const& key) {
|
||||
// Return lower bound binary search result.
|
||||
size_t size = Base::leafElementCount(leaf);
|
||||
if (size == 0)
|
||||
return {0, false};
|
||||
|
||||
size_t len = size;
|
||||
size_t first = 0;
|
||||
size_t middle = 0;
|
||||
size_t half;
|
||||
while (len > 0) {
|
||||
half = len / 2;
|
||||
middle = first + half;
|
||||
if (Base::leafKey(leaf, middle) < key) {
|
||||
first = middle + 1;
|
||||
len = len - half - 1;
|
||||
} else {
|
||||
len = half;
|
||||
}
|
||||
}
|
||||
return make_pair(first, first < size && !(key < Base::leafKey(leaf, first)));
|
||||
}
|
||||
|
||||
template <typename Base>
|
||||
size_t BTreeMixin<Base>::indexFind(Index const& index, Key const& key) {
|
||||
// Return upper bound binary search result of range [1, size];
|
||||
size_t size = Base::indexPointerCount(index);
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
size_t len = size - 1;
|
||||
size_t first = 1;
|
||||
size_t middle = 1;
|
||||
size_t half;
|
||||
while (len > 0) {
|
||||
half = len / 2;
|
||||
middle = first + half;
|
||||
if (key < Base::indexKeyBefore(index, middle)) {
|
||||
len = half;
|
||||
} else {
|
||||
first = middle + 1;
|
||||
len = len - half - 1;
|
||||
}
|
||||
}
|
||||
return first - 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1188
source/core/StarBTreeDatabase.cpp
Normal file
1188
source/core/StarBTreeDatabase.cpp
Normal file
File diff suppressed because it is too large
Load diff
344
source/core/StarBTreeDatabase.hpp
Normal file
344
source/core/StarBTreeDatabase.hpp
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
#ifndef STAR_BTREE_DATABASE_HPP
|
||||
#define STAR_BTREE_DATABASE_HPP
|
||||
|
||||
#include "StarSet.hpp"
|
||||
#include "StarBTree.hpp"
|
||||
#include "StarLruCache.hpp"
|
||||
#include "StarDataStreamDevices.hpp"
|
||||
#include "StarThread.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_EXCEPTION(DBException, IOException);
|
||||
|
||||
class BTreeDatabase {
|
||||
public:
|
||||
uint32_t const ContentIdentifierStringSize = 16;
|
||||
|
||||
BTreeDatabase();
|
||||
BTreeDatabase(String const& contentIdentifier, size_t keySize);
|
||||
~BTreeDatabase();
|
||||
|
||||
// The underlying device will be allocated in "blocks" of this size.
|
||||
// The larger the block size, the larger that index and leaf nodes can be
|
||||
// before they need to be split, but it also means that more space is wasted
|
||||
// for index or leaf nodes that are not completely full. Cannot be changed
|
||||
// once the database is opened. Defaults to 2048.
|
||||
uint32_t blockSize() const;
|
||||
void setBlockSize(uint32_t blockSize);
|
||||
|
||||
// Constant size of the database keys. Should be much smaller than the block
|
||||
// size, cannot be changed once a database is opened. Defaults zero, which
|
||||
// is invalid, so must be set if opening a new database.
|
||||
uint32_t keySize() const;
|
||||
void setKeySize(uint32_t keySize);
|
||||
|
||||
// Must be no greater than ContentIdentifierStringSize large. May not be
|
||||
// called when the database is opened.
|
||||
String contentIdentifier() const;
|
||||
void setContentIdentifier(String contentIdentifier);
|
||||
|
||||
// Cache size for index nodes, defaults to 64
|
||||
uint32_t indexCacheSize() const;
|
||||
void setIndexCacheSize(uint32_t indexCacheSize);
|
||||
|
||||
// If true, very write operation will immediately result in a commit.
|
||||
// Defaults to true.
|
||||
bool autoCommit() const;
|
||||
void setAutoCommit(bool autoCommit);
|
||||
|
||||
IODevicePtr ioDevice() const;
|
||||
void setIODevice(IODevicePtr device);
|
||||
|
||||
// If an existing database is opened, this will update the key size, block
|
||||
// size, and content identifier with those from the opened database.
|
||||
// Otherwise, it will use the currently set values. Returns true if a new
|
||||
// database was created, false if an existing database was found and opened.
|
||||
bool open();
|
||||
|
||||
bool isOpen() const;
|
||||
|
||||
bool contains(ByteArray const& k);
|
||||
|
||||
Maybe<ByteArray> find(ByteArray const& k);
|
||||
List<pair<ByteArray, ByteArray>> find(ByteArray const& lower, ByteArray const& upper);
|
||||
|
||||
void forEach(ByteArray const& lower, ByteArray const& upper, function<void(ByteArray, ByteArray)> v);
|
||||
void forAll(function<void(ByteArray, ByteArray)> v);
|
||||
|
||||
// Returns true if a value was overwritten
|
||||
bool insert(ByteArray const& k, ByteArray const& data);
|
||||
|
||||
// Returns true if the element was found and removed
|
||||
bool remove(ByteArray const& k);
|
||||
|
||||
// Remove all elements in the given range, returns keys removed.
|
||||
List<ByteArray> remove(ByteArray const& lower, ByteArray const& upper);
|
||||
|
||||
uint64_t recordCount();
|
||||
|
||||
// The depth of the index nodes in this database
|
||||
uint8_t indexLevels();
|
||||
|
||||
uint32_t totalBlockCount();
|
||||
uint32_t freeBlockCount();
|
||||
uint32_t indexBlockCount();
|
||||
uint32_t leafBlockCount();
|
||||
|
||||
void commit();
|
||||
void rollback();
|
||||
|
||||
void close(bool closeDevice = false);
|
||||
|
||||
private:
|
||||
typedef uint32_t BlockIndex;
|
||||
static BlockIndex const InvalidBlockIndex = (BlockIndex)(-1);
|
||||
static uint32_t const HeaderSize = 512;
|
||||
|
||||
// 8 byte magic file identifier
|
||||
static char const* const VersionMagic;
|
||||
static uint32_t const VersionMagicSize = 8;
|
||||
// 2 byte leaf and index start markers.
|
||||
static char const* const FreeIndexMagic;
|
||||
static char const* const IndexMagic;
|
||||
static char const* const LeafMagic;
|
||||
// static uint32_t const BlockMagicSize = 2;
|
||||
static size_t const BTreeRootSelectorBit = 32;
|
||||
static size_t const BTreeRootInfoStart = 33;
|
||||
static size_t const BTreeRootInfoSize = 17;
|
||||
|
||||
struct FreeIndexBlock {
|
||||
BlockIndex nextFreeBlock;
|
||||
List<BlockIndex> freeBlocks;
|
||||
};
|
||||
|
||||
struct IndexNode {
|
||||
size_t pointerCount() const;
|
||||
BlockIndex pointer(size_t i) const;
|
||||
void updatePointer(size_t i, BlockIndex p);
|
||||
|
||||
ByteArray const& keyBefore(size_t i) const;
|
||||
void updateKeyBefore(size_t i, ByteArray k);
|
||||
|
||||
void removeBefore(size_t i);
|
||||
void insertAfter(size_t i, ByteArray k, BlockIndex p);
|
||||
|
||||
uint8_t indexLevel() const;
|
||||
void setIndexLevel(uint8_t indexLevel);
|
||||
|
||||
// count is number of elements to shift left *including* right's beginPointer
|
||||
void shiftLeft(ByteArray const& mid, IndexNode& right, size_t count);
|
||||
|
||||
// count is number of elements to shift right
|
||||
void shiftRight(ByteArray const& mid, IndexNode& left, size_t count);
|
||||
|
||||
// i should be index of pointer that will be the new beginPointer of right
|
||||
// node (cannot be 0).
|
||||
ByteArray split(IndexNode& right, size_t i);
|
||||
|
||||
struct Element {
|
||||
ByteArray key;
|
||||
BlockIndex pointer;
|
||||
};
|
||||
typedef List<Element> ElementList;
|
||||
|
||||
BlockIndex self;
|
||||
uint8_t level;
|
||||
Maybe<BlockIndex> beginPointer;
|
||||
ElementList pointers;
|
||||
};
|
||||
|
||||
struct LeafNode {
|
||||
size_t count() const;
|
||||
ByteArray const& key(size_t i) const;
|
||||
ByteArray const& data(size_t i) const;
|
||||
|
||||
void insert(size_t i, ByteArray k, ByteArray d);
|
||||
void remove(size_t i);
|
||||
|
||||
// count is number of elements to shift left
|
||||
void shiftLeft(LeafNode& right, size_t count);
|
||||
|
||||
// count is number of elements to shift right
|
||||
void shiftRight(LeafNode& left, size_t count);
|
||||
|
||||
// i should be index of element that will be the new start of right node.
|
||||
// Returns right index node.
|
||||
void split(LeafNode& right, size_t i);
|
||||
|
||||
struct Element {
|
||||
ByteArray key;
|
||||
ByteArray data;
|
||||
};
|
||||
typedef List<Element> ElementList;
|
||||
|
||||
BlockIndex self;
|
||||
ElementList elements;
|
||||
};
|
||||
|
||||
struct BTreeImpl {
|
||||
typedef ByteArray Key;
|
||||
typedef ByteArray Data;
|
||||
typedef BlockIndex Pointer;
|
||||
|
||||
typedef shared_ptr<IndexNode> Index;
|
||||
typedef shared_ptr<LeafNode> Leaf;
|
||||
|
||||
Pointer rootPointer();
|
||||
bool rootIsLeaf();
|
||||
void setNewRoot(Pointer pointer, bool isLeaf);
|
||||
|
||||
Index createIndex(Pointer beginPointer);
|
||||
Index loadIndex(Pointer pointer);
|
||||
bool indexNeedsShift(Index const& index);
|
||||
bool indexShift(Index const& left, Key const& mid, Index const& right);
|
||||
Maybe<pair<Key, Index>> indexSplit(Index const& index);
|
||||
Pointer storeIndex(Index index);
|
||||
void deleteIndex(Index index);
|
||||
|
||||
Leaf createLeaf();
|
||||
Leaf loadLeaf(Pointer pointer);
|
||||
bool leafNeedsShift(Leaf const& l);
|
||||
bool leafShift(Leaf& left, Leaf& right);
|
||||
Maybe<Leaf> leafSplit(Leaf& leaf);
|
||||
Pointer storeLeaf(Leaf leaf);
|
||||
void deleteLeaf(Leaf leaf);
|
||||
|
||||
size_t indexPointerCount(Index const& index);
|
||||
Pointer indexPointer(Index const& index, size_t i);
|
||||
void indexUpdatePointer(Index& index, size_t i, Pointer p);
|
||||
Key indexKeyBefore(Index const& index, size_t i);
|
||||
void indexUpdateKeyBefore(Index& index, size_t i, Key k);
|
||||
void indexRemoveBefore(Index& index, size_t i);
|
||||
void indexInsertAfter(Index& index, size_t i, Key k, Pointer p);
|
||||
size_t indexLevel(Index const& index);
|
||||
void setIndexLevel(Index& index, size_t indexLevel);
|
||||
|
||||
size_t leafElementCount(Leaf const& leaf);
|
||||
Key leafKey(Leaf const& leaf, size_t i);
|
||||
Data leafData(Leaf const& leaf, size_t i);
|
||||
void leafInsert(Leaf& leaf, size_t i, Key k, Data d);
|
||||
void leafRemove(Leaf& leaf, size_t i);
|
||||
Maybe<Pointer> nextLeaf(Leaf const& leaf);
|
||||
void setNextLeaf(Leaf& leaf, Maybe<Pointer> n);
|
||||
|
||||
BTreeDatabase* parent;
|
||||
};
|
||||
|
||||
void readBlock(BlockIndex blockIndex, size_t blockOffset, char* block, size_t size) const;
|
||||
ByteArray readBlock(BlockIndex blockIndex) const;
|
||||
void updateBlock(BlockIndex blockIndex, ByteArray const& block);
|
||||
|
||||
void rawReadBlock(BlockIndex blockIndex, size_t blockOffset, char* block, size_t size) const;
|
||||
void rawWriteBlock(BlockIndex blockIndex, size_t blockOffset, char const* block, size_t size) const;
|
||||
|
||||
void updateHeadFreeIndexBlock(BlockIndex newHead);
|
||||
|
||||
FreeIndexBlock readFreeIndexBlock(BlockIndex blockIndex);
|
||||
void writeFreeIndexBlock(BlockIndex blockIndex, FreeIndexBlock indexBlock);
|
||||
|
||||
uint32_t leafSize(shared_ptr<LeafNode> const& leaf) const;
|
||||
uint32_t maxIndexPointers() const;
|
||||
|
||||
uint32_t dataSize(ByteArray const& d) const;
|
||||
List<BlockIndex> leafTailBlocks(BlockIndex leafPointer);
|
||||
|
||||
void freeBlock(BlockIndex b);
|
||||
BlockIndex reserveBlock();
|
||||
BlockIndex makeEndBlock();
|
||||
|
||||
void dirty();
|
||||
void writeRoot();
|
||||
void readRoot();
|
||||
void doCommit();
|
||||
|
||||
void checkIfOpen(char const* methodName, bool shouldBeOpen) const;
|
||||
void checkBlockIndex(size_t blockIndex) const;
|
||||
void checkKeySize(ByteArray const& k) const;
|
||||
uint32_t maxFreeIndexLength() const;
|
||||
|
||||
mutable ReadersWriterMutex m_lock;
|
||||
|
||||
BTreeMixin<BTreeImpl> m_impl;
|
||||
|
||||
IODevicePtr m_device;
|
||||
bool m_open;
|
||||
|
||||
uint32_t m_blockSize;
|
||||
String m_contentIdentifier;
|
||||
uint32_t m_keySize;
|
||||
|
||||
bool m_autoCommit;
|
||||
|
||||
// Reading values can mutate the index cache, so the index cache is kept
|
||||
// using a different lock. It is only necessary to acquire this lock when
|
||||
// NOT holding the main writer lock, because if the main writer lock is held
|
||||
// then no other method would be loading an index anyway.
|
||||
mutable SpinLock m_indexCacheSpinLock;
|
||||
LruCache<BlockIndex, shared_ptr<IndexNode>> m_indexCache;
|
||||
|
||||
BlockIndex m_headFreeIndexBlock;
|
||||
StreamOffset m_deviceSize;
|
||||
BlockIndex m_root;
|
||||
bool m_rootIsLeaf;
|
||||
bool m_usingAltRoot;
|
||||
bool m_dirty;
|
||||
|
||||
// Blocks that can be freely allocated and written to without violating
|
||||
// atomic consistency
|
||||
Set<BlockIndex> m_availableBlocks;
|
||||
|
||||
// Blocks to be freed on next commit.
|
||||
Deque<BlockIndex> m_pendingFree;
|
||||
|
||||
// Blocks that have been written in uncommitted portions of the tree.
|
||||
Set<BlockIndex> m_uncommitted;
|
||||
};
|
||||
|
||||
// Version of BTreeDatabase that hashes keys with SHA-256 to produce a unique
|
||||
// constant size key.
|
||||
class BTreeSha256Database : private BTreeDatabase {
|
||||
public:
|
||||
BTreeSha256Database();
|
||||
BTreeSha256Database(String const& contentIdentifier);
|
||||
|
||||
// Keys can be arbitrary size, actual key is the SHA-256 checksum of the key.
|
||||
bool contains(ByteArray const& key);
|
||||
Maybe<ByteArray> find(ByteArray const& key);
|
||||
bool insert(ByteArray const& key, ByteArray const& value);
|
||||
bool remove(ByteArray const& key);
|
||||
|
||||
// Convenience string versions of access methods. Equivalent to the utf8
|
||||
// bytes of the string minus the null terminator.
|
||||
bool contains(String const& key);
|
||||
Maybe<ByteArray> find(String const& key);
|
||||
bool insert(String const& key, ByteArray const& value);
|
||||
bool remove(String const& key);
|
||||
|
||||
using BTreeDatabase::ContentIdentifierStringSize;
|
||||
using BTreeDatabase::blockSize;
|
||||
using BTreeDatabase::setBlockSize;
|
||||
using BTreeDatabase::contentIdentifier;
|
||||
using BTreeDatabase::setContentIdentifier;
|
||||
using BTreeDatabase::indexCacheSize;
|
||||
using BTreeDatabase::setIndexCacheSize;
|
||||
using BTreeDatabase::autoCommit;
|
||||
using BTreeDatabase::setAutoCommit;
|
||||
using BTreeDatabase::ioDevice;
|
||||
using BTreeDatabase::setIODevice;
|
||||
using BTreeDatabase::open;
|
||||
using BTreeDatabase::isOpen;
|
||||
using BTreeDatabase::recordCount;
|
||||
using BTreeDatabase::indexLevels;
|
||||
using BTreeDatabase::totalBlockCount;
|
||||
using BTreeDatabase::freeBlockCount;
|
||||
using BTreeDatabase::indexBlockCount;
|
||||
using BTreeDatabase::leafBlockCount;
|
||||
using BTreeDatabase::commit;
|
||||
using BTreeDatabase::rollback;
|
||||
using BTreeDatabase::close;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
419
source/core/StarBiMap.hpp
Normal file
419
source/core/StarBiMap.hpp
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
#ifndef STAR_BI_MAP_HPP
|
||||
#define STAR_BI_MAP_HPP
|
||||
|
||||
#include "StarString.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// Bi-directional map of unique sets of elements with quick map access from
|
||||
// either the left or right element to the other side. Every left side value
|
||||
// must be unique from every other left side value and the same for the right
|
||||
// side.
|
||||
template <typename LeftT,
|
||||
typename RightT,
|
||||
typename LeftMapT = Map<LeftT, RightT const*>,
|
||||
typename RightMapT = Map<RightT, LeftT const*>>
|
||||
class BiMap {
|
||||
public:
|
||||
typedef LeftT Left;
|
||||
typedef RightT Right;
|
||||
typedef LeftMapT LeftMap;
|
||||
typedef RightMapT RightMap;
|
||||
|
||||
typedef pair<Left, Right> value_type;
|
||||
|
||||
struct BiMapIterator {
|
||||
BiMapIterator& operator++();
|
||||
BiMapIterator operator++(int);
|
||||
|
||||
bool operator==(BiMapIterator const& rhs) const;
|
||||
bool operator!=(BiMapIterator const& rhs) const;
|
||||
|
||||
pair<Left const&, Right const&> operator*() const;
|
||||
|
||||
typename LeftMap::const_iterator iterator;
|
||||
};
|
||||
|
||||
typedef BiMapIterator iterator;
|
||||
typedef iterator const_iterator;
|
||||
|
||||
template <typename Collection>
|
||||
static BiMap from(Collection const& c);
|
||||
|
||||
BiMap();
|
||||
BiMap(BiMap const& map);
|
||||
|
||||
template <typename InputIterator>
|
||||
BiMap(InputIterator beg, InputIterator end);
|
||||
|
||||
BiMap(std::initializer_list<value_type> list);
|
||||
|
||||
List<Left> leftValues() const;
|
||||
List<Right> rightValues() const;
|
||||
List<value_type> pairs() const;
|
||||
|
||||
bool hasLeftValue(Left const& left) const;
|
||||
bool hasRightValue(Right const& right) const;
|
||||
|
||||
Right const& getRight(Left const& left) const;
|
||||
Left const& getLeft(Right const& right) const;
|
||||
|
||||
Right valueRight(Left const& left, Right const& def = Right()) const;
|
||||
Left valueLeft(Right const& right, Left const& def = Left()) const;
|
||||
|
||||
Maybe<Right> maybeRight(Left const& left) const;
|
||||
|
||||
Maybe<Left> maybeLeft(Right const& right) const;
|
||||
|
||||
Right takeRight(Left const& left);
|
||||
Left takeLeft(Right const& right);
|
||||
|
||||
Maybe<Right> maybeTakeRight(Left const& left);
|
||||
Maybe<Left> maybeTakeLeft(Right const& right);
|
||||
|
||||
Right const* rightPtr(Left const& left) const;
|
||||
Left const* leftPtr(Right const& right) const;
|
||||
|
||||
BiMap& operator=(BiMap const& map);
|
||||
|
||||
pair<iterator, bool> insert(value_type const& val);
|
||||
|
||||
// Returns true if value was inserted, false if either the left or right side
|
||||
// already existed.
|
||||
bool insert(Left const& left, Right const& right);
|
||||
|
||||
// Throws an exception if the pair cannot be inserted
|
||||
void add(Left const& left, Right const& right);
|
||||
void add(value_type const& value);
|
||||
|
||||
// Overwrites the left / right mapping regardless of whether each side
|
||||
// already exists.
|
||||
void overwrite(Left const& left, Right const& right);
|
||||
void overwrite(value_type const& value);
|
||||
|
||||
// Removes the pair with the given left side, returns true if this pair was
|
||||
// found, false otherwise.
|
||||
bool removeLeft(Left const& left);
|
||||
|
||||
// Removes the pair with the given right side, returns true if this pair was
|
||||
// found, false otherwise.
|
||||
bool removeRight(Right const& right);
|
||||
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
|
||||
size_t size() const;
|
||||
|
||||
void clear();
|
||||
|
||||
bool empty() const;
|
||||
|
||||
bool operator==(BiMap const& m) const;
|
||||
|
||||
private:
|
||||
LeftMap m_leftMap;
|
||||
RightMap m_rightMap;
|
||||
};
|
||||
|
||||
template <typename Left, typename Right, typename LeftHash = Star::hash<Left>, typename RightHash = Star::hash<Right>>
|
||||
using BiHashMap = BiMap<Left, Right, StableHashMap<Left, Right const*, LeftHash>, StableHashMap<Right, Left const*, RightHash>>;
|
||||
|
||||
// Case insensitive Enum <-> String map
|
||||
template <typename EnumType>
|
||||
using EnumMap = BiMap<EnumType,
|
||||
String,
|
||||
Map<EnumType, String const*>,
|
||||
StableHashMap<String, EnumType const*, CaseInsensitiveStringHash, CaseInsensitiveStringCompare>>;
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
auto BiMap<LeftT, RightT, LeftMapT, RightMapT>::BiMapIterator::operator++() -> BiMapIterator & {
|
||||
++iterator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
auto BiMap<LeftT, RightT, LeftMapT, RightMapT>::BiMapIterator::operator++(int) -> BiMapIterator {
|
||||
BiMapIterator last{iterator};
|
||||
++iterator;
|
||||
return last;
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
bool BiMap<LeftT, RightT, LeftMapT, RightMapT>::BiMapIterator::operator==(BiMapIterator const& rhs) const {
|
||||
return iterator == rhs.iterator;
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
bool BiMap<LeftT, RightT, LeftMapT, RightMapT>::BiMapIterator::operator!=(BiMapIterator const& rhs) const {
|
||||
return iterator != rhs.iterator;
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
pair<LeftT const&, RightT const&> BiMap<LeftT, RightT, LeftMapT, RightMapT>::BiMapIterator::operator*() const {
|
||||
return {iterator->first, *iterator->second};
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
template <typename Collection>
|
||||
BiMap<LeftT, RightT, LeftMapT, RightMapT> BiMap<LeftT, RightT, LeftMapT, RightMapT>::from(Collection const& c) {
|
||||
return BiMap(c.begin(), c.end());
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
BiMap<LeftT, RightT, LeftMapT, RightMapT>::BiMap() {}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
BiMap<LeftT, RightT, LeftMapT, RightMapT>::BiMap(BiMap const& map)
|
||||
: BiMap(map.begin(), map.end()) {}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
template <typename InputIterator>
|
||||
BiMap<LeftT, RightT, LeftMapT, RightMapT>::BiMap(InputIterator beg, InputIterator end) {
|
||||
while (beg != end) {
|
||||
insert(*beg);
|
||||
++beg;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
BiMap<LeftT, RightT, LeftMapT, RightMapT>::BiMap(std::initializer_list<value_type> list) {
|
||||
for (value_type const& v : list) {
|
||||
if (!insert(v.first, v.second))
|
||||
throw MapException::format("Repeat pair in BiMap initializer_list construction: (%s, %s)", outputAny(v.first), outputAny(v.second));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
List<LeftT> BiMap<LeftT, RightT, LeftMapT, RightMapT>::leftValues() const {
|
||||
return m_leftMap.keys();
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
List<RightT> BiMap<LeftT, RightT, LeftMapT, RightMapT>::rightValues() const {
|
||||
return m_rightMap.keys();
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
auto BiMap<LeftT, RightT, LeftMapT, RightMapT>::pairs() const -> List<value_type> {
|
||||
List<value_type> values;
|
||||
for (auto const& p : *this)
|
||||
values.append(p);
|
||||
return values;
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
bool BiMap<LeftT, RightT, LeftMapT, RightMapT>::hasLeftValue(Left const& left) const {
|
||||
return m_leftMap.contains(left);
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
bool BiMap<LeftT, RightT, LeftMapT, RightMapT>::hasRightValue(Right const& right) const {
|
||||
return m_rightMap.contains(right);
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
RightT const& BiMap<LeftT, RightT, LeftMapT, RightMapT>::getRight(Left const& left) const {
|
||||
return *m_leftMap.get(left);
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
LeftT const& BiMap<LeftT, RightT, LeftMapT, RightMapT>::getLeft(Right const& right) const {
|
||||
return *m_rightMap.get(right);
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
RightT BiMap<LeftT, RightT, LeftMapT, RightMapT>::valueRight(Left const& left, Right const& def) const {
|
||||
return maybeRight(left).value(def);
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
LeftT BiMap<LeftT, RightT, LeftMapT, RightMapT>::valueLeft(Right const& right, Left const& def) const {
|
||||
return maybeLeft(right).value(def);
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
Maybe<RightT> BiMap<LeftT, RightT, LeftMapT, RightMapT>::maybeRight(Left const& left) const {
|
||||
auto i = m_leftMap.find(left);
|
||||
if (i != m_leftMap.end())
|
||||
return *i->second;
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
Maybe<LeftT> BiMap<LeftT, RightT, LeftMapT, RightMapT>::maybeLeft(Right const& right) const {
|
||||
auto i = m_rightMap.find(right);
|
||||
if (i != m_rightMap.end())
|
||||
return *i->second;
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
RightT BiMap<LeftT, RightT, LeftMapT, RightMapT>::takeRight(Left const& left) {
|
||||
if (auto right = maybeTakeRight(left))
|
||||
return right.take();
|
||||
throw MapException::format("No such key in BiMap::takeRight", outputAny(left));
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
LeftT BiMap<LeftT, RightT, LeftMapT, RightMapT>::takeLeft(Right const& right) {
|
||||
if (auto left = maybeTakeLeft(right))
|
||||
return left.take();
|
||||
throw MapException::format("No such key in BiMap::takeLeft", outputAny(right));
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
Maybe<RightT> BiMap<LeftT, RightT, LeftMapT, RightMapT>::maybeTakeRight(Left const& left) {
|
||||
if (auto rightPtr = m_leftMap.maybeTake(left).value()) {
|
||||
Right right = *rightPtr;
|
||||
m_rightMap.remove(*rightPtr);
|
||||
return right;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
Maybe<LeftT> BiMap<LeftT, RightT, LeftMapT, RightMapT>::maybeTakeLeft(Right const& right) {
|
||||
if (auto leftPtr = m_rightMap.maybeTake(right).value()) {
|
||||
Left left = *leftPtr;
|
||||
m_leftMap.remove(*leftPtr);
|
||||
return left;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
RightT const* BiMap<LeftT, RightT, LeftMapT, RightMapT>::rightPtr(Left const& left) const {
|
||||
return m_leftMap.value(left);
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
LeftT const* BiMap<LeftT, RightT, LeftMapT, RightMapT>::leftPtr(Right const& right) const {
|
||||
return m_rightMap.value(right);
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
BiMap<LeftT, RightT, LeftMapT, RightMapT>& BiMap<LeftT, RightT, LeftMapT, RightMapT>::operator=(BiMap const& map) {
|
||||
if (this != &map) {
|
||||
clear();
|
||||
for (auto const& p : map)
|
||||
insert(p);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
auto BiMap<LeftT, RightT, LeftMapT, RightMapT>::insert(value_type const& val) -> pair<iterator, bool> {
|
||||
auto leftRes = m_leftMap.insert(make_pair(val.first, nullptr));
|
||||
if (!leftRes.second)
|
||||
return {BiMapIterator{leftRes.first}, false};
|
||||
|
||||
auto rightRes = m_rightMap.insert(make_pair(val.second, nullptr));
|
||||
starAssert(rightRes.second == true);
|
||||
leftRes.first->second = &rightRes.first->first;
|
||||
rightRes.first->second = &leftRes.first->first;
|
||||
return {BiMapIterator{leftRes.first}, true};
|
||||
};
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
bool BiMap<LeftT, RightT, LeftMapT, RightMapT>::insert(Left const& left, Right const& right) {
|
||||
return insert(make_pair(left, right)).second;
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
void BiMap<LeftT, RightT, LeftMapT, RightMapT>::add(Left const& left, Right const& right) {
|
||||
if (m_leftMap.contains(left))
|
||||
throw MapException(strf("BiMap already contains left side value '%s'", outputAny(left)));
|
||||
|
||||
if (m_rightMap.contains(right))
|
||||
throw MapException(strf("BiMap already contains right side value '%s'", outputAny(right)));
|
||||
|
||||
insert(left, right);
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
void BiMap<LeftT, RightT, LeftMapT, RightMapT>::add(value_type const& value) {
|
||||
add(value.first, value.second);
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
void BiMap<LeftT, RightT, LeftMapT, RightMapT>::overwrite(Left const& left, Right const& right) {
|
||||
removeLeft(left);
|
||||
removeRight(right);
|
||||
insert(left, right);
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
void BiMap<LeftT, RightT, LeftMapT, RightMapT>::overwrite(value_type const& value) {
|
||||
return overwrite(value.first, value.second);
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
bool BiMap<LeftT, RightT, LeftMapT, RightMapT>::removeLeft(Left const& left) {
|
||||
if (auto right = m_leftMap.value(left)) {
|
||||
m_rightMap.remove(*right);
|
||||
m_leftMap.remove(left);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
bool BiMap<LeftT, RightT, LeftMapT, RightMapT>::removeRight(Right const& right) {
|
||||
if (auto left = m_rightMap.value(right)) {
|
||||
m_leftMap.remove(*left);
|
||||
m_rightMap.remove(right);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
auto BiMap<LeftT, RightT, LeftMapT, RightMapT>::begin() const -> const_iterator {
|
||||
return BiMapIterator{m_leftMap.begin()};
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
auto BiMap<LeftT, RightT, LeftMapT, RightMapT>::end() const -> const_iterator {
|
||||
return BiMapIterator{m_leftMap.end()};
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
size_t BiMap<LeftT, RightT, LeftMapT, RightMapT>::size() const {
|
||||
return m_leftMap.size();
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
void BiMap<LeftT, RightT, LeftMapT, RightMapT>::clear() {
|
||||
m_leftMap.clear();
|
||||
m_rightMap.clear();
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
bool BiMap<LeftT, RightT, LeftMapT, RightMapT>::empty() const {
|
||||
return m_leftMap.empty();
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT, typename LeftMapT, typename RightMapT>
|
||||
bool BiMap<LeftT, RightT, LeftMapT, RightMapT>::operator==(BiMap const& m) const {
|
||||
if (&m == this)
|
||||
return true;
|
||||
|
||||
if (size() != m.size())
|
||||
return false;
|
||||
|
||||
for (auto const& pair : *this) {
|
||||
if (auto p = m.rightPtr(pair.first))
|
||||
if (!p || *p != pair.second)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
268
source/core/StarBlockAllocator.hpp
Normal file
268
source/core/StarBlockAllocator.hpp
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
#ifndef STAR_BLOCK_ALLOCATOR_HPP
|
||||
#define STAR_BLOCK_ALLOCATOR_HPP
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <limits>
|
||||
#include <typeindex>
|
||||
|
||||
#include "StarException.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
// Constant size only allocator using fixed size blocks of memory. much faster
|
||||
// than general purpose allocators, but not thread safe. Useful as the
|
||||
// allocator for containers that mostly allocate one element at a time, such as
|
||||
// std::list, std::map, std::set etc.
|
||||
template <typename T, size_t BlockSize>
|
||||
class BlockAllocator {
|
||||
public:
|
||||
typedef T value_type;
|
||||
|
||||
typedef T* pointer;
|
||||
typedef T const* const_pointer;
|
||||
|
||||
typedef T& reference;
|
||||
typedef T const& const_reference;
|
||||
|
||||
// Allocator can be shared, but since it is NOT thread safe this should not
|
||||
// be done by default.
|
||||
typedef std::false_type propagate_on_container_copy_assignment;
|
||||
typedef std::true_type propagate_on_container_move_assignment;
|
||||
typedef std::true_type propagate_on_container_swap;
|
||||
|
||||
template <class U>
|
||||
struct rebind {
|
||||
typedef BlockAllocator<U, BlockSize> other;
|
||||
};
|
||||
|
||||
BlockAllocator();
|
||||
// Copy constructed BlockAllocators of the same type share underlying
|
||||
// resources.
|
||||
BlockAllocator(BlockAllocator const& other) = default;
|
||||
BlockAllocator(BlockAllocator&& other) = default;
|
||||
// Copy constructed BlockAllocators of different type share no resources
|
||||
template <class U>
|
||||
BlockAllocator(BlockAllocator<U, BlockSize> const& other);
|
||||
|
||||
BlockAllocator& operator=(BlockAllocator const& rhs) = default;
|
||||
BlockAllocator& operator=(BlockAllocator&& rhs) = default;
|
||||
|
||||
// If n is != 1, will fall back on std::allocator<T>
|
||||
T* allocate(size_t n);
|
||||
void deallocate(T* p, size_t n);
|
||||
|
||||
template <typename... Args>
|
||||
void construct(pointer p, Args&&... args) const;
|
||||
void destroy(pointer p) const;
|
||||
|
||||
// BlockAllocator will always be != to any other BlockAllocator instance
|
||||
template <class U>
|
||||
bool operator==(BlockAllocator<U, BlockSize> const& rhs) const;
|
||||
template <class U>
|
||||
bool operator!=(BlockAllocator<U, BlockSize> const& rhs) const;
|
||||
|
||||
private:
|
||||
template <typename OtherT, size_t OtherBlockSize>
|
||||
friend class BlockAllocator;
|
||||
|
||||
using ChunkIndex =
|
||||
std::conditional_t<BlockSize <= std::numeric_limits<uint8_t>::max(), uint8_t,
|
||||
std::conditional_t<BlockSize <= std::numeric_limits<uint16_t>::max(), uint16_t,
|
||||
std::conditional_t<BlockSize <= std::numeric_limits<uint32_t>::max(), uint32_t,
|
||||
std::conditional_t<BlockSize <= std::numeric_limits<uint64_t>::max(), uint64_t, uintmax_t>>>>;
|
||||
|
||||
static ChunkIndex const NullChunkIndex = std::numeric_limits<ChunkIndex>::max();
|
||||
|
||||
struct Unallocated {
|
||||
ChunkIndex prev;
|
||||
ChunkIndex next;
|
||||
};
|
||||
|
||||
typedef std::aligned_union_t<0, T, Unallocated> Chunk;
|
||||
|
||||
struct Block {
|
||||
T* allocate();
|
||||
void deallocate(T* ptr);
|
||||
|
||||
bool full() const;
|
||||
bool empty() const;
|
||||
|
||||
Chunk* chunkPointer(ChunkIndex chunkIndex);
|
||||
|
||||
std::array<Chunk, BlockSize> chunks;
|
||||
ChunkIndex firstUnallocated = NullChunkIndex;
|
||||
ChunkIndex allocationCount = 0;
|
||||
};
|
||||
|
||||
struct Data {
|
||||
std::vector<unique_ptr<Block>> blocks;
|
||||
Block* unfilledBlock;
|
||||
std::allocator<T> multiAllocator;
|
||||
};
|
||||
|
||||
typedef std::unordered_map<std::type_index, shared_ptr<void>> BlockAllocatorFamily;
|
||||
|
||||
static Data* getAllocatorData(BlockAllocatorFamily& family);
|
||||
|
||||
shared_ptr<BlockAllocatorFamily> m_family;
|
||||
Data* m_data;
|
||||
};
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
BlockAllocator<T, BlockSize>::BlockAllocator() {
|
||||
m_family = make_shared<BlockAllocatorFamily>();
|
||||
m_data = getAllocatorData(*m_family);
|
||||
m_data->blocks.reserve(32);
|
||||
m_data->unfilledBlock = nullptr;
|
||||
}
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
template <class U>
|
||||
BlockAllocator<T, BlockSize>::BlockAllocator(BlockAllocator<U, BlockSize> const& other)
|
||||
: m_family(other.m_family) {
|
||||
m_data = getAllocatorData(*m_family);
|
||||
}
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
T* BlockAllocator<T, BlockSize>::allocate(size_t n) {
|
||||
if (n == 1) {
|
||||
if (m_data->unfilledBlock == nullptr) {
|
||||
for (auto const& p : m_data->blocks) {
|
||||
if (!p->full()) {
|
||||
m_data->unfilledBlock = p.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_data->unfilledBlock) {
|
||||
auto block = make_unique<Block>();
|
||||
m_data->unfilledBlock = block.get();
|
||||
auto sortedPosition = std::lower_bound(m_data->blocks.begin(), m_data->blocks.end(), block.get(), [](std::unique_ptr<Block> const& a, Block* b) {
|
||||
return a.get() < b;
|
||||
});
|
||||
m_data->blocks.insert(sortedPosition, move(block));
|
||||
}
|
||||
}
|
||||
|
||||
auto allocated = m_data->unfilledBlock->allocate();
|
||||
if (m_data->unfilledBlock->full())
|
||||
m_data->unfilledBlock = nullptr;
|
||||
return allocated;
|
||||
} else {
|
||||
return m_data->multiAllocator.allocate(n);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
void BlockAllocator<T, BlockSize>::deallocate(T* p, size_t n) {
|
||||
if (n == 1) {
|
||||
starAssert(p);
|
||||
|
||||
auto i = std::upper_bound(m_data->blocks.begin(), m_data->blocks.end(), p, [](T* a, std::unique_ptr<Block> const& b) {
|
||||
return a < (T*)b->chunkPointer(0);
|
||||
});
|
||||
|
||||
starAssert(i != m_data->blocks.begin());
|
||||
--i;
|
||||
|
||||
(*i)->deallocate(p);
|
||||
|
||||
if (!m_data->unfilledBlock) {
|
||||
m_data->unfilledBlock = i->get();
|
||||
} else if ((*i)->empty()) {
|
||||
if (m_data->unfilledBlock != i->get())
|
||||
m_data->blocks.erase(i);
|
||||
}
|
||||
} else {
|
||||
m_data->multiAllocator.deallocate(p, n);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
template <typename... Args>
|
||||
void BlockAllocator<T, BlockSize>::construct(pointer p, Args&&... args) const {
|
||||
new (p) T(forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
void BlockAllocator<T, BlockSize>::destroy(pointer p) const {
|
||||
p->~T();
|
||||
}
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
template <class U>
|
||||
bool BlockAllocator<T, BlockSize>::operator==(BlockAllocator<U, BlockSize> const& rhs) const {
|
||||
return m_family == rhs.m_family;
|
||||
}
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
template <class U>
|
||||
bool BlockAllocator<T, BlockSize>::operator!=(BlockAllocator<U, BlockSize> const& rhs) const {
|
||||
return m_family != rhs.m_family;
|
||||
}
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
T* BlockAllocator<T, BlockSize>::Block::allocate() {
|
||||
starAssert(allocationCount < BlockSize);
|
||||
|
||||
T* allocated;
|
||||
if (firstUnallocated == NullChunkIndex) {
|
||||
allocated = (T*)chunkPointer(allocationCount);
|
||||
} else {
|
||||
void* chunk = chunkPointer(firstUnallocated);
|
||||
starAssert(((Unallocated*)chunk)->prev == NullChunkIndex);
|
||||
firstUnallocated = ((Unallocated*)chunk)->next;
|
||||
if (firstUnallocated != NullChunkIndex)
|
||||
((Unallocated*)chunkPointer(firstUnallocated))->prev = NullChunkIndex;
|
||||
allocated = (T*)chunk;
|
||||
}
|
||||
|
||||
++allocationCount;
|
||||
return allocated;
|
||||
}
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
void BlockAllocator<T, BlockSize>::Block::deallocate(T* ptr) {
|
||||
starAssert(allocationCount > 0);
|
||||
|
||||
ChunkIndex chunkIndex = ptr - (T*)chunkPointer(0);
|
||||
starAssert((T*)chunkPointer(chunkIndex) == ptr);
|
||||
|
||||
auto c = (Unallocated*)chunkPointer(chunkIndex);
|
||||
c->prev = NullChunkIndex;
|
||||
c->next = firstUnallocated;
|
||||
if (firstUnallocated != NullChunkIndex)
|
||||
((Unallocated*)chunkPointer(firstUnallocated))->prev = chunkIndex;
|
||||
firstUnallocated = chunkIndex;
|
||||
--allocationCount;
|
||||
}
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
bool BlockAllocator<T, BlockSize>::Block::full() const {
|
||||
return allocationCount == BlockSize;
|
||||
}
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
bool BlockAllocator<T, BlockSize>::Block::empty() const {
|
||||
return allocationCount == 0;
|
||||
}
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
auto BlockAllocator<T, BlockSize>::Block::chunkPointer(ChunkIndex chunkIndex) -> Chunk* {
|
||||
starAssert(chunkIndex < BlockSize);
|
||||
return &chunks[chunkIndex];
|
||||
}
|
||||
|
||||
template <typename T, size_t BlockSize>
|
||||
typename BlockAllocator<T, BlockSize>::Data* BlockAllocator<T, BlockSize>::getAllocatorData(BlockAllocatorFamily& family) {
|
||||
auto& dataptr = family[typeid(Data)];
|
||||
if (!dataptr)
|
||||
dataptr = make_shared<Data>();
|
||||
return (Data*)dataptr.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
287
source/core/StarBuffer.cpp
Normal file
287
source/core/StarBuffer.cpp
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
#include "StarBuffer.hpp"
|
||||
#include "StarMathCommon.hpp"
|
||||
#include "StarIODevice.hpp"
|
||||
#include "StarFormat.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
Buffer::Buffer()
|
||||
: m_pos(0) {
|
||||
setMode(IOMode::ReadWrite);
|
||||
}
|
||||
|
||||
Buffer::Buffer(size_t initialSize)
|
||||
: Buffer() {
|
||||
reset(initialSize);
|
||||
}
|
||||
|
||||
Buffer::Buffer(ByteArray b)
|
||||
: Buffer() {
|
||||
reset(move(b));
|
||||
}
|
||||
|
||||
Buffer::Buffer(Buffer const& buffer)
|
||||
: Buffer() {
|
||||
operator=(buffer);
|
||||
}
|
||||
|
||||
Buffer::Buffer(Buffer&& buffer)
|
||||
: Buffer() {
|
||||
operator=(move(buffer));
|
||||
}
|
||||
|
||||
StreamOffset Buffer::pos() {
|
||||
return m_pos;
|
||||
}
|
||||
|
||||
void Buffer::seek(StreamOffset pos, IOSeek mode) {
|
||||
StreamOffset newPos = m_pos;
|
||||
if (mode == IOSeek::Absolute)
|
||||
newPos = pos;
|
||||
else if (mode == IOSeek::Relative)
|
||||
newPos += pos;
|
||||
else if (mode == IOSeek::End)
|
||||
newPos = m_bytes.size() - pos;
|
||||
m_pos = newPos;
|
||||
}
|
||||
|
||||
void Buffer::resize(StreamOffset size) {
|
||||
data().resize((size_t)size);
|
||||
}
|
||||
|
||||
bool Buffer::atEnd() {
|
||||
return m_pos >= m_bytes.size();
|
||||
}
|
||||
|
||||
size_t Buffer::read(char* data, size_t len) {
|
||||
size_t l = doRead(m_pos, data, len);
|
||||
m_pos += l;
|
||||
return l;
|
||||
}
|
||||
|
||||
size_t Buffer::write(char const* data, size_t len) {
|
||||
size_t l = doWrite(m_pos, data, len);
|
||||
m_pos += l;
|
||||
return l;
|
||||
}
|
||||
|
||||
size_t Buffer::readAbsolute(StreamOffset readPosition, char* data, size_t len) {
|
||||
size_t rpos = readPosition;
|
||||
if ((StreamOffset)rpos != readPosition)
|
||||
throw IOException("Error, readPosition out of range");
|
||||
|
||||
return doRead(rpos, data, len);
|
||||
}
|
||||
|
||||
size_t Buffer::writeAbsolute(StreamOffset writePosition, char const* data, size_t len) {
|
||||
size_t wpos = writePosition;
|
||||
if ((StreamOffset)wpos != writePosition)
|
||||
throw IOException("Error, writePosition out of range");
|
||||
|
||||
return doWrite(wpos, data, len);
|
||||
}
|
||||
|
||||
void Buffer::open(IOMode mode) {
|
||||
setMode(mode);
|
||||
if (mode & IOMode::Write && mode & IOMode::Truncate)
|
||||
resize(0);
|
||||
if (mode & IOMode::Append)
|
||||
seek(0, IOSeek::End);
|
||||
}
|
||||
|
||||
String Buffer::deviceName() const {
|
||||
return strf("Buffer <%s>", this);
|
||||
}
|
||||
|
||||
StreamOffset Buffer::size() {
|
||||
return m_bytes.size();
|
||||
}
|
||||
|
||||
ByteArray& Buffer::data() {
|
||||
return m_bytes;
|
||||
}
|
||||
|
||||
ByteArray const& Buffer::data() const {
|
||||
return m_bytes;
|
||||
}
|
||||
|
||||
ByteArray Buffer::takeData() {
|
||||
ByteArray ret = move(m_bytes);
|
||||
reset(0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
char* Buffer::ptr() {
|
||||
return data().ptr();
|
||||
}
|
||||
|
||||
char const* Buffer::ptr() const {
|
||||
return m_bytes.ptr();
|
||||
}
|
||||
|
||||
size_t Buffer::dataSize() const {
|
||||
return m_bytes.size();
|
||||
}
|
||||
|
||||
void Buffer::reserve(size_t size) {
|
||||
data().reserve(size);
|
||||
}
|
||||
|
||||
void Buffer::clear() {
|
||||
m_pos = 0;
|
||||
m_bytes.clear();
|
||||
}
|
||||
|
||||
bool Buffer::empty() const {
|
||||
return m_bytes.empty();
|
||||
}
|
||||
|
||||
void Buffer::reset(size_t newSize) {
|
||||
m_pos = 0;
|
||||
m_bytes.fill(newSize, 0);
|
||||
}
|
||||
|
||||
void Buffer::reset(ByteArray b) {
|
||||
m_pos = 0;
|
||||
m_bytes = move(b);
|
||||
}
|
||||
|
||||
Buffer& Buffer::operator=(Buffer const& buffer) {
|
||||
IODevice::operator=(buffer);
|
||||
m_pos = buffer.m_pos;
|
||||
m_bytes = buffer.m_bytes;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Buffer& Buffer::operator=(Buffer&& buffer) {
|
||||
IODevice::operator=(buffer);
|
||||
m_pos = buffer.m_pos;
|
||||
m_bytes = move(buffer.m_bytes);
|
||||
|
||||
buffer.m_pos = 0;
|
||||
buffer.m_bytes = ByteArray();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t Buffer::doRead(size_t pos, char* data, size_t len) {
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
if (!isReadable())
|
||||
throw IOException("Error, read called on non-readable Buffer");
|
||||
|
||||
if (pos >= m_bytes.size())
|
||||
return 0;
|
||||
|
||||
size_t l = min(m_bytes.size() - pos, len);
|
||||
memcpy(data, m_bytes.ptr() + pos, l);
|
||||
return l;
|
||||
}
|
||||
|
||||
size_t Buffer::doWrite(size_t pos, char const* data, size_t len) {
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
if (!isWritable())
|
||||
throw EofException("Error, write called on non-writable Buffer");
|
||||
|
||||
if (pos + len > m_bytes.size())
|
||||
m_bytes.resize(pos + len);
|
||||
|
||||
memcpy(m_bytes.ptr() + pos, data, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
ExternalBuffer::ExternalBuffer()
|
||||
: m_pos(0), m_bytes(nullptr), m_size(0) {
|
||||
setMode(IOMode::Read);
|
||||
}
|
||||
|
||||
ExternalBuffer::ExternalBuffer(char const* externalData, size_t len) : ExternalBuffer() {
|
||||
reset(externalData, len);
|
||||
}
|
||||
|
||||
StreamOffset ExternalBuffer::pos() {
|
||||
return m_pos;
|
||||
}
|
||||
|
||||
void ExternalBuffer::seek(StreamOffset pos, IOSeek mode) {
|
||||
StreamOffset newPos = m_pos;
|
||||
if (mode == IOSeek::Absolute)
|
||||
newPos = pos;
|
||||
else if (mode == IOSeek::Relative)
|
||||
newPos += pos;
|
||||
else if (mode == IOSeek::End)
|
||||
newPos = m_size - pos;
|
||||
m_pos = newPos;
|
||||
}
|
||||
|
||||
bool ExternalBuffer::atEnd() {
|
||||
return m_pos >= m_size;
|
||||
}
|
||||
|
||||
size_t ExternalBuffer::read(char* data, size_t len) {
|
||||
size_t l = doRead(m_pos, data, len);
|
||||
m_pos += l;
|
||||
return l;
|
||||
}
|
||||
|
||||
size_t ExternalBuffer::write(char const*, size_t) {
|
||||
throw IOException("Error, ExternalBuffer is not writable");
|
||||
}
|
||||
|
||||
size_t ExternalBuffer::readAbsolute(StreamOffset readPosition, char* data, size_t len) {
|
||||
size_t rpos = readPosition;
|
||||
if ((StreamOffset)rpos != readPosition)
|
||||
throw IOException("Error, readPosition out of range");
|
||||
|
||||
return doRead(rpos, data, len);
|
||||
}
|
||||
|
||||
size_t ExternalBuffer::writeAbsolute(StreamOffset, char const*, size_t) {
|
||||
throw IOException("Error, ExternalBuffer is not writable");
|
||||
}
|
||||
|
||||
String ExternalBuffer::deviceName() const {
|
||||
return strf("ExternalBuffer <%s>", this);
|
||||
}
|
||||
|
||||
StreamOffset ExternalBuffer::size() {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
char const* ExternalBuffer::ptr() const {
|
||||
return m_bytes;
|
||||
}
|
||||
|
||||
size_t ExternalBuffer::dataSize() const {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
bool ExternalBuffer::empty() const {
|
||||
return m_size == 0;
|
||||
}
|
||||
|
||||
void ExternalBuffer::reset(char const* externalData, size_t len) {
|
||||
m_pos = 0;
|
||||
m_bytes = externalData;
|
||||
m_size = len;
|
||||
}
|
||||
|
||||
size_t ExternalBuffer::doRead(size_t pos, char* data, size_t len) {
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
if (!isReadable())
|
||||
throw IOException("Error, read called on non-readable Buffer");
|
||||
|
||||
if (pos >= m_size)
|
||||
return 0;
|
||||
|
||||
size_t l = min(m_size - pos, len);
|
||||
memcpy(data, m_bytes + pos, l);
|
||||
return l;
|
||||
}
|
||||
|
||||
}
|
||||
122
source/core/StarBuffer.hpp
Normal file
122
source/core/StarBuffer.hpp
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#ifndef STAR_BUFFER_HPP
|
||||
#define STAR_BUFFER_HPP
|
||||
|
||||
#include "StarIODevice.hpp"
|
||||
#include "StarString.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(Buffer);
|
||||
STAR_CLASS(ExternalBuffer);
|
||||
|
||||
// Wraps a ByteArray to an IODevice
|
||||
class Buffer : public IODevice {
|
||||
public:
|
||||
// Constructs buffer open ReadWrite
|
||||
Buffer();
|
||||
Buffer(size_t initialSize);
|
||||
Buffer(ByteArray b);
|
||||
Buffer(Buffer const& buffer);
|
||||
Buffer(Buffer&& buffer);
|
||||
|
||||
StreamOffset pos() override;
|
||||
void seek(StreamOffset pos, IOSeek mode = IOSeek::Absolute) override;
|
||||
void resize(StreamOffset size) override;
|
||||
bool atEnd() override;
|
||||
|
||||
size_t read(char* data, size_t len) override;
|
||||
size_t write(char const* data, size_t len) override;
|
||||
|
||||
size_t readAbsolute(StreamOffset readPosition, char* data, size_t len) override;
|
||||
size_t writeAbsolute(StreamOffset writePosition, char const* data, size_t len) override;
|
||||
|
||||
void open(IOMode mode) override;
|
||||
|
||||
String deviceName() const override;
|
||||
|
||||
StreamOffset size() override;
|
||||
|
||||
ByteArray& data();
|
||||
ByteArray const& data() const;
|
||||
|
||||
// If this class holds the underlying data, then this method is cheap, and
|
||||
// will move the data out of this class into the returned array, otherwise,
|
||||
// this will incur a copy. Afterwards, this Buffer will be left empty.
|
||||
ByteArray takeData();
|
||||
|
||||
// Returns a pointer to the beginning of the Buffer.
|
||||
char* ptr();
|
||||
char const* ptr() const;
|
||||
|
||||
// Same thing as size(), just size_t type (since this is in-memory)
|
||||
size_t dataSize() const;
|
||||
void reserve(size_t size);
|
||||
|
||||
// Clears buffer, moves position to 0.
|
||||
void clear();
|
||||
bool empty() const;
|
||||
|
||||
// Reset buffer with new contents, moves position to 0.
|
||||
void reset(size_t newSize);
|
||||
void reset(ByteArray b);
|
||||
|
||||
Buffer& operator=(Buffer const& buffer);
|
||||
Buffer& operator=(Buffer&& buffer);
|
||||
|
||||
private:
|
||||
size_t doRead(size_t pos, char* data, size_t len);
|
||||
size_t doWrite(size_t pos, char const* data, size_t len);
|
||||
|
||||
size_t m_pos;
|
||||
ByteArray m_bytes;
|
||||
};
|
||||
|
||||
// Wraps an externally held sequence of bytes to a read-only IODevice
|
||||
class ExternalBuffer : public IODevice {
|
||||
public:
|
||||
// Constructs an empty ReadOnly ExternalBuffer.
|
||||
ExternalBuffer();
|
||||
// Constructs a ReadOnly ExternalBuffer pointing to the given external data, which
|
||||
// must be valid for the lifetime of the ExternalBuffer.
|
||||
ExternalBuffer(char const* externalData, size_t len);
|
||||
|
||||
ExternalBuffer(ExternalBuffer const& buffer) = default;
|
||||
ExternalBuffer& operator=(ExternalBuffer const& buffer) = default;
|
||||
|
||||
StreamOffset pos() override;
|
||||
void seek(StreamOffset pos, IOSeek mode = IOSeek::Absolute) override;
|
||||
bool atEnd() override;
|
||||
|
||||
size_t read(char* data, size_t len) override;
|
||||
size_t write(char const* data, size_t len) override;
|
||||
|
||||
size_t readAbsolute(StreamOffset readPosition, char* data, size_t len) override;
|
||||
size_t writeAbsolute(StreamOffset writePosition, char const* data, size_t len) override;
|
||||
|
||||
String deviceName() const override;
|
||||
|
||||
StreamOffset size() override;
|
||||
|
||||
// Returns a pointer to the beginning of the Buffer.
|
||||
char const* ptr() const;
|
||||
|
||||
// Same thing as size(), just size_t type (since this is in-memory)
|
||||
size_t dataSize() const;
|
||||
|
||||
// Clears buffer, moves position to 0.
|
||||
bool empty() const;
|
||||
|
||||
// Reset buffer with new contents, moves position to 0.
|
||||
void reset(char const* externalData, size_t len);
|
||||
|
||||
private:
|
||||
size_t doRead(size_t pos, char* data, size_t len);
|
||||
|
||||
size_t m_pos;
|
||||
char const* m_bytes;
|
||||
size_t m_size;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
258
source/core/StarByteArray.cpp
Normal file
258
source/core/StarByteArray.cpp
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
#include "StarByteArray.hpp"
|
||||
#include "StarEncode.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ByteArray ByteArray::fromCString(char const* str) {
|
||||
return ByteArray(str, strlen(str));
|
||||
}
|
||||
|
||||
ByteArray ByteArray::fromCStringWithNull(char const* str) {
|
||||
size_t len = strlen(str);
|
||||
ByteArray ba(str, len + 1);
|
||||
ba[len] = 0;
|
||||
return ba;
|
||||
}
|
||||
|
||||
ByteArray ByteArray::withReserve(size_t capacity) {
|
||||
ByteArray bytes;
|
||||
bytes.reserve(capacity);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
ByteArray::ByteArray() {
|
||||
m_data = nullptr;
|
||||
m_capacity = 0;
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
ByteArray::ByteArray(size_t dataSize, char c)
|
||||
: ByteArray() {
|
||||
fill(dataSize, c);
|
||||
}
|
||||
|
||||
ByteArray::ByteArray(const char* data, size_t dataSize)
|
||||
: ByteArray() {
|
||||
append(data, dataSize);
|
||||
}
|
||||
|
||||
ByteArray::ByteArray(ByteArray const& b)
|
||||
: ByteArray() {
|
||||
operator=(b);
|
||||
}
|
||||
|
||||
ByteArray::ByteArray(ByteArray&& b) noexcept
|
||||
: ByteArray() {
|
||||
operator=(move(b));
|
||||
}
|
||||
|
||||
ByteArray::~ByteArray() {
|
||||
reset();
|
||||
}
|
||||
|
||||
ByteArray& ByteArray::operator=(ByteArray const& b) {
|
||||
if (&b != this) {
|
||||
clear();
|
||||
append(b);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ByteArray& ByteArray::operator=(ByteArray&& b) noexcept {
|
||||
if (&b != this) {
|
||||
reset();
|
||||
|
||||
m_data = take(b.m_data);
|
||||
m_capacity = take(b.m_capacity);
|
||||
m_size = take(b.m_size);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ByteArray::reset() {
|
||||
if (m_data) {
|
||||
Star::free(m_data, m_capacity);
|
||||
m_data = nullptr;
|
||||
m_capacity = 0;
|
||||
m_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ByteArray::reserve(size_t newCapacity) {
|
||||
if (newCapacity > m_capacity) {
|
||||
if (!m_data) {
|
||||
auto newMem = (char*)Star::malloc(newCapacity);
|
||||
if (!newMem)
|
||||
throw MemoryException::format("Could not set new ByteArray capacity %s\n", newCapacity);
|
||||
m_data = newMem;
|
||||
m_capacity = newCapacity;
|
||||
} else {
|
||||
newCapacity = max({m_capacity * 2, newCapacity, (size_t)8});
|
||||
auto newMem = (char*)Star::realloc(m_data, newCapacity);
|
||||
if (!newMem)
|
||||
throw MemoryException::format("Could not set new ByteArray capacity %s\n", newCapacity);
|
||||
m_data = newMem;
|
||||
m_capacity = newCapacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ByteArray::resize(size_t size, char f) {
|
||||
if (m_size == size)
|
||||
return;
|
||||
|
||||
size_t oldSize = m_size;
|
||||
resize(size);
|
||||
for (size_t i = oldSize; i < m_size; ++i)
|
||||
(*this)[i] = f;
|
||||
}
|
||||
|
||||
void ByteArray::fill(size_t s, char c) {
|
||||
if (s != NPos)
|
||||
resize(s);
|
||||
|
||||
memset(m_data, c, m_size);
|
||||
}
|
||||
|
||||
void ByteArray::fill(char c) {
|
||||
fill(NPos, c);
|
||||
}
|
||||
|
||||
ByteArray ByteArray::sub(size_t b, size_t s) const {
|
||||
if (b == 0 && s >= m_size) {
|
||||
return ByteArray(*this);
|
||||
} else {
|
||||
return ByteArray(m_data + b, min(m_size, b + s));
|
||||
}
|
||||
}
|
||||
|
||||
ByteArray ByteArray::left(size_t s) const {
|
||||
return sub(0, s);
|
||||
}
|
||||
|
||||
ByteArray ByteArray::right(size_t s) const {
|
||||
if (s > m_size)
|
||||
s = 0;
|
||||
else
|
||||
s = m_size - s;
|
||||
|
||||
return sub(s, m_size);
|
||||
}
|
||||
|
||||
void ByteArray::trimLeft(size_t s) {
|
||||
if (s >= m_size) {
|
||||
clear();
|
||||
} else {
|
||||
std::memmove(m_data, m_data + s, m_size - s);
|
||||
resize(m_size - s);
|
||||
}
|
||||
}
|
||||
|
||||
void ByteArray::trimRight(size_t s) {
|
||||
if (s >= m_size)
|
||||
clear();
|
||||
else
|
||||
resize(m_size - s);
|
||||
}
|
||||
|
||||
size_t ByteArray::diffChar(const ByteArray& b) const {
|
||||
size_t s = min(m_size, b.size());
|
||||
char* ac = m_data;
|
||||
char* bc = b.m_data;
|
||||
size_t i;
|
||||
for (i = 0; i < s; ++i) {
|
||||
if (ac[i] != bc[i])
|
||||
break;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
int ByteArray::compare(const ByteArray& b) const {
|
||||
if (m_size == 0 && b.m_size == 0)
|
||||
return 0;
|
||||
|
||||
if (m_size == 0)
|
||||
return -1;
|
||||
|
||||
if (b.m_size == 0)
|
||||
return 1;
|
||||
|
||||
size_t d = diffChar(b);
|
||||
if (d == m_size) {
|
||||
if (d != b.m_size)
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (d == b.m_size) {
|
||||
if (d != m_size)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned char c1 = (*this)[d];
|
||||
unsigned char c2 = b[d];
|
||||
|
||||
if (c1 < c2) {
|
||||
return -1;
|
||||
} else if (c1 > c2) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ByteArray ByteArray::andWith(ByteArray const& rhs, bool extend) {
|
||||
return combineWith([](char a, char b) { return a & b; }, rhs, extend);
|
||||
}
|
||||
|
||||
ByteArray ByteArray::orWith(ByteArray const& rhs, bool extend) {
|
||||
return combineWith([](char a, char b) { return a | b; }, rhs, extend);
|
||||
}
|
||||
|
||||
ByteArray ByteArray::xorWith(ByteArray const& rhs, bool extend) {
|
||||
return combineWith([](char a, char b) { return a ^ b; }, rhs, extend);
|
||||
}
|
||||
|
||||
void ByteArray::insert(size_t pos, char byte) {
|
||||
starAssert(pos <= m_size);
|
||||
resize(m_size + 1);
|
||||
for (size_t i = m_size - 1; i > pos; --i)
|
||||
m_data[i] = m_data[i - 1];
|
||||
m_data[pos] = byte;
|
||||
}
|
||||
|
||||
ByteArray::iterator ByteArray::insert(const_iterator pos, char byte) {
|
||||
size_t d = pos - begin();
|
||||
insert(d, byte);
|
||||
return begin() + d + 1;
|
||||
}
|
||||
|
||||
void ByteArray::push_back(char byte) {
|
||||
resize(m_size + 1);
|
||||
m_data[m_size - 1] = byte;
|
||||
}
|
||||
|
||||
bool ByteArray::operator<(const ByteArray& b) const {
|
||||
return compare(b) < 0;
|
||||
}
|
||||
|
||||
bool ByteArray::operator==(const ByteArray& b) const {
|
||||
return compare(b) == 0;
|
||||
}
|
||||
|
||||
bool ByteArray::operator!=(const ByteArray& b) const {
|
||||
return compare(b) != 0;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const ByteArray& b) {
|
||||
os << "0x" << hexEncode(b);
|
||||
return os;
|
||||
}
|
||||
|
||||
}
|
||||
259
source/core/StarByteArray.hpp
Normal file
259
source/core/StarByteArray.hpp
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
#ifndef STAR_BYTE_ARRAY_H
|
||||
#define STAR_BYTE_ARRAY_H
|
||||
|
||||
#include "StarHash.hpp"
|
||||
#include "StarException.hpp"
|
||||
#include "StarFormat.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(ByteArray);
|
||||
|
||||
// Class to hold an array of bytes. Contains an internal buffer that may be
|
||||
// larger than what is reported by size(), to avoid repeated allocations when a
|
||||
// ByteArray grows.
|
||||
class ByteArray {
|
||||
public:
|
||||
typedef char value_type;
|
||||
typedef char* iterator;
|
||||
typedef char const* const_iterator;
|
||||
|
||||
// Constructs a byte array from a given c string WITHOUT including the
|
||||
// trailing '\0'
|
||||
static ByteArray fromCString(char const* str);
|
||||
// Same, but includes the trailing '\0'
|
||||
static ByteArray fromCStringWithNull(char const* str);
|
||||
static ByteArray withReserve(size_t capacity);
|
||||
|
||||
ByteArray();
|
||||
ByteArray(size_t dataSize, char c);
|
||||
ByteArray(char const* data, size_t dataSize);
|
||||
ByteArray(ByteArray const& b);
|
||||
ByteArray(ByteArray&& b) noexcept;
|
||||
~ByteArray();
|
||||
|
||||
ByteArray& operator=(ByteArray const& b);
|
||||
ByteArray& operator=(ByteArray&& b) noexcept;
|
||||
|
||||
char const* ptr() const;
|
||||
char* ptr();
|
||||
|
||||
size_t size() const;
|
||||
// Maximum size before realloc
|
||||
size_t capacity() const;
|
||||
// Is zero size
|
||||
bool empty() const;
|
||||
|
||||
// Sets size to 0.
|
||||
void clear();
|
||||
// Clears and resets buffer to empty.
|
||||
void reset();
|
||||
|
||||
void reserve(size_t capacity);
|
||||
|
||||
void resize(size_t size);
|
||||
// resize, filling new space with given byte if it exists.
|
||||
void resize(size_t size, char f);
|
||||
|
||||
// fill array with byte.
|
||||
void fill(char c);
|
||||
// fill array and resize to new size.
|
||||
void fill(size_t size, char c);
|
||||
|
||||
void append(ByteArray const& b);
|
||||
void append(char const* data, size_t len);
|
||||
void appendByte(char b);
|
||||
|
||||
void copyTo(char* data, size_t len) const;
|
||||
void copyTo(char* data) const;
|
||||
|
||||
// Copy from ByteArray starting at pos, to data, with size len.
|
||||
void copyTo(char* data, size_t pos, size_t len) const;
|
||||
// Copy from data pointer to ByteArray at pos with size len.
|
||||
// Resizes if needed.
|
||||
void writeFrom(char const* data, size_t pos, size_t len);
|
||||
|
||||
ByteArray sub(size_t b, size_t s) const;
|
||||
ByteArray left(size_t s) const;
|
||||
ByteArray right(size_t s) const;
|
||||
|
||||
void trimLeft(size_t s);
|
||||
void trimRight(size_t s);
|
||||
|
||||
// returns location of first character that is different than the given
|
||||
// ByteArray.
|
||||
size_t diffChar(ByteArray const& b) const;
|
||||
// returns -1 if this < b, 0 if this == b, 1 if this > b
|
||||
int compare(ByteArray const& b) const;
|
||||
|
||||
template <typename Combiner>
|
||||
ByteArray combineWith(Combiner&& combine, ByteArray const& rhs, bool extend = false);
|
||||
|
||||
ByteArray andWith(ByteArray const& rhs, bool extend = false);
|
||||
ByteArray orWith(ByteArray const& rhs, bool extend = false);
|
||||
ByteArray xorWith(ByteArray const& rhs, bool extend = false);
|
||||
|
||||
iterator begin();
|
||||
iterator end();
|
||||
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
|
||||
void insert(size_t pos, char byte);
|
||||
iterator insert(const_iterator pos, char byte);
|
||||
void push_back(char byte);
|
||||
|
||||
char& operator[](size_t i);
|
||||
char operator[](size_t i) const;
|
||||
char at(size_t i) const;
|
||||
|
||||
bool operator<(ByteArray const& b) const;
|
||||
bool operator==(ByteArray const& b) const;
|
||||
bool operator!=(ByteArray const& b) const;
|
||||
|
||||
private:
|
||||
char* m_data;
|
||||
size_t m_capacity;
|
||||
size_t m_size;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<ByteArray> {
|
||||
size_t operator()(ByteArray const& b) const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, ByteArray const& b);
|
||||
|
||||
inline void ByteArray::clear() {
|
||||
resize(0);
|
||||
}
|
||||
|
||||
inline void ByteArray::resize(size_t size) {
|
||||
reserve(size);
|
||||
m_size = size;
|
||||
}
|
||||
|
||||
inline void ByteArray::append(ByteArray const& b) {
|
||||
append(b.ptr(), b.size());
|
||||
}
|
||||
|
||||
inline void ByteArray::append(const char* data, size_t len) {
|
||||
resize(m_size + len);
|
||||
std::memcpy(m_data + m_size - len, data, len);
|
||||
}
|
||||
|
||||
inline void ByteArray::appendByte(char b) {
|
||||
resize(m_size + 1);
|
||||
m_data[m_size - 1] = b;
|
||||
}
|
||||
|
||||
inline bool ByteArray::empty() const {
|
||||
return m_size == 0;
|
||||
}
|
||||
|
||||
inline char const* ByteArray::ptr() const {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
inline char* ByteArray::ptr() {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
inline size_t ByteArray::size() const {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
inline size_t ByteArray::capacity() const {
|
||||
return m_capacity;
|
||||
}
|
||||
|
||||
inline void ByteArray::copyTo(char* data, size_t len) const {
|
||||
len = min(m_size, len);
|
||||
std::memcpy(data, m_data, len);
|
||||
}
|
||||
|
||||
inline void ByteArray::copyTo(char* data) const {
|
||||
copyTo(data, m_size);
|
||||
}
|
||||
|
||||
inline void ByteArray::copyTo(char* data, size_t pos, size_t len) const {
|
||||
if (len == 0 || pos >= m_size)
|
||||
return;
|
||||
|
||||
len = min(m_size - pos, len);
|
||||
std::memcpy(data, m_data + pos, len);
|
||||
}
|
||||
|
||||
inline void ByteArray::writeFrom(const char* data, size_t pos, size_t len) {
|
||||
if (pos + len > m_size)
|
||||
resize(pos + len);
|
||||
|
||||
std::memcpy(m_data + pos, data, len);
|
||||
}
|
||||
|
||||
template <typename Combiner>
|
||||
ByteArray ByteArray::combineWith(Combiner&& combine, ByteArray const& rhs, bool extend) {
|
||||
ByteArray const* smallerArray = &rhs;
|
||||
ByteArray const* largerArray = this;
|
||||
|
||||
if (m_size < rhs.size())
|
||||
swap(smallerArray, largerArray);
|
||||
|
||||
ByteArray res;
|
||||
res.resize(smallerArray->size());
|
||||
|
||||
for (size_t i = 0; i < smallerArray->size(); ++i)
|
||||
res[i] = combine((*smallerArray)[i], (*largerArray)[i]);
|
||||
|
||||
if (extend) {
|
||||
res.resize(largerArray->size());
|
||||
for (size_t i = smallerArray->size(); i < largerArray->size(); ++i)
|
||||
res[i] = (*largerArray)[i];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
inline ByteArray::iterator ByteArray::begin() {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
inline ByteArray::iterator ByteArray::end() {
|
||||
return m_data + m_size;
|
||||
}
|
||||
|
||||
inline ByteArray::const_iterator ByteArray::begin() const {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
inline ByteArray::const_iterator ByteArray::end() const {
|
||||
return m_data + m_size;
|
||||
}
|
||||
|
||||
inline char& ByteArray::operator[](size_t i) {
|
||||
starAssert(i < m_size);
|
||||
return m_data[i];
|
||||
}
|
||||
|
||||
inline char ByteArray::operator[](size_t i) const {
|
||||
starAssert(i < m_size);
|
||||
return m_data[i];
|
||||
}
|
||||
|
||||
inline char ByteArray::at(size_t i) const {
|
||||
if (i >= m_size)
|
||||
throw OutOfRangeException(strf("Out of range in ByteArray::at(%s)", i));
|
||||
|
||||
return m_data[i];
|
||||
}
|
||||
|
||||
inline size_t hash<ByteArray>::operator()(ByteArray const& b) const {
|
||||
PLHasher hash;
|
||||
for (size_t i = 0; i < b.size(); ++i)
|
||||
hash.put(b[i]);
|
||||
return hash.hash();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
109
source/core/StarBytes.hpp
Normal file
109
source/core/StarBytes.hpp
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#ifndef STAR_BYTES_HPP
|
||||
#define STAR_BYTES_HPP
|
||||
|
||||
#include "StarMemory.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
enum class ByteOrder {
|
||||
BigEndian,
|
||||
LittleEndian,
|
||||
NoConversion
|
||||
};
|
||||
|
||||
ByteOrder platformByteOrder();
|
||||
|
||||
void swapByteOrder(void* ptr, size_t len);
|
||||
void swapByteOrder(void* dest, void const* src, size_t len);
|
||||
|
||||
void toByteOrder(ByteOrder order, void* ptr, size_t len);
|
||||
void toByteOrder(ByteOrder order, void* dest, void const* src, size_t len);
|
||||
void fromByteOrder(ByteOrder order, void* ptr, size_t len);
|
||||
void fromByteOrder(ByteOrder order, void* dest, void const* src, size_t len);
|
||||
|
||||
template <typename T>
|
||||
T toByteOrder(ByteOrder order, T const& t) {
|
||||
T ret;
|
||||
toByteOrder(order, &ret, &t, sizeof(t));
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T fromByteOrder(ByteOrder order, T const& t) {
|
||||
T ret;
|
||||
fromByteOrder(order, &ret, &t, sizeof(t));
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T toBigEndian(T const& t) {
|
||||
return toByteOrder(ByteOrder::BigEndian, t);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T fromBigEndian(T const& t) {
|
||||
return fromByteOrder(ByteOrder::BigEndian, t);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T toLittleEndian(T const& t) {
|
||||
return toByteOrder(ByteOrder::LittleEndian, t);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T fromLittleEndian(T const& t) {
|
||||
return fromByteOrder(ByteOrder::LittleEndian, t);
|
||||
}
|
||||
|
||||
inline ByteOrder platformByteOrder() {
|
||||
#if STAR_LITTLE_ENDIAN
|
||||
return ByteOrder::LittleEndian;
|
||||
#else
|
||||
return ByteOrder::BigEndian;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void swapByteOrder(void* ptr, size_t len) {
|
||||
uint8_t* data = static_cast<uint8_t*>(ptr);
|
||||
uint8_t spare;
|
||||
for (size_t i = 0; i < len / 2; ++i) {
|
||||
spare = data[len - 1 - i];
|
||||
data[len - 1 - i] = data[i];
|
||||
data[i] = spare;
|
||||
}
|
||||
}
|
||||
|
||||
inline void swapByteOrder(void* dest, const void* src, size_t len) {
|
||||
const uint8_t* srcdata = static_cast<const uint8_t*>(src);
|
||||
uint8_t* destdata = static_cast<uint8_t*>(dest);
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
destdata[len - 1 - i] = srcdata[i];
|
||||
}
|
||||
|
||||
inline void toByteOrder(ByteOrder order, void* ptr, size_t len) {
|
||||
if (order != ByteOrder::NoConversion && platformByteOrder() != order)
|
||||
swapByteOrder(ptr, len);
|
||||
}
|
||||
|
||||
inline void toByteOrder(ByteOrder order, void* dest, void const* src, size_t len) {
|
||||
if (order != ByteOrder::NoConversion && platformByteOrder() != order)
|
||||
swapByteOrder(dest, src, len);
|
||||
else
|
||||
memcpy(dest, src, len);
|
||||
}
|
||||
|
||||
inline void fromByteOrder(ByteOrder order, void* ptr, size_t len) {
|
||||
if (order != ByteOrder::NoConversion && platformByteOrder() != order)
|
||||
swapByteOrder(ptr, len);
|
||||
}
|
||||
|
||||
inline void fromByteOrder(ByteOrder order, void* dest, void const* src, size_t len) {
|
||||
if (order != ByteOrder::NoConversion && platformByteOrder() != order)
|
||||
swapByteOrder(dest, src, len);
|
||||
else
|
||||
memcpy(dest, src, len);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue