This commit is contained in:
Aria 2025-03-21 22:23:30 +11:00
commit 9c94d113d3
Signed by untrusted user who does not match committer: aria
GPG key ID: 19AB7AA462B8AB3B
10260 changed files with 1237388 additions and 0 deletions

533
source/CMakeLists.txt Normal file
View 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 ()

View 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})

View 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() {}
}

View 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

View 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

View 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());
}
}

View 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

View 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"}
};
}

View 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

View 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

View 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;
}
}

View 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
}
}

View 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

View 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
}
}

View 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

View 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;
}
}

View 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

View 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);
}
}
}

View 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

View 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);
}
}

View 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

View 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

View file

@ -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;
}
}

View file

@ -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

View 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

View 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

View 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

View 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

View 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(&params);
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, &params, &((*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

View 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

View 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"

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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)

View 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;
}
}
}

View 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

View 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

File diff suppressed because it is too large Load diff

380
source/base/StarAssets.hpp Normal file
View 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

View 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

View 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

View 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));
}
}

View 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

View 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

View 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);
}
}

View 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

View 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));
}
}
}
}

View 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
View 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
View 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

View 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;
}
}

View 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

View 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}";
}

View 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

View 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));
}
}

View 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

View 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);
}
}

View 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

View 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})

View 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);

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View 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
View 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
View 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

View 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
View 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

View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

View 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
View 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

View 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
View 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
View 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

View 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;
}
}

View 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
View 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