diff --git a/.gitignore b/.gitignore index b01c266e47..5b31358da0 100644 --- a/.gitignore +++ b/.gitignore @@ -162,11 +162,11 @@ raylib*/ box2d*/ # Temp Impulse Wars files -pufferlib/ocean/impulse_wars/*-debug/ -pufferlib/ocean/impulse_wars/*-release/ -pufferlib/ocean/impulse_wars/debug-*/ -pufferlib/ocean/impulse_wars/release-*/ -pufferlib/ocean/impulse_wars/benchmark/ +ocean/impulse_wars/*-debug/ +ocean/impulse_wars/*-release/ +ocean/impulse_wars/debug-*/ +ocean/impulse_wars/release-*/ +ocean/impulse_wars/benchmark/ # Data resources/drive/data/* diff --git a/build.sh b/build.sh index 492c033686..46beafaceb 100755 --- a/build.sh +++ b/build.sh @@ -50,20 +50,23 @@ if [ "$ENV" = "all" ]; then exit 0 fi +STANDALONE_LDFLAGS=(-fuse-ld=lld) +SHARED_LDFLAGS=(-fuse-ld=lld) + # Linux/mac PLATFORM="$(uname -s)" if [ "$PLATFORM" = "Linux" ]; then RAYLIB_NAME='raylib-5.5_linux_amd64' OMP_LIB=-lomp5 SANITIZE_FLAGS=(-fsanitize=address,undefined,bounds,pointer-overflow,leak -fno-omit-frame-pointer) - STANDALONE_LDFLAGS=(-lGL) - SHARED_LDFLAGS=(-Bsymbolic-functions) + STANDALONE_LDFLAGS+=(-lGL) + SHARED_LDFLAGS+=(-Bsymbolic-functions) else RAYLIB_NAME='raylib-5.5_macos' OMP_LIB=-lomp SANITIZE_FLAGS=() - STANDALONE_LDFLAGS=(-framework Cocoa -framework IOKit -framework CoreVideo -framework OpenGL) - SHARED_LDFLAGS=(-framework Cocoa -framework OpenGL -framework IOKit -undefined dynamic_lookup) + STANDALONE_LDFLAGS+=(-framework Cocoa -framework IOKit -framework CoreVideo -framework OpenGL) + SHARED_LDFLAGS+=(-framework Cocoa -framework OpenGL -framework IOKit -undefined dynamic_lookup) fi CLANG_WARN=( @@ -75,6 +78,7 @@ CLANG_WARN=( -Wno-incompatible-pointer-types-discards-qualifiers -Wno-error=array-parameter ) +CLANG_OPT=() download() { local name=$1 url=$2 @@ -108,14 +112,24 @@ elif [ "$ENV" = "trailer" ]; then OUTPUT_NAME="trailer/trailer" elif [ "$ENV" = "impulse_wars" ]; then SRC_DIR="ocean/$ENV" - if [ "$MODE" = "web" ]; then BOX2D_NAME='box2d-web' - elif [ "$PLATFORM" = "Linux" ]; then BOX2D_NAME='box2d-linux-amd64' - else BOX2D_NAME='box2d-macos-arm64' + if [ "$MODE" = "web" ]; then + BOX2D_NAME='box2d-web' + elif [ "$PLATFORM" = "Linux" ]; then + BOX2D_NAME='box2d-linux-amd64' + else + BOX2D_NAME='box2d-macos-arm64' fi + BOX2D_URL="https://github.com/capnspacehook/box2d/releases/latest/download" download "$BOX2D_NAME" "$BOX2D_URL/$BOX2D_NAME.tar.gz" INCLUDES+=(-I./$BOX2D_NAME/include -I./$BOX2D_NAME/src) - LINK_ARCHIVES+=("./$BOX2D_NAME/libbox2d.a") + + if [ -z "$DEBUG" ]; then + CLANG_OPT+=(-flto -fno-math-errno -march=native) + LINK_ARCHIVES+=("./$BOX2D_NAME/libbox2d.a") + else + LINK_ARCHIVES+=("./$BOX2D_NAME/libbox2dd.a") + fi elif [ -d "ocean/$ENV" ]; then SRC_DIR="ocean/$ENV" else @@ -126,13 +140,13 @@ OUTPUT_NAME=${OUTPUT_NAME:-$ENV} # Standalone environment build if [ -n "$DEBUG" ] || [ "$MODE" = "local" ]; then - CLANG_OPT=(-g -O0 "${CLANG_WARN[@]}" "${SANITIZE_FLAGS[@]}") + CLANG_OPT+=(-g -O0 "${CLANG_WARN[@]}" "${SANITIZE_FLAGS[@]}") NVCC_OPT="-O0 -g" LINK_OPT="-g" else - CLANG_OPT=(-O2 -DNDEBUG "${CLANG_WARN[@]}") - NVCC_OPT="-O2 --threads 0" - LINK_OPT="-O2" + CLANG_OPT+=(-O3 -DNDEBUG "${CLANG_WARN[@]}") + NVCC_OPT="-O3 --threads 0" + LINK_OPT="-O3" fi if [ "$MODE" = "local" ] || [ "$MODE" = "fast" ]; then FLAGS=( @@ -242,6 +256,7 @@ echo "Compiling static library for $ENV..." ${CC:-clang} -c "${CLANG_OPT[@]}" $EXTRA_CFLAGS \ -I. -Isrc -I$SRC_DIR -Ivendor \ -I./$RAYLIB_NAME/include -I$CUDA_HOME/include \ + "${INCLUDES[@]}" \ -DPLATFORM_DESKTOP \ -fno-semantic-interposition -fvisibility=hidden \ -fPIC -fopenmp \ @@ -273,7 +288,7 @@ if [ -z "$MODE" ]; then LINK_CMD=( ${CXX:-g++} -shared -fPIC -fopenmp - build/bindings.o "$STATIC_LIB" "$RAYLIB_A" + build/bindings.o "$STATIC_LIB" "${LINK_ARCHIVES[@]}" -L$CUDA_HOME/lib64 $CUDNN_LFLAG $NCCL_LFLAG "${WHEEL_RPATH_FLAGS[@]}" -lcudart -lnccl -lnvidia-ml -lcublas -lcusolver -lcurand -lcudnn @@ -298,7 +313,7 @@ elif [ "$MODE" = "cpu" ]; then src/bindings_cpu.cpp -o build/bindings_cpu.o LINK_CMD=( ${CXX:-g++} -shared -fPIC -fopenmp - build/bindings_cpu.o "$STATIC_LIB" "$RAYLIB_A" + build/bindings_cpu.o "$STATIC_LIB" "${LINK_ARCHIVES[@]}" -lm -lpthread $OMP_LIB $LINK_OPT "${SHARED_LDFLAGS[@]}" -o "$OUTPUT" @@ -317,7 +332,7 @@ elif [ "$MODE" = "profile" ]; then $PRECISION \ -Xcompiler=-fopenmp \ tests/profile_kernels.cu vendor/ini.c \ - "$STATIC_LIB" "$RAYLIB_A" \ + "$STATIC_LIB" "${LINK_ARCHIVES[@]}" \ -lnccl -lnvidia-ml -lcublas -lcurand -lcudnn \ -lGL -lm -lpthread $OMP_LIB \ -o profile diff --git a/config/impulse_wars.ini b/config/impulse_wars.ini index 3e7c7f7bbb..a252984ddc 100644 --- a/config/impulse_wars.ini +++ b/config/impulse_wars.ini @@ -1,143 +1,25 @@ [base] env_name = impulse_wars -max_suggestion_cost = 10_800 - -[policy] -hidden_size = 512 -cnn_channels = 64 - -# These must match what's set in env below -continuous = False -num_drones = 2 -is_training = True - -[vec] -num_envs = 4 -#num_workers = 4 -#batch_size = 4 - [env] -num_envs = 1024 num_drones = 2 num_agents = 1 enable_teams = False -sitting_duck = False +sitting_duck = True continuous = False is_training = True +reward_win = 2.0 +reward_self_kill = -1.0 +reward_enemy_death = 1.0 +reward_enemy_kill = 1.0 +reward_death = 0.0 +reward_energy_emptied = -0.75 +reward_weapon_pickup = 0.5 +reward_shield_break = 0.5 +reward_shot_hit_coef = 0.005 +reward_explosion_hit_coef = 0.005 + [train] total_timesteps = 1_000_000_000 checkpoint_interval = 250 - -learning_rate = 0.005 - -compile = False -compile_mode = reduce-overhead -compile_fullgraph = False - - -[sweep] -downsample = 10 -max_cost = 900 - -[sweep.env.num_envs] -distribution = uniform_pow2 -min = 1 -max = 1024 -mean = 128 -scale = auto - -# reward parameters -[sweep.env.reward_win] -distribution = uniform -min = 0.0 -mean = 2.0 -max = 5.0 -scale = auto - -[sweep.env.reward_self_kill] -distribution = uniform -min = -3.0 -mean = -1.0 -max = 0.0 -scale = auto - -[sweep.env.reward_enemy_death] -distribution = uniform -min = 0.0 -mean = 1.0 -max = 3.0 -scale = auto - -[sweep.env.reward_kill] -distribution = uniform -min = 0.0 -mean = 1.0 -max = 3.0 -scale = auto - -[sweep.env.reward_death] -distribution = uniform -min = -1.0 -mean = -0.25 -max = 0.0 -scale = auto - -[sweep.env.reward_energy_emptied] -distribution = uniform -min = -2.0 -mean = -0.75 -max = 0.0 -scale = auto - -[sweep.env.reward_weapon_pickup] -distribution = uniform -min = 0.0 -mean = 0.5 -max = 3.0 -scale = auto - -[sweep.env.reward_shield_break] -distribution = uniform -min = 0.0 -mean = 0.5 -max = 3.0 -scale = auto - -[sweep.env.reward_shot_hit_coef] -distribution = log_normal -min = 0.0005 -mean = 0.005 -max = 0.05 -scale = auto - -[sweep.env.reward_explosion_hit_coef] -distribution = log_normal -min = 0.0005 -mean = 0.005 -max = 0.05 -scale = auto - -# hyperparameters -[sweep.train.total_timesteps] -distribution = log_normal -min = 250_000_000 -max = 1_500_000_000 -mean = 500_000_000 -scale = time - -[sweep.train.batch_size] -distribution = uniform_pow2 -min = 65_536 -max = 1_048_576 -mean = 262_144 -scale = auto - -[sweep.train.horizon] -distribution = uniform_pow2 -min = 64 -max = 256 -mean = 128 -scale = auto - diff --git a/ocean/impulse_wars/.clang-format b/ocean/impulse_wars/.clang-format new file mode 100644 index 0000000000..98f71421ec --- /dev/null +++ b/ocean/impulse_wars/.clang-format @@ -0,0 +1,228 @@ +--- +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: BlockIndent +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: false +BinPackParameters: false +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Custom +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertBraces: true +InsertNewlineAtEOF: false +InsertTrailingCommas: Wrapped +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +KeepEmptyLinesAtEOF: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: Never +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +... + diff --git a/ocean/impulse_wars/CMakeLists.txt b/ocean/impulse_wars/CMakeLists.txt new file mode 100644 index 0000000000..55f49a73f6 --- /dev/null +++ b/ocean/impulse_wars/CMakeLists.txt @@ -0,0 +1,138 @@ +# 3.22 was released on Nov 2021, should be widely available +cmake_minimum_required(VERSION 3.22) +include(FetchContent) + +project( + impulse-wars + DESCRIPTION "Impulse Wars" + LANGUAGES C +) + +message(INFO " C Compiler: ${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_VERSION} ${CMAKE_C_COMPILER_ID}") + +# use ccache if available to speed up subsequent builds +find_program(CCACHE_FOUND "ccache") +if(CCACHE_FOUND) + set(CMAKE_C_COMPILER_LAUNCHER "ccache") +endif() + +# enable some C23 features, the c2x standard is a WIP standard supported +# by gcc since 9 (May 2019) and clang since 9 (Sep 2019) +set(CMAKE_C_FLAGS_INIT " -std=c2x") + +# force position independent code everywhere to prevent some rare +# linker errors depending on what compiler is used +add_compile_options("-fPIC") + +if(CMAKE_BUILD_TYPE MATCHES Debug) + # leak detection doesn't work correctly when the code is called by + # Python, so disable it + if(DEFINED BUILD_PYTHON_MODULE) + add_compile_options("-fno-omit-frame-pointer" "-fsanitize=address,undefined,bounds,pointer-overflow") + add_link_options("-shared-libasan" "-fno-omit-frame-pointer" "-fsanitize=address,undefined,bounds,pointer-overflow") + else() + add_compile_options("-fno-omit-frame-pointer" "-fsanitize=address,undefined,bounds,pointer-overflow,leak") + add_link_options("-fno-omit-frame-pointer" "-fsanitize=address,undefined,bounds,pointer-overflow,leak") + endif() + + # mold is an extremely fast linker, use it if available + # only use mold in debug mode, link time optimization currently doesn't + # work with mold and provides large speedups + find_program(MOLD_FOUND "mold") + if(MOLD_FOUND) + add_link_options("-fuse-ld=mold") + endif() +else() + add_compile_options("-flto" "-fno-math-errno") + if (NOT DEFINED EMSCRIPTEN) + # emscripten doesn't support -march=native, it doesn't make sense + # for WASM anyway + add_compile_options("-march=native") + else() + # tell emscripten to generate an HTML file that can be used to + # test the WASM, and ensure necessary code is transformed to be + # async friendly; it allows the game to be run much more smoothly + set(CMAKE_EXECUTABLE_SUFFIX ".html") + add_link_options("-sASYNCIFY") + endif() + # ensure the linker used is from the same compiler toolchain, or else + # link time optimization will probably fail; if we're using + # emscripten it will use it's own linker + if(CMAKE_C_COMPILER_ID MATCHES "Clang" AND NOT DEFINED EMSCRIPTEN) + add_link_options("-fuse-ld=lld") + endif() + + # add_compile_options("-pg") + # add_link_options("-pg") +endif() + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) +set(FETCHCONTENT_QUIET FALSE) + +# fetch and configure dependencies +FetchContent_Declare( + raylib + URL https://github.com/raysan5/raylib/archive/c1ab645ca298a2801097931d1079b10ff7eb9df8.zip # 5.5 +) +set(BUILD_SHARED_LIBS OFF CACHE BOOL "Statically link raylib" FORCE) +set(WITH_PIC "Compile static library as position-independent code" ON) +set(CUSTOMIZE_BUILD ON CACHE BOOL "Customize raylib build settings" FORCE) +set(USE_AUDIO OFF CACHE BOOL "Don't build unused audio module" FORCE) +FetchContent_MakeAvailable(raylib) + +# if box2d is fetched first installing built python module will fail +# for reasons unbeknownst to mere mortals +# maybe due to install prefix schenanigans? +FetchContent_Declare( + box2d + URL https://github.com/capnspacehook/box2d/archive/df25d747be0ab2fd9425eece022d2ec897c2028d.zip +) +set(BOX2D_ENABLE_SIMD ON CACHE BOOL "Enable SIMD math (faster)" FORCE) +set(BOX2D_AVX2 ON CACHE BOOL "Enable AVX2 (faster)" FORCE) +add_compile_definitions(B2_MAX_WORLDS=65534) +FetchContent_MakeAvailable(box2d) +# this is set to off by box2d to enable cross platform determinism, but +# I don't care about that and want the small speedup instead +target_compile_options(box2d PRIVATE "-ffp-contract=fast") + +function(configure_target target_name) + target_include_directories( + ${target_name} PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/../../vendor" + ) + + # Mark box2d as a system include directory to suppress warnings from it + target_include_directories(${target_name} SYSTEM PRIVATE "${box2d_SOURCE_DIR}/src") + + target_link_libraries(${target_name} PRIVATE raylib box2d) + + target_compile_options(${target_name} PRIVATE + "-Werror" "-Wall" "-Wextra" "-Wpedantic" + "-Wno-implicit-fallthrough" "-Wno-variadic-macros" "-Wno-strict-prototypes" "-Wno-gnu-statement-expression" + ) +endfunction() + +if(DEFINED BUILD_PYTHON_MODULE) + find_package( + Python + COMPONENTS Interpreter Development.Module NumPy + REQUIRED + ) + + python_add_library(binding MODULE binding.c WITH_SOABI) + + target_include_directories(binding PRIVATE + ${Python_NumPy_INCLUDE_DIRS} + ) + + configure_target(binding) + + install(TARGETS binding DESTINATION .) +elseif(DEFINED BUILD_DEMO) + add_executable(demo "${CMAKE_CURRENT_SOURCE_DIR}/impulse_wars.c") + configure_target(demo) +elseif(DEFINED BUILD_BENCHMARK) + add_executable(benchmark "${CMAKE_CURRENT_SOURCE_DIR}/benchmark.c") + configure_target(benchmark) +endif() diff --git a/ocean/impulse_wars/Makefile b/ocean/impulse_wars/Makefile new file mode 100644 index 0000000000..ce593669da --- /dev/null +++ b/ocean/impulse_wars/Makefile @@ -0,0 +1,61 @@ +RELEASE_PYTHON_MODULE_DIR := python-module-release +DEBUG_PYTHON_MODULE_DIR := python-module-debug +DEBUG_DIR := debug-demo +RELEASE_DIR := release-demo +RELEASE_WEB_DIR := release-demo-web +BENCHMARK_DIR := benchmark + +DEBUG_BUILD_TYPE := Debug +RELEASE_BUILD_TYPE := Release + +# install build dependencies if this is a fresh build, Python won't +# install build dependencies when --no-build-isolation is passed +# build with no isolation so that builds can be cached and/or incremental + +# build Python module in release mode +.PHONY: python-module-release +python-module-release: + @test -d $(RELEASE_PYTHON_MODULE_DIR) || pip install scikit-build-core autopxd2 cython + @pip install --no-build-isolation --config-settings=editable.rebuild=true -Cbuild-dir=$(RELEASE_PYTHON_MODULE_DIR) -v . + +# build Python module in debug mode +.PHONY: python-module-debug +python-module-debug: + @test -d $(DEBUG_PYTHON_MODULE_DIR) || pip install scikit-build-core autopxd2 cython + @pip install --no-build-isolation --config-settings=editable.rebuild=true --config-settings=cmake.build-type="Debug" -Cbuild-dir=$(DEBUG_PYTHON_MODULE_DIR) -v . + +# build C demo in debug mode +.PHONY: debug-demo +debug-demo: + @mkdir -p $(DEBUG_DIR) + @cd $(DEBUG_DIR) && \ + cmake -GNinja -DCMAKE_BUILD_TYPE=$(DEBUG_BUILD_TYPE) -DBUILD_DEMO=true -DCMAKE_C_COMPILER=clang-20 .. && \ + cmake --build . + +# build C demo in release mode +.PHONY: release-demo +release-demo: + @mkdir -p $(RELEASE_DIR) + @cd $(RELEASE_DIR) && \ + cmake -GNinja -DCMAKE_BUILD_TYPE=$(RELEASE_BUILD_TYPE) -DBUILD_DEMO=true -DCMAKE_C_COMPILER=clang-20 .. && \ + cmake --build . + +# build C demo in release mode for web +.PHONY: release-demo-web +release-demo-web: + @mkdir -p $(RELEASE_WEB_DIR) + @cd $(RELEASE_WEB_DIR) && \ + emcmake cmake -GNinja -DCMAKE_BUILD_TYPE=$(RELEASE_BUILD_TYPE) -DPLATFORM=Web -DBUILD_DEMO=true .. && \ + cmake --build . + +# build C benchmark +.PHONY: benchmark +benchmark: + @mkdir -p $(BENCHMARK_DIR) + @cd $(BENCHMARK_DIR) && \ + cmake -GNinja -DCMAKE_BUILD_TYPE=$(RELEASE_BUILD_TYPE) -DBUILD_BENCHMARK=true -DCMAKE_C_COMPILER=clang-20 .. && \ + cmake --build . + +.PHONY: clean +clean: + @rm -rf build $(RELEASE_PYTHON_MODULE_DIR) $(DEBUG_PYTHON_MODULE_DIR) $(DEBUG_DIR) $(RELEASE_DIR) $(RELEASE_WEB_DIR) $(BENCHMARK_DIR) diff --git a/ocean/impulse_wars/README.md b/ocean/impulse_wars/README.md new file mode 100644 index 0000000000..accca74381 --- /dev/null +++ b/ocean/impulse_wars/README.md @@ -0,0 +1,12 @@ +# Impulse Wars + +To build, you need to have the following: +- cmake +- make +- ninja +- raylib required deps installed: https://github.com/raysan5/raylib/wiki/Working-on-GNU-Linux + +Run `make && cp python-module-release/binding.*.so .` to build the python module in release mode. +`puffer_impulse_wars` env should now be trainable. + +When watching evaluations, you need to set all instances of `is_training = False` and `render = True` in the config file. diff --git a/ocean/impulse_wars/benchmark.c b/ocean/impulse_wars/benchmark.c index 3071bf91b3..c11c1e2050 100644 --- a/ocean/impulse_wars/benchmark.c +++ b/ocean/impulse_wars/benchmark.c @@ -1,16 +1,16 @@ #include "env.h" void randActions(iwEnv *e) { - // e->lastRandState = e->randState; + // e->lastRandState = e->rng; uint8_t actionOffset = 0; for (uint8_t i = 0; i < e->numDrones; i++) { - e->actions[actionOffset + 0] = randFloat(&e->randState, -1.0f, 1.0f); - e->actions[actionOffset + 1] = randFloat(&e->randState, -1.0f, 1.0f); - e->actions[actionOffset + 2] = randFloat(&e->randState, -1.0f, 1.0f); - e->actions[actionOffset + 3] = randFloat(&e->randState, -1.0f, 1.0f); - e->actions[actionOffset + 4] = randFloat(&e->randState, -1.0f, 1.0f); - e->actions[actionOffset + 5] = randFloat(&e->randState, -1.0f, 1.0f); - e->actions[actionOffset + 6] = randFloat(&e->randState, -1.0f, 1.0f); + e->actions[actionOffset + 0] = randFloat(&e->rng, -1.0f, 1.0f); + e->actions[actionOffset + 1] = randFloat(&e->rng, -1.0f, 1.0f); + e->actions[actionOffset + 2] = randFloat(&e->rng, -1.0f, 1.0f); + e->actions[actionOffset + 3] = randFloat(&e->rng, -1.0f, 1.0f); + e->actions[actionOffset + 4] = randFloat(&e->rng, -1.0f, 1.0f); + e->actions[actionOffset + 5] = randFloat(&e->rng, -1.0f, 1.0f); + e->actions[actionOffset + 6] = randFloat(&e->rng, -1.0f, 1.0f); actionOffset += CONTINUOUS_ACTION_SIZE; } diff --git a/ocean/impulse_wars/binding.c b/ocean/impulse_wars/binding.c index 28b429773b..0e4186fbf2 100644 --- a/ocean/impulse_wars/binding.c +++ b/ocean/impulse_wars/binding.c @@ -1,177 +1,81 @@ -#include - #include "env.h" -static PyObject *get_consts(PyObject *self, PyObject *args); +#define OBS_SIZE 998 // for 2 drones (players) +// actions: +// 9: move, noop + 8 directions +// 17: aim, noop + 16 directions +// 2: shoot or not +// 2: brake or not +// 2: burst or not +#define NUM_ATNS 5 +#define ACT_SIZES {9, 17, 2, 2, 2} +#define OBS_TENSOR_T FloatTensor #define Env iwEnv -#define MY_SHARED -#define MY_METHODS {"get_consts", get_consts, METH_VARARGS, "Get constants"} - -#include "../env_binding.h" - -#define setDictVal(dict, key, val) \ - if (PyDict_SetItemString(dict, key, PyLong_FromLong(val)) < 0) { \ - PyErr_SetString(PyExc_RuntimeError, "Failed to set " key " in dict"); \ - return NULL; \ - } - -static PyObject *get_consts(PyObject *self, PyObject *args) { - PyObject *dronesArg = PyTuple_GetItem(args, 0); - if (!PyObject_TypeCheck(dronesArg, &PyLong_Type)) { - PyErr_SetString(PyExc_TypeError, "num_drones must be an integer"); - return NULL; - } - const uint8_t numDrones = (uint8_t)PyLong_AsLong(dronesArg); - - PyObject *dict = PyDict_New(); - if (PyErr_Occurred()) { - return NULL; - } - - const uint16_t droneObsOffset = ENEMY_DRONE_OBS_OFFSET + ((numDrones - 1) * ENEMY_DRONE_OBS_SIZE); +#include "vecenv.h" - setDictVal(dict, "obsBytes", obsBytes(numDrones)); - setDictVal(dict, "mapObsSize", MAP_OBS_SIZE); - setDictVal(dict, "discreteObsSize", discreteObsSize(numDrones)); - setDictVal(dict, "continuousObsSize", continuousObsSize(numDrones)); - setDictVal(dict, "continuousObsBytes", continuousObsSize(numDrones) * sizeof(float)); - setDictVal(dict, "wallTypes", NUM_WALL_TYPES); - setDictVal(dict, "weaponTypes", NUM_WEAPONS + 1); - setDictVal(dict, "mapObsRows", MAP_OBS_ROWS); - setDictVal(dict, "mapObsColumns", MAP_OBS_COLUMNS); - setDictVal(dict, "continuousObsOffset", alignedSize(MAP_OBS_SIZE, sizeof(float))); - setDictVal(dict, "numNearWallObs", NUM_NEAR_WALL_OBS); - setDictVal(dict, "nearWallTypesObsOffset", NEAR_WALL_TYPES_OBS_OFFSET); - setDictVal(dict, "nearWallPosObsSize", NEAR_WALL_POS_OBS_SIZE); - setDictVal(dict, "nearWallObsSize", NEAR_WALL_OBS_SIZE); - setDictVal(dict, "nearWallPosObsOffset", NEAR_WALL_POS_OBS_OFFSET); - setDictVal(dict, "numFloatingWallObs", NUM_FLOATING_WALL_OBS); - setDictVal(dict, "floatingWallTypesObsOffset", FLOATING_WALL_TYPES_OBS_OFFSET); - setDictVal(dict, "floatingWallInfoObsSize", FLOATING_WALL_INFO_OBS_SIZE); - setDictVal(dict, "floatingWallObsSize", FLOATING_WALL_OBS_SIZE); - setDictVal(dict, "floatingWallInfoObsOffset", FLOATING_WALL_INFO_OBS_OFFSET); - setDictVal(dict, "numWeaponPickupObs", NUM_WEAPON_PICKUP_OBS); - setDictVal(dict, "weaponPickupTypesObsOffset", WEAPON_PICKUP_WEAPONS_OBS_OFFSET); - setDictVal(dict, "weaponPickupPosObsSize", WEAPON_PICKUP_POS_OBS_SIZE); - setDictVal(dict, "weaponPickupObsSize", WEAPON_PICKUP_OBS_SIZE); - setDictVal(dict, "weaponPickupPosObsOffset", WEAPON_PICKUP_POS_OBS_OFFSET); - setDictVal(dict, "numProjectileObs", NUM_PROJECTILE_OBS); - setDictVal(dict, "projectileDroneObsOffset", PROJECTILE_DRONE_OBS_OFFSET); - setDictVal(dict, "projectileTypesObsOffset", PROJECTILE_WEAPONS_OBS_OFFSET); - setDictVal(dict, "projectileInfoObsSize", PROJECTILE_INFO_OBS_SIZE); - setDictVal(dict, "projectileObsSize", PROJECTILE_OBS_SIZE); - setDictVal(dict, "projectileInfoObsOffset", PROJECTILE_INFO_OBS_OFFSET); - setDictVal(dict, "enemyDroneWeaponsObsOffset", ENEMY_DRONE_WEAPONS_OBS_OFFSET); - setDictVal(dict, "enemyDroneObsOffset", ENEMY_DRONE_OBS_OFFSET); - setDictVal(dict, "enemyDroneObsSize", ENEMY_DRONE_OBS_SIZE); - setDictVal(dict, "droneObsOffset", droneObsOffset); - setDictVal(dict, "droneObsSize", DRONE_OBS_SIZE); - setDictVal(dict, "miscObsSize", MISC_OBS_SIZE); - setDictVal(dict, "miscObsOffset", droneObsOffset + DRONE_OBS_SIZE); +#define DICTGET(key) dict_get(kwargs, key)->value - setDictVal(dict, "maxDrones", MAX_DRONES); - setDictVal(dict, "contActionsSize", CONTINUOUS_ACTION_SIZE); - - return dict; -} - -static PyObject *my_shared(PyObject *self, PyObject *args, PyObject *kwargs) { - VecEnv *ve = unpack_vecenv(args); - initMaps(ve->envs[0]); - - for (uint16_t i = 0; i < ve->num_envs; i++) { - iwEnv *e = (iwEnv *)ve->envs[i]; - setupEnv(e); - } - - return Py_None; -} - -static int my_init(iwEnv *e, PyObject *args, PyObject *kwargs) { +void my_init(Env *env, Dict *kwargs) { initEnv( - e, - (uint8_t)unpack(kwargs, "num_drones"), - (uint8_t)unpack(kwargs, "num_agents"), - (int8_t)unpack(kwargs, "map_idx"), - (uint64_t)unpack(kwargs, "seed"), - (bool)unpack(kwargs, "enable_teams"), - (bool)unpack(kwargs, "sitting_duck"), - (bool)unpack(kwargs, "is_training"), - (bool)unpack(kwargs, "continuous") + env, + MAX_DRONES, + 1, + -1, + 0, + (bool)DICTGET("enable_teams"), + (bool)DICTGET("sitting_duck"), + (bool)DICTGET("is_training"), + (bool)DICTGET("continuous") ); + setRewards( - e, - (float)unpack(kwargs, "reward_win"), - (float)unpack(kwargs, "reward_self_kill"), - (float)unpack(kwargs, "reward_enemy_death"), - (float)unpack(kwargs, "reward_enemy_kill"), + env, + (float)DICTGET("reward_win"), + (float)DICTGET("reward_self_kill"), + (float)DICTGET("reward_enemy_death"), + (float)DICTGET("reward_enemy_kill"), 0.0f, // teammate death punishment 0.0f, // teammate kill punishment - (float)unpack(kwargs, "reward_death"), - (float)unpack(kwargs, "reward_energy_emptied"), - (float)unpack(kwargs, "reward_weapon_pickup"), - (float)unpack(kwargs, "reward_shield_break"), - (float)unpack(kwargs, "reward_shot_hit_coef"), - (float)unpack(kwargs, "reward_explosion_hit_coef") + (float)DICTGET("reward_death"), + (float)DICTGET("reward_energy_emptied"), + (float)DICTGET("reward_weapon_pickup"), + (float)DICTGET("reward_shield_break"), + (float)DICTGET("reward_shot_hit_coef"), + (float)DICTGET("reward_explosion_hit_coef") ); - return 0; -} - -#define _LOG_BUF_SIZE 128 - -char *droneLog(char *buf, const uint8_t droneIdx, const char *name) { - snprintf(buf, _LOG_BUF_SIZE, "drone_%d_%s", droneIdx, name); - return buf; -} -char *weaponLog(char *buf, const uint8_t droneIdx, const uint8_t weaponIdx, const char *name) { - snprintf(buf, _LOG_BUF_SIZE, "drone_%d_%s_%s", droneIdx, weaponNames[weaponIdx], name); - return buf; + initMaps(env); } -static int my_log(PyObject *dict, Log *log) { - assign_to_dict(dict, "episode_length", log->length); - assign_to_dict(dict, "ties", log->ties); - - assign_to_dict(dict, "perf", log->stats[0].wins); - assign_to_dict(dict, "score", log->stats[0].wins); - - char buf[_LOG_BUF_SIZE] = {0}; - for (uint8_t i = 0; i < MAX_DRONES; i++) { - assign_to_dict(dict, droneLog(buf, i, "returns"), log->stats[i].returns); - assign_to_dict(dict, droneLog(buf, i, "distance_traveled"), log->stats[i].distanceTraveled); - assign_to_dict(dict, droneLog(buf, i, "abs_distance_traveled"), log->stats[i].absDistanceTraveled); - assign_to_dict(dict, droneLog(buf, i, "brake_time"), log->stats[i].brakeTime); - assign_to_dict(dict, droneLog(buf, i, "total_bursts"), log->stats[i].totalBursts); - assign_to_dict(dict, droneLog(buf, i, "bursts_hit"), log->stats[i].burstsHit); - assign_to_dict(dict, droneLog(buf, i, "energy_emptied"), log->stats[i].energyEmptied); - assign_to_dict(dict, droneLog(buf, i, "shields_broken"), log->stats[i].shieldsBroken); - assign_to_dict(dict, droneLog(buf, i, "own_shield_broken"), log->stats[i].ownShieldBroken); - assign_to_dict(dict, droneLog(buf, i, "self_kills"), log->stats[i].selfKills); - assign_to_dict(dict, droneLog(buf, i, "kills"), log->stats[i].kills); - assign_to_dict(dict, droneLog(buf, i, "unknown_kills"), log->stats[i].unknownKills); - assign_to_dict(dict, droneLog(buf, i, "wins"), log->stats[i].wins); - - // useful for debugging weapon balance, but really slows down - // sweeps due to adding a ton of extra logging data - // - // for (uint8_t j = 0; j < _NUM_WEAPONS; j++) { - // assign_to_dict(dict, weaponLog(buf, i, j, "shots_fired"), log->stats[i].shotsFired[j]); - // assign_to_dict(dict, weaponLog(buf, i, j, "shots_hit"), log->stats[i].shotsHit[j]); - // assign_to_dict(dict, weaponLog(buf, i, j, "shots_taken"), log->stats[i].shotsTaken[j]); - // assign_to_dict(dict, weaponLog(buf, i, j, "own_shots_taken"), log->stats[i].ownShotsTaken[j]); - // assign_to_dict(dict, weaponLog(buf, i, j, "picked_up"), log->stats[i].weaponsPickedUp[j]); - // assign_to_dict(dict, weaponLog(buf, i, j, "shot_distances"), log->stats[i].shotDistances[j]); - // } - - assign_to_dict(dict, droneLog(buf, i, "total_shots_fired"), log->stats[i].totalShotsFired); - assign_to_dict(dict, droneLog(buf, i, "total_shots_hit"), log->stats[i].totalShotsHit); - assign_to_dict(dict, droneLog(buf, i, "total_shots_taken"), log->stats[i].totalShotsTaken); - assign_to_dict(dict, droneLog(buf, i, "total_own_shots_taken"), log->stats[i].totalOwnShotsTaken); - assign_to_dict(dict, droneLog(buf, i, "total_picked_up"), log->stats[i].totalWeaponsPickedUp); - assign_to_dict(dict, droneLog(buf, i, "total_shot_distances"), log->stats[i].totalShotDistances); - } - - return 0; +#define LOG_DRONE_STATS(log, out, idx, idxStr) \ + dict_set(out, "drone_" idxStr "_returns", log->stats[idx].returns); \ + dict_set(out, "drone_" idxStr "_distance_traveled", log->stats[idx].distanceTraveled); \ + dict_set(out, "drone_" idxStr "_abs_distance_traveled", log->stats[idx].absDistanceTraveled); \ + dict_set(out, "drone_" idxStr "_brake_time", log->stats[idx].brakeTime); \ + dict_set(out, "drone_" idxStr "_total_bursts", log->stats[idx].totalBursts); \ + dict_set(out, "drone_" idxStr "_bursts_hit", log->stats[idx].burstsHit); \ + dict_set(out, "drone_" idxStr "_energy_emptied", log->stats[idx].energyEmptied); \ + dict_set(out, "drone_" idxStr "_shields_broken", log->stats[idx].shieldsBroken); \ + dict_set(out, "drone_" idxStr "_own_shield_broken", log->stats[idx].ownShieldBroken); \ + dict_set(out, "drone_" idxStr "_self_kills", log->stats[idx].selfKills); \ + dict_set(out, "drone_" idxStr "_kills", log->stats[idx].kills); \ + dict_set(out, "drone_" idxStr "_unknown_kills", log->stats[idx].unknownKills); \ + dict_set(out, "drone_" idxStr "_wins", log->stats[idx].wins); \ + dict_set(out, "drone_" idxStr "_total_shots_fired", log->stats[idx].totalShotsFired); \ + dict_set(out, "drone_" idxStr "_total_shots_hit", log->stats[idx].totalShotsHit); \ + dict_set(out, "drone_" idxStr "_total_shots_taken", log->stats[idx].totalShotsTaken); \ + dict_set(out, "drone_" idxStr "_total_own_shots_taken", log->stats[idx].totalOwnShotsTaken); \ + dict_set(out, "drone_" idxStr "_total_picked_up", log->stats[idx].totalWeaponsPickedUp); \ + dict_set(out, "drone_" idxStr "_total_shot_distances", log->stats[idx].totalShotDistances) + +void my_log(Log *log, Dict *out) { + dict_set(out, "episode_length", log->length); + dict_set(out, "ties", log->ties); + + dict_set(out, "perf", log->stats[0].wins); + dict_set(out, "score", log->stats[0].wins); + + LOG_DRONE_STATS(log, out, 0, "0"); + LOG_DRONE_STATS(log, out, 1, "1"); } diff --git a/ocean/impulse_wars/env.h b/ocean/impulse_wars/env.h index 2162b50144..2a45003a15 100644 --- a/ocean/impulse_wars/env.h +++ b/ocean/impulse_wars/env.h @@ -292,7 +292,7 @@ void computeNearObs(iwEnv *e, const droneEntity *drone, const uint16_t discreteO } void computeObs(iwEnv *e) { - for (uint8_t agentIdx = 0; agentIdx < e->numAgents; agentIdx++) { + for (uint8_t agentIdx = 0; agentIdx < e->num_agents; agentIdx++) { droneEntity *agentDrone = safe_array_get_at(e->drones, agentIdx); // if the drone is dead, only compute observations if it died // this step and it isn't out of bounds @@ -463,6 +463,7 @@ void computeObs(iwEnv *e) { } void setupEnv(iwEnv *e) { + e->isSetup = true; e->needsReset = false; e->stepsLeft = e->totalSteps; @@ -478,7 +479,7 @@ void setupEnv(iwEnv *e) { if (!e->isTraining) { firstMap = 1; } - mapIdx = randInt(&e->randState, firstMap, NUM_MAPS - 1); + mapIdx = randInt(&e->rng, firstMap, NUM_MAPS - 1); } DEBUG_LOGF("setting up map %d", mapIdx); setupMap(e, mapIdx); @@ -493,7 +494,7 @@ void setupEnv(iwEnv *e) { DEBUG_LOG("creating weapon pickups"); // start spawning pickups in a random quadrant - e->lastSpawnQuad = randInt(&e->randState, 0, 3); + e->lastSpawnQuad = randInt(&e->rng, 0, 3); for (uint8_t i = 0; i < maps[mapIdx]->weaponPickups; i++) { createWeaponPickup(e); } @@ -530,7 +531,7 @@ iwEnv *initEnv(iwEnv *e, uint8_t numDrones, uint8_t numAgents, int8_t mapIdx, ui DEBUG_LOGF("seed: %lu", seed); e->numDrones = numDrones; - e->numAgents = numAgents; + e->num_agents = numAgents; e->teamsEnabled = enableTeams; e->numTeams = numDrones; if (e->teamsEnabled) { @@ -557,11 +558,11 @@ iwEnv *initEnv(iwEnv *e, uint8_t numDrones, uint8_t numAgents, int8_t mapIdx, ui e->continuousActions = continuousActions; - // TODO: remove when puffer bindings add truncations - e->truncations = fastCalloc(numDrones, sizeof(uint8_t)); + // e->truncations = fastCalloc(numDrones, sizeof(uint8_t)); setEnvFrameRate(e); - e->randState = seed; + e->rng = seed; + e->isSetup = false; e->needsReset = false; b2WorldDef worldDef = b2DefaultWorldDef(); @@ -620,9 +621,9 @@ void setRewards(iwEnv *e, float winReward, float selfKillPunishment, float enemy void clearEnv(iwEnv *e) { // rewards get cleared in stepEnv every step - // memset(e->masks, 1, e->numAgents * sizeof(uint8_t)); - memset(e->terminals, 0x0, e->numAgents * sizeof(uint8_t)); - memset(e->truncations, 0x0, e->numAgents * sizeof(uint8_t)); + // memset(e->masks, 1, e->num_agents * sizeof(uint8_t)); + memset(e->terminals, 0.0f, e->num_agents * sizeof(float)); + // memset(e->truncations, 0x0, e->num_agents * sizeof(uint8_t)); e->episodeLength = 0; memset(e->stats, 0x0, sizeof(e->stats)); @@ -667,30 +668,33 @@ void clearEnv(iwEnv *e) { } void destroyEnv(iwEnv *e) { - clearEnv(e); + if (e->isSetup) { + clearEnv(e); - for (uint8_t i = 0; i < NUM_MAPS; i++) { - pathingInfo *info = &e->mapPathing[i]; - fastFree(info->paths); - fastFree(info->pathBuffer); - } - fastFree(e->mapPathing); + for (size_t i = 0; i < cc_array_size(e->walls); i++) { + wallEntity *wall = safe_array_get_at(e->walls, i); + destroyWall(e, wall, false); + } - for (size_t i = 0; i < cc_array_size(e->walls); i++) { - wallEntity *wall = safe_array_get_at(e->walls, i); - destroyWall(e, wall, false); - } + for (size_t i = 0; i < cc_array_size(e->cells); i++) { + mapCell *cell = safe_array_get_at(e->cells, i); + fastFree(cell); + } - for (size_t i = 0; i < cc_array_size(e->cells); i++) { - mapCell *cell = safe_array_get_at(e->cells, i); - fastFree(cell); - } + for (size_t i = 0; i < cc_array_size(e->entities); i++) { + entity *ent = safe_array_get_at(e->entities, i); + fastFree(ent->id); + fastFree(ent); + } - for (size_t i = 0; i < cc_array_size(e->entities); i++) { - entity *ent = safe_array_get_at(e->entities, i); - fastFree(ent->id); - fastFree(ent); + for (uint8_t i = 0; i < NUM_MAPS; i++) { + pathingInfo *info = &e->mapPathing[i]; + fastFree(info->paths); + fastFree(info->pathBuffer); + } + fastFree(e->mapPathing); } + b2DestroyIdPool(&e->idPool); cc_array_destroy(e->entities); @@ -712,7 +716,9 @@ void destroyEnv(iwEnv *e) { } void resetEnv(iwEnv *e) { - clearEnv(e); + if (e->isSetup) { + clearEnv(e); + } setupEnv(e); } @@ -749,7 +755,7 @@ float computeReward(iwEnv *e, droneEntity *drone) { reward += e->shieldBreakReward; } - if (e->numAgents == e->numDrones) { + if (e->num_agents == e->numDrones) { if (drone->stepInfo.shotTaken[i] != 0) { reward -= drone->stepInfo.shotTaken[i] * e->shotHitRewardCoef; } @@ -791,7 +797,7 @@ float computeReward(iwEnv *e, droneEntity *drone) { const float REWARD_EPS = 1.0e-6f; void computeRewards(iwEnv *e, const bool roundOver, const int8_t winner, const int8_t winningTeam) { - if (roundOver && winner != -1 && winner < e->numAgents) { + if (roundOver && winner != -1 && winner < e->num_agents) { e->rewards[winner] += e->winReward; } @@ -807,7 +813,7 @@ void computeRewards(iwEnv *e, const bool roundOver, const int8_t winner, const i reward += e->selfKillPunishment; } } - if (i < e->numAgents) { + if (i < e->num_agents) { e->rewards[i] += reward; } e->stats[i].returns += reward; @@ -821,23 +827,39 @@ static inline bool isActionNoop(const b2Vec2 action) { agentActions _computeActions(iwEnv *e, droneEntity *drone, const agentActions *manualActions) { agentActions actions = {0}; - const uint8_t offset = drone->idx * CONTINUOUS_ACTION_SIZE; if (manualActions == NULL) { - actions.move = (b2Vec2){.x = e->actions[offset + 0], .y = e->actions[offset + 1]}; - actions.aim = (b2Vec2){.x = e->actions[offset + 2], .y = e->actions[offset + 3]}; + float (*envActions)[7] = (float (*)[7])e->actions; + + uint8_t move = envActions[drone->idx][0]; + // 0 is no-op for both move and aim + ASSERT(move <= 8); + if (move != 0) { + move--; + actions.move.x = discMoveToContMoveMap[0][move]; + actions.move.y = discMoveToContMoveMap[1][move]; + } + uint8_t aim = envActions[drone->idx][0]; + ASSERT(aim <= 16); + if (aim != 0) { + aim--; + actions.aim.x = discAimToContAimMap[0][aim]; + actions.aim.y = discAimToContAimMap[1][aim]; + } + if (e->continuousActions) { actions.move.x = tanhf(actions.move.x); actions.move.y = tanhf(actions.move.y); actions.aim.x = tanhf(actions.aim.x); actions.aim.y = tanhf(actions.aim.y); } - actions.chargingWeapon = e->actions[offset + 4] > 0.0f; + + actions.chargingWeapon = envActions[drone->idx][4] > 0.0f; actions.shoot = actions.chargingWeapon; if (!actions.chargingWeapon && drone->chargingWeapon) { actions.shoot = true; } - actions.brake = e->actions[offset + 5] > 0.0f; - actions.chargingBurst = e->actions[offset + 6] > 0.0f; + actions.brake = envActions[drone->idx][5] > 0.0f; + actions.chargingBurst = envActions[drone->idx][6] > 0.0f; } else { actions.move = manualActions->move; actions.aim = manualActions->aim; @@ -1065,7 +1087,7 @@ void stepEnv(iwEnv *e) { continue; } - if (i < e->numAgents) { + if (i < e->num_agents) { stepActions[i] = computeActions(e, drone, NULL); } else { const agentActions scriptedActions = scriptedAgentActions(e, drone); @@ -1074,7 +1096,7 @@ void stepEnv(iwEnv *e) { } // reset reward buffer - memset(e->rewards, 0x0, e->numAgents * sizeof(float)); + memset(e->rewards, 0x0, e->num_agents * sizeof(float)); for (int i = 0; i < e->frameSkip; i++) { #ifdef __EMSCRIPTEN__ @@ -1154,7 +1176,7 @@ void stepEnv(iwEnv *e) { // handle sudden death e->stepsLeft = max(e->stepsLeft - 1, 0); - if ((!e->isTraining || e->numDrones == e->numAgents) && e->stepsLeft == 0) { + if ((!e->isTraining || e->numDrones == e->num_agents) && e->stepsLeft == 0) { e->suddenDeathSteps = max(e->suddenDeathSteps - 1, 0); if (e->suddenDeathSteps == 0) { DEBUG_LOG("placing sudden death walls"); @@ -1190,9 +1212,9 @@ void stepEnv(iwEnv *e) { } } else { deadDrones++; - if (i < e->numAgents) { + if (i < e->num_agents) { if (drone->diedThisStep) { - e->terminals[i] = 1; + e->terminals[i] = 1.0f; } // else { // e->masks[i] = 0; @@ -1212,7 +1234,7 @@ void stepEnv(iwEnv *e) { } // if the enemy drone(s) are scripted don't enable sudden death // so that the agent has to work for victories - if (e->isTraining && e->numDrones != e->numAgents && e->stepsLeft == 0) { + if (e->isTraining && e->numDrones != e->num_agents && e->stepsLeft == 0) { roundOver = true; lastAliveTeam = -1; } @@ -1226,13 +1248,13 @@ void stepEnv(iwEnv *e) { } if (roundOver) { - if (e->numDrones != e->numAgents && e->stepsLeft == 0) { - DEBUG_LOG("truncating episode"); - memset(e->truncations, 1, e->numAgents * sizeof(uint8_t)); - } else { - DEBUG_LOG("terminating episode"); - memset(e->terminals, 1, e->numAgents * sizeof(uint8_t)); - } + // if (e->numDrones != e->num_agents && e->stepsLeft == 0) { + // DEBUG_LOG("truncating episode"); + // memset(e->truncations, 1, e->num_agents * sizeof(uint8_t)); + // } + + DEBUG_LOG("terminating episode"); + memset(e->terminals, 1.0f, e->num_agents * sizeof(float)); Log log = {0}; log.length = e->episodeLength; diff --git a/ocean/impulse_wars/game.h b/ocean/impulse_wars/game.h index 65e76f12e5..c47539f906 100644 --- a/ocean/impulse_wars/game.h +++ b/ocean/impulse_wars/game.h @@ -336,14 +336,14 @@ bool findOpenPos(iwEnv *e, const enum shapeCategory shapeType, b2Vec2 *emptyPos, uint16_t cellIdx; if (quad == -1) { - cellIdx = randInt(&e->randState, 0, nCells); + cellIdx = randInt(&e->rng, 0, nCells); } else { const float minX = e->map->spawnQuads[quad].min.x; const float minY = e->map->spawnQuads[quad].min.y; const float maxX = e->map->spawnQuads[quad].max.x; const float maxY = e->map->spawnQuads[quad].max.y; - b2Vec2 randPos = {.x = randFloat(&e->randState, minX, maxX), .y = randFloat(&e->randState, minY, maxY)}; + b2Vec2 randPos = {.x = randFloat(&e->rng, minX, maxX), .y = randFloat(&e->rng, minY, maxY)}; cellIdx = entityPosToCellIdx(e, randPos); } if (bitTest(checkedCells, cellIdx)) { @@ -551,7 +551,7 @@ enum weaponType randWeaponPickupType(iwEnv *e) { totalWeight += spawnWeights[i - 1]; } - const float randPick = randFloat(&e->randState, 0.0f, totalWeight); + const float randPick = randFloat(&e->rng, 0.0f, totalWeight); float cumulativeWeight = 0.0f; enum weaponType type = STANDARD_WEAPON; for (uint8_t i = 1; i < NUM_WEAPONS; i++) { @@ -718,7 +718,7 @@ void createDrone(iwEnv *e, const uint8_t idx) { // doing this while training will result in much slower learning // due to drones starting much farther apart if (e->lastSpawnQuad == -1) { - spawnQuad = randInt(&e->randState, 0, 3); + spawnQuad = randInt(&e->rng, 0, 3); } else if (e->numDrones == 2) { spawnQuad = 3 - e->lastSpawnQuad; } else { @@ -787,10 +787,10 @@ void droneAddEnergy(droneEntity *drone, float energy) { } void createDronePiece(iwEnv *e, droneEntity *drone, const bool fromShield) { - const float distance = randFloat(&e->randState, DRONE_PIECE_MIN_DISTANCE, DRONE_PIECE_MAX_DISTANCE); - const b2Vec2 direction = {.x = randFloat(&e->randState, -1.0f, 1.0f), .y = randFloat(&e->randState, -1.0f, 1.0f)}; + const float distance = randFloat(&e->rng, DRONE_PIECE_MIN_DISTANCE, DRONE_PIECE_MAX_DISTANCE); + const b2Vec2 direction = {.x = randFloat(&e->rng, -1.0f, 1.0f), .y = randFloat(&e->rng, -1.0f, 1.0f)}; const b2Vec2 pos = b2MulAdd(drone->pos, distance, direction); - const b2Rot rot = b2MakeRot(randFloat(&e->randState, -PI, PI)); + const b2Rot rot = b2MakeRot(randFloat(&e->rng, -PI, PI)); dronePieceEntity *piece = fastCalloc(1, sizeof(dronePieceEntity)); piece->droneIdx = drone->idx; @@ -810,9 +810,9 @@ void createDronePiece(iwEnv *e, droneEntity *drone, const bool fromShield) { pieceBodyDef.linearDamping = DRONE_PIECE_LINEAR_DAMPING; pieceBodyDef.angularDamping = DRONE_PIECE_ANGULAR_DAMPING; const float bonus = 1.0f + min(b2Length(drone->velocity) / 15.0f, 5.0f); - const float speed = randFloat(&e->randState, DRONE_PIECE_MIN_SPEED, DRONE_PIECE_MAX_SPEED) * bonus; + const float speed = randFloat(&e->rng, DRONE_PIECE_MIN_SPEED, DRONE_PIECE_MAX_SPEED) * bonus; pieceBodyDef.linearVelocity = b2MulSV(speed, direction); - pieceBodyDef.angularVelocity = randFloat(&e->randState, -PI, PI); + pieceBodyDef.angularVelocity = randFloat(&e->rng, -PI, PI); pieceBodyDef.userData = ent; piece->bodyID = b2CreateBody(e->worldID, &pieceBodyDef); @@ -1145,8 +1145,8 @@ void createProjectile(iwEnv *e, droneEntity *drone, const b2Vec2 normAim) { b2Vec2 forwardVel = b2MulSV(b2Dot(drone->velocity, normAim), normAim); b2Vec2 lateralVel = b2Sub(drone->velocity, forwardVel); lateralVel = b2MulSV(projectileShapeDef.density * DRONE_MOVE_AIM_COEF, lateralVel); - b2Vec2 aim = weaponAdjustAim(&e->randState, drone->weaponInfo->type, drone->heat, normAim); - b2Vec2 fire = b2MulAdd(lateralVel, weaponFire(&e->randState, drone->weaponInfo->type), aim); + b2Vec2 aim = weaponAdjustAim(&e->rng, drone->weaponInfo->type, drone->heat, normAim); + b2Vec2 fire = b2MulAdd(lateralVel, weaponFire(&e->rng, drone->weaponInfo->type), aim); b2Body_ApplyLinearImpulseToCenter(projectileBodyID, fire, true); projectileEntity *projectile = fastCalloc(1, sizeof(projectileEntity)); @@ -1387,8 +1387,8 @@ bool explodeCallback(b2ShapeId shapeID, void *context) { // if the direction is zero, the magnitude cannot be calculated // correctly so set the direction randomly if (b2VecEqual(direction, b2Vec2_zero)) { - direction.x = randFloat(&ctx->e->randState, -1.0f, 1.0f); - direction.y = randFloat(&ctx->e->randState, -1.0f, 1.0f); + direction.x = randFloat(&ctx->e->rng, -1.0f, 1.0f); + direction.y = randFloat(&ctx->e->rng, -1.0f, 1.0f); direction = b2Normalize(direction); } diff --git a/ocean/impulse_wars/helpers.h b/ocean/impulse_wars/helpers.h index 1692d6b9bd..4458ffae71 100644 --- a/ocean/impulse_wars/helpers.h +++ b/ocean/impulse_wars/helpers.h @@ -8,7 +8,7 @@ #include "box2d/box2d.h" -#include "include/cc_array.h" +#include "cc_array.h" #ifndef NDEBUG #define ON_ERROR __builtin_trap() @@ -109,26 +109,12 @@ ASSERTF(fabs(vec.y - norm.y) < 0.000001f, "vec: %f, %f norm: %f, %f", vec.x, vec.y, norm.x, norm.y); \ } while (0) -// use malloc when debugging so the address sanitizer can find issues with -// heap memory, use dlmalloc in release mode for performance; emscripten -// uses dlmalloc by default so no need to change anything here; dlmalloc -// sometimes won't compile on macOS so just use malloc and friends -#if !defined(NDEBUG) || defined(__EMSCRIPTEN__) || defined(__APPLE__) #define fastMalloc(size) malloc(size) #define fastMallocFn malloc #define fastCalloc(nmemb, size) calloc(nmemb, size) #define fastCallocFn calloc #define fastFree(ptr) free(ptr) #define fastFreeFn free -#else -#include "include/dlmalloc.h" -#define fastMalloc(size) dlmalloc(size) -#define fastMallocFn dlmalloc -#define fastCalloc(nmemb, size) dlcalloc(nmemb, size) -#define fastCallocFn dlcalloc -#define fastFree(ptr) dlfree(ptr) -#define fastFreeFn dlfree -#endif static inline void create_array(CC_Array **array, size_t initialCap) { CC_ArrayConf conf; diff --git a/ocean/impulse_wars/impulse_wars.c b/ocean/impulse_wars/impulse_wars.c index 1dfbd1d5c3..ec8e82cab3 100644 --- a/ocean/impulse_wars/impulse_wars.c +++ b/ocean/impulse_wars/impulse_wars.c @@ -16,9 +16,9 @@ int main(void) { posix_memalign((void **)&e->observations, sizeof(void *), alignedSize(NUM_DRONES * obsBytes(NUM_DRONES), sizeof(float))); e->rewards = fastCalloc(NUM_DRONES, sizeof(float)); e->actions = fastCalloc(NUM_DRONES * CONTINUOUS_ACTION_SIZE, sizeof(float)); - e->masks = fastCalloc(NUM_DRONES, sizeof(uint8_t)); - e->terminals = fastCalloc(NUM_DRONES, sizeof(uint8_t)); - e->truncations = fastCalloc(NUM_DRONES, sizeof(uint8_t)); + // e->masks = fastCalloc(NUM_DRONES, sizeof(uint8_t)); + e->terminals = fastCalloc(NUM_DRONES, sizeof(float)); + // e->truncations = fastCalloc(NUM_DRONES, sizeof(uint8_t)); rayClient *client = createRayClient(); e->client = client; @@ -41,9 +41,9 @@ int main(void) { free(e->observations); fastFree(e->actions); fastFree(e->rewards); - fastFree(e->masks); + // fastFree(e->masks); fastFree(e->terminals); - fastFree(e->truncations); + // fastFree(e->truncations); fastFree(e); destroyRayClient(client); #endif diff --git a/ocean/impulse_wars/map.h b/ocean/impulse_wars/map.h index eb5f2c7026..3e3c3f7c50 100644 --- a/ocean/impulse_wars/map.h +++ b/ocean/impulse_wars/map.h @@ -450,8 +450,8 @@ void setupMap(iwEnv *e, const uint8_t mapIdx) { e->mapIdx = mapIdx; e->map = maps[mapIdx]; e->defaultWeapon = weaponInfos[maps[mapIdx]->defaultWeapon]; - if (e->isTraining && randFloat(&e->randState, 0.0f, 1.0f) < 0.25f) { - e->defaultWeapon = weaponInfos[randInt(&e->randState, 0, NUM_WEAPONS - 1)]; + if (e->isTraining && randFloat(&e->rng, 0.0f, 1.0f) < 0.25f) { + e->defaultWeapon = weaponInfos[randInt(&e->rng, 0, NUM_WEAPONS - 1)]; } uint16_t cellIdx = 0; @@ -576,7 +576,13 @@ bool posValidDroneSpawnPoint(const iwEnv *e, const b2Vec2 pos) { return true; } +bool MAPS_INITIALIZED = false; + void initMaps(iwEnv *e) { + if (MAPS_INITIALIZED) { + return; + } + for (uint8_t i = 0; i < NUM_MAPS; i++) { setupMap(e, i); mapEntry *map = maps[i]; @@ -631,6 +637,8 @@ void initMaps(iwEnv *e) { } e->mapIdx = -1; + + MAPS_INITIALIZED = true; } void destroyMaps() { diff --git a/ocean/impulse_wars/render.h b/ocean/impulse_wars/render.h index d4300d5538..0b46c74d98 100644 --- a/ocean/impulse_wars/render.h +++ b/ocean/impulse_wars/render.h @@ -809,7 +809,7 @@ void renderUI(const iwEnv *e, const bool starting) { char *playerType = ""; if (droneControlledByHuman(e, drone->idx)) { playerType = "Human"; - } else if (drone->idx < e->numAgents) { + } else if (drone->idx < e->num_agents) { playerType = "NN"; } else { if (e->sittingDuck) { @@ -1211,7 +1211,7 @@ void renderDroneGuides(iwEnv *e, const droneEntity *drone, const bool ending) { if (!b2VecEqual(drone->lastMove, b2Vec2_zero) && !ending) { const float moveMagnitude = b2Length(drone->lastMove); const float thrusterAngle = RAD2DEG * b2Atan2(-drone->lastMove.y, -drone->lastMove.x); - const float flickerWidth = randFloat(&e->randState, -0.05f, 0.05f); + const float flickerWidth = randFloat(&e->rng, -0.05f, 0.05f); const float thrusterWidth = 2.5f * ((halfDroneRadius * moveMagnitude) + halfDroneRadius + flickerWidth); const b2Vec2 thrusterPos = b2MulAdd(drone->pos, -thrusterWidth / 2.0f, drone->lastMove); const Color thrusterColor = Fade(getDroneColor(drone->idx), 0.9); diff --git a/ocean/impulse_wars/types.h b/ocean/impulse_wars/types.h index 6df5014e95..682b280e72 100644 --- a/ocean/impulse_wars/types.h +++ b/ocean/impulse_wars/types.h @@ -6,11 +6,11 @@ #include "raylib.h" #include "rlights.h" -#include "include/cc_array.h" +#include "cc_array.h" #include "settings.h" -#define _MAX_DRONES 4 +#define _MAX_DRONES 2 const uint8_t NUM_WALL_TYPES = 3; @@ -413,7 +413,7 @@ typedef struct debugPoint { typedef struct iwEnv { uint8_t numDrones; - uint8_t numAgents; + uint8_t num_agents; uint8_t numTeams; bool teamsEnabled; bool sittingDuck; @@ -439,15 +439,16 @@ typedef struct iwEnv { uint8_t *observations; float *rewards; float *actions; - uint8_t *masks; - uint8_t *terminals; - uint8_t *truncations; + // uint8_t *masks; + float *terminals; + // uint8_t *truncations; uint8_t frameRate; float deltaTime; uint8_t frameSkip; uint8_t box2dSubSteps; - uint64_t randState; + uint64_t rng; + bool isSetup; bool needsReset; uint16_t episodeLength; diff --git a/pufferlib/pufferl.py b/pufferlib/pufferl.py index 8fc0c03a89..19c54791aa 100644 --- a/pufferlib/pufferl.py +++ b/pufferlib/pufferl.py @@ -146,7 +146,7 @@ def print_dashboard(args, model_size, flat_logs, clear=False, idx=[0], u = left if i % 2 == 0 else right u.add_row(f'{b2}{k[4:]}', f'{b2}{v:.3f}') i += 1 - if i == 30: + if i == 60: break if clear: diff --git a/resources/impulse_wars/shaders/gls330/bloom.fs b/resources/impulse_wars/shaders/gls330/bloom.fs index 246acb6af8..bcd2fa189d 100644 --- a/resources/impulse_wars/shaders/gls330/bloom.fs +++ b/resources/impulse_wars/shaders/gls330/bloom.fs @@ -23,7 +23,7 @@ #define BLOOM_ADDITIVE 1 #define BLOOM_SOFT_LIGHT 2 -noperspective in vec2 fragTexCoord; +in vec2 fragTexCoord; uniform sampler2D uTexColor; uniform sampler2D uTexBloomBlur; diff --git a/resources/impulse_wars/shaders/gls330/blur.fs b/resources/impulse_wars/shaders/gls330/blur.fs index f853a495e1..880d2ad2cc 100644 --- a/resources/impulse_wars/shaders/gls330/blur.fs +++ b/resources/impulse_wars/shaders/gls330/blur.fs @@ -22,7 +22,7 @@ #version 330 core -noperspective in vec2 fragTexCoord; +in vec2 fragTexCoord; uniform sampler2D uTexture; uniform vec2 uTexelDir; diff --git a/src/bindings.cu b/src/bindings.cu index 4469cb512c..4f413e8983 100644 --- a/src/bindings.cu +++ b/src/bindings.cu @@ -106,7 +106,7 @@ pybind11::dict puf_eval_log(pybind11::object pufferl_obj) { pufferl.last_log_step = pufferl.global_step; pybind11::dict env_dict; - Dict* env_out = create_dict(32); + Dict* env_out = create_dict(64); static_vec_eval_log(pufferl.vec, env_out); for (int i = 0; i < env_out->size; i++) { env_dict[env_out->items[i].key] = env_out->items[i].value; @@ -318,7 +318,7 @@ void cpu_vec_step_py(VecEnv& ve, long long actions_ptr) { } py::dict vec_log(VecEnv& ve) { - Dict* out = create_dict(32); + Dict* out = create_dict(64); static_vec_log(ve.vec, out); py::dict result; for (int i = 0; i < out->size; i++) { diff --git a/src/bindings_cpu.cpp b/src/bindings_cpu.cpp index 5ba4dc81e5..a4e0b7633c 100644 --- a/src/bindings_cpu.cpp +++ b/src/bindings_cpu.cpp @@ -141,7 +141,7 @@ static void cpu_vec_step_py(VecEnv& ve, long long actions_ptr) { } static py::dict vec_log(VecEnv& ve) { - Dict* out = create_dict(32); + Dict* out = create_dict(64); static_vec_log(ve.vec, out); py::dict result; for (int i = 0; i < out->size; i++) diff --git a/src/pufferlib.cu b/src/pufferlib.cu index 3a3e6ee00e..c74d3553ee 100644 --- a/src/pufferlib.cu +++ b/src/pufferlib.cu @@ -330,7 +330,7 @@ typedef struct { } PuffeRL; Dict* log_environments_impl(PuffeRL& pufferl) { - Dict* out = create_dict(32); + Dict* out = create_dict(64); static_vec_log(pufferl.vec, out); return out; } diff --git a/vendor/cc_array.h b/vendor/cc_array.h new file mode 100644 index 0000000000..311f99122b --- /dev/null +++ b/vendor/cc_array.h @@ -0,0 +1,1410 @@ +/* + * Collections-C + * Copyright (C) 2013-2015 Srđan Panić + * + * This file is part of Collections-C. + * + * Collections-C is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Collections-C is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Collections-C. If not, see . + */ + +#ifndef CC_ARRAY_H +#define CC_ARRAY_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "cc_common.h" + +/** + * A dynamic array that expands automatically as elements are + * added. The array supports amortized constant time insertion + * and removal of elements at the end of the array, as well as + * constant time access. + */ +typedef struct cc_array_s CC_Array; + +/** + * Array configuration structure. Used to initialize a new Array + * with specific values. + */ +typedef struct cc_array_conf_s { + /** + * The initial capacity of the array */ + size_t capacity; + + /** + * The rate at which the buffer expands (capacity * exp_factor). */ + float exp_factor; + + /** + * Memory allocators used to allocate the Array structure and the + * underlying data buffers. */ + void *(*mem_alloc)(size_t size); + void *(*mem_calloc)(size_t blocks, size_t size); + void (*mem_free)(void *block); +} CC_ArrayConf; + +/** + * Array iterator structure. Used to iterate over the elements of + * the array in an ascending order. The iterator also supports + * operations for safely adding and removing elements during + * iteration. + */ +typedef struct cc_array_iter_s { + /** + * The array associated with this iterator */ + CC_Array *ar; + + /** + * The current position of the iterator.*/ + size_t index; + + /** + * Set to true if the last returned element was removed. */ + bool last_removed; +} CC_ArrayIter; + +/** + * Array zip iterator structure. Used to iterate over the elements of two + * arrays in lockstep in an ascending order until one of the Arrays is + * exhausted. The iterator also supports operations for safely adding + * and removing elements during iteration. + */ +typedef struct array_zip_iter_s { + CC_Array *ar1; + CC_Array *ar2; + size_t index; + bool last_removed; +} CC_ArrayZipIter; + +enum cc_stat cc_array_new(CC_Array **out); +enum cc_stat cc_array_new_conf(CC_ArrayConf const *const conf, CC_Array **out); +void cc_array_conf_init(CC_ArrayConf *conf); +size_t cc_array_struct_size(); + +void cc_array_destroy(CC_Array *ar); +void cc_array_destroy_cb(CC_Array *ar, void (*cb)(void *)); + +enum cc_stat cc_array_add(CC_Array *ar, void *element); +enum cc_stat cc_array_add_at(CC_Array *ar, void *element, size_t index); +enum cc_stat cc_array_replace_at(CC_Array *ar, void *element, size_t index, void **out); +enum cc_stat cc_array_swap_at(CC_Array *ar, size_t index1, size_t index2); + +enum cc_stat cc_array_remove(CC_Array *ar, void *element, void **out); +enum cc_stat cc_array_remove_fast(CC_Array *ar, void *element, void **out); +enum cc_stat cc_array_remove_at(CC_Array *ar, size_t index, void **out); +enum cc_stat cc_array_remove_fast_at(CC_Array *ar, size_t index, void **out); +enum cc_stat cc_array_remove_last(CC_Array *ar, void **out); +void cc_array_remove_all(CC_Array *ar); +void cc_array_remove_all_free(CC_Array *ar); + +enum cc_stat cc_array_get_at(const CC_Array *ar, size_t index, void **out); +enum cc_stat cc_array_get_last(const CC_Array *ar, void **out); + +enum cc_stat cc_array_subarray(CC_Array *ar, size_t from, size_t to, CC_Array **out); +enum cc_stat cc_array_copy_shallow(CC_Array *ar, CC_Array **out); +enum cc_stat cc_array_copy_deep(CC_Array *ar, void *(*cp)(void *), CC_Array **out); + +void cc_array_reverse(CC_Array *ar); +enum cc_stat cc_array_trim_capacity(CC_Array *ar); + +size_t cc_array_contains(const CC_Array *ar, void *element); +size_t cc_array_contains_value(const CC_Array *ar, void *element, int (*cmp)(const void *, const void *)); +size_t cc_array_size(const CC_Array *ar); +size_t cc_array_capacity(const CC_Array *ar); + +enum cc_stat cc_array_index_of(const CC_Array *ar, void *element, size_t *index); +void cc_array_sort(CC_Array *ar, int (*cmp)(const void *, const void *)); + +void cc_array_map(CC_Array *ar, void (*fn)(void *)); +void cc_array_reduce(CC_Array *ar, void (*fn)(void *, void *, void *), void *result); + +enum cc_stat cc_array_filter_mut(CC_Array *ar, bool (*predicate)(const void *)); +enum cc_stat cc_array_filter(CC_Array *ar, bool (*predicate)(const void *), CC_Array **out); + +void cc_array_iter_init(CC_ArrayIter *iter, CC_Array *ar); +enum cc_stat cc_array_iter_next(CC_ArrayIter *iter, void **out); +enum cc_stat cc_array_iter_remove(CC_ArrayIter *iter, void **out); +enum cc_stat cc_array_iter_remove_fast(CC_ArrayIter *iter, void **out); +enum cc_stat cc_array_iter_add(CC_ArrayIter *iter, void *element); +enum cc_stat cc_array_iter_replace(CC_ArrayIter *iter, void *element, void **out); +size_t cc_array_iter_index(CC_ArrayIter *iter); + +void cc_array_zip_iter_init(CC_ArrayZipIter *iter, CC_Array *a1, CC_Array *a2); +enum cc_stat cc_array_zip_iter_next(CC_ArrayZipIter *iter, void **out1, void **out2); +enum cc_stat cc_array_zip_iter_add(CC_ArrayZipIter *iter, void *e1, void *e2); +enum cc_stat cc_array_zip_iter_remove(CC_ArrayZipIter *iter, void **out1, void **out2); +enum cc_stat cc_array_zip_iter_replace(CC_ArrayZipIter *iter, void *e1, void *e2, void **out1, void **out2); +size_t cc_array_zip_iter_index(CC_ArrayZipIter *iter); + +const void *const *cc_array_get_buffer(CC_Array *ar); + +#define CC_ARRAY_FOREACH(val, array, body) \ + { \ + CC_ArrayIter cc_array_iter_53d46d2a04458e7b; \ + cc_array_iter_init(&cc_array_iter_53d46d2a04458e7b, array); \ + void *val; \ + while (cc_array_iter_next(&cc_array_iter_53d46d2a04458e7b, &val) != CC_ITER_END) \ + body \ + } + +#define CC_ARRAY_FOREACH_ZIP(val1, val2, array1, array2, body) \ + { \ + CC_ArrayZipIter cc_array_zip_iter_ea08d3e52f25883b3; \ + cc_array_zip_iter_init(&cc_array_zip_iter_ea08d3e52f25883b3, array1, array2); \ + void *val1; \ + void *val2; \ + while (cc_array_zip_iter_next(&cc_array_zip_iter_ea08d3e52f25883b3, &val1, &val2) != CC_ITER_END) \ + body \ + } + +#define DEFAULT_CAPACITY 8 +#define DEFAULT_EXPANSION_FACTOR 2 + +struct cc_array_s { + size_t size; + size_t capacity; + float exp_factor; + void **buffer; + + void *(*mem_alloc)(size_t size); + void *(*mem_calloc)(size_t blocks, size_t size); + void (*mem_free)(void *block); +}; + +static enum cc_stat expand_array_capacity(CC_Array *ar); + +/** + * Creates a new empty array and returns a status code. + * + * @param[out] out pointer to where the newly created CC_Array is to be stored + * + * @return CC_OK if the creation was successful, or CC_ERR_ALLOC if the + * memory allocation for the new CC_Array structure failed. + */ +enum cc_stat cc_array_new(CC_Array **out) { + CC_ArrayConf c; + cc_array_conf_init(&c); + return cc_array_new_conf(&c, out); +} + +/** + * Creates a new empty CC_Array based on the specified CC_ArrayConf struct and + * returns a status code. + * + * The CC_Array is allocated using the allocators specified in the CC_ArrayConf + * struct. The allocation may fail if underlying allocator fails. It may also + * fail if the values of exp_factor and capacity in the CC_ArrayConf do not meet + * the following condition: exp_factor < (CC_MAX_ELEMENTS / capacity). + * + * @param[in] conf array configuration structure + * @param[out] out pointer to where the newly created CC_Array is to be stored + * + * @return CC_OK if the creation was successful, CC_ERR_INVALID_CAPACITY if + * the above mentioned condition is not met, or CC_ERR_ALLOC if the memory + * allocation for the new CC_Array structure failed. + */ +enum cc_stat cc_array_new_conf(CC_ArrayConf const *const conf, CC_Array **out) { + float ex; + + /* The expansion factor must be greater than one for the + * array to grow */ + if (conf->exp_factor <= 1) { + ex = DEFAULT_EXPANSION_FACTOR; + } else { + ex = conf->exp_factor; + } + + /* Needed to avoid an integer overflow on the first resize and + * to easily check for any future overflows. */ + if (!conf->capacity || ex >= CC_MAX_ELEMENTS / conf->capacity) { + return CC_ERR_INVALID_CAPACITY; + } + + CC_Array *ar = (CC_Array *)conf->mem_calloc(1, sizeof(CC_Array)); + + if (!ar) { + return CC_ERR_ALLOC; + } + + void **buff = (void **)conf->mem_alloc(conf->capacity * sizeof(void *)); + + if (!buff) { + conf->mem_free(ar); + return CC_ERR_ALLOC; + } + + ar->buffer = buff; + ar->exp_factor = ex; + ar->capacity = conf->capacity; + ar->mem_alloc = conf->mem_alloc; + ar->mem_calloc = conf->mem_calloc; + ar->mem_free = conf->mem_free; + + *out = ar; + return CC_OK; +} + +/** + * Initializes the fields of the CC_ArrayConf struct to default values. + * + * @param[in, out] conf CC_ArrayConf structure that is being initialized + */ +void cc_array_conf_init(CC_ArrayConf *conf) { + conf->exp_factor = DEFAULT_EXPANSION_FACTOR; + conf->capacity = DEFAULT_CAPACITY; + conf->mem_alloc = malloc; + conf->mem_calloc = calloc; + conf->mem_free = free; +} + +/** + * Destroys the CC_Array structure, but leaves the data it used to hold intact. + * + * @param[in] ar the array that is to be destroyed + */ +void cc_array_destroy(CC_Array *ar) { + ar->mem_free(ar->buffer); + ar->mem_free(ar); +} + +/** + * Destroys the CC_Array structure along with all the data it holds. + * + * @note + * This function should not be called on a array that has some of its elements + * allocated on the stack. + * + * @param[in] ar the array that is being destroyed + */ +void cc_array_destroy_cb(CC_Array *ar, void (*cb)(void *)) { + size_t i; + for (i = 0; i < ar->size; i++) { + cb(ar->buffer[i]); + } + + cc_array_destroy(ar); +} + +/** + * Adds a new element to the CC_Array. The element is appended to the array making + * it the last element (the one with the highest index) of the CC_Array. + * + * @param[in] ar the array to which the element is being added + * @param[in] element the element that is being added + * + * @return CC_OK if the element was successfully added, CC_ERR_ALLOC if the + * memory allocation for the new element failed, or CC_ERR_MAX_CAPACITY if the + * array is already at maximum capacity. + */ +enum cc_stat cc_array_add(CC_Array *ar, void *element) { + if (ar->size >= ar->capacity) { + enum cc_stat status = expand_array_capacity(ar); + if (status != CC_OK) { + return status; + } + } + + ar->buffer[ar->size] = element; + ar->size++; + + return CC_OK; +} + +/** + * Adds a new element to the array at a specified position by shifting all + * subsequent elements by one. The specified index must be within the bounds + * of the array. This function may also fail if the memory allocation for + * the new element was unsuccessful. + * + * @param[in] ar the array to which the element is being added + * @param[in] element the element that is being added + * @param[in] index the position in the array at which the element is being + * added + * + * @return CC_OK if the element was successfully added, CC_ERR_OUT_OF_RANGE if + * the specified index was not in range, CC_ERR_ALLOC if the memory + * allocation for the new element failed, or CC_ERR_MAX_CAPACITY if the + * array is already at maximum capacity. + */ +enum cc_stat cc_array_add_at(CC_Array *ar, void *element, size_t index) { + if (index == ar->size) { + return cc_array_add(ar, element); + } + + if ((ar->size == 0 && index != 0) || index > (ar->size - 1)) { + return CC_ERR_OUT_OF_RANGE; + } + + if (ar->size >= ar->capacity) { + enum cc_stat status = expand_array_capacity(ar); + if (status != CC_OK) { + return status; + } + } + + size_t shift = (ar->size - index) * sizeof(void *); + + memmove(&(ar->buffer[index + 1]), + &(ar->buffer[index]), + shift); + + ar->buffer[index] = element; + ar->size++; + + return CC_OK; +} + +/** + * Replaces an array element at the specified index and optionally sets the out + * parameter to the value of the replaced element. The specified index must be + * within the bounds of the CC_Array. + * + * @param[in] ar array whose element is being replaced + * @param[in] element replacement element + * @param[in] index index at which the replacement element should be inserted + * @param[out] out pointer to where the replaced element is stored, or NULL if + * it is to be ignored + * + * @return CC_OK if the element was successfully replaced, or CC_ERR_OUT_OF_RANGE + * if the index was out of range. + */ +enum cc_stat cc_array_replace_at(CC_Array *ar, void *element, size_t index, void **out) { + if (index >= ar->size) { + return CC_ERR_OUT_OF_RANGE; + } + + if (out) { + *out = ar->buffer[index]; + } + + ar->buffer[index] = element; + + return CC_OK; +} + +enum cc_stat cc_array_swap_at(CC_Array *ar, size_t index1, size_t index2) { + void *tmp; + + if (index1 >= ar->size || index2 >= ar->size) { + return CC_ERR_OUT_OF_RANGE; + } + + tmp = ar->buffer[index1]; + + ar->buffer[index1] = ar->buffer[index2]; + ar->buffer[index2] = tmp; + return CC_OK; +} + +/** + * Removes the specified element from the CC_Array if such element exists and + * optionally sets the out parameter to the value of the removed element. + * + * @param[in] ar array from which the element is being removed + * @param[in] element element being removed + * @param[out] out pointer to where the removed value is stored, or NULL + * if it is to be ignored + * + * @return CC_OK if the element was successfully removed, or + * CC_ERR_VALUE_NOT_FOUND if the element was not found. + */ +enum cc_stat cc_array_remove(CC_Array *ar, void *element, void **out) { + size_t index; + enum cc_stat status = cc_array_index_of(ar, element, &index); + + if (status == CC_ERR_OUT_OF_RANGE) { + return CC_ERR_VALUE_NOT_FOUND; + } + + if (index != ar->size - 1) { + size_t block_size = (ar->size - 1 - index) * sizeof(void *); + + memmove(&(ar->buffer[index]), + &(ar->buffer[index + 1]), + block_size); + } + ar->size--; + + if (out) { + *out = element; + } + + return CC_OK; +} + +/** + * Removes a CC_Array element without preserving order and optionally sets the + * out parameter to the value of the removed element. The last element of the + * array is moved to the index of the element being removed, and the last + * element is removed. + * + * @param[in] ar the array whose last element is being removed + * @param[out] out pointer to where the removed value is stored, or NULL if it is + * to be ignored + * + * @return CC_OK if the element was successfully removed, or CC_ERR_OUT_OF_RANGE + * if the CC_Array is already empty. + */ +enum cc_stat cc_array_remove_fast(CC_Array *ar, void *element, void **out) { + size_t index = 0; + const enum cc_stat status = cc_array_index_of(ar, element, &index); + if (status != CC_OK) { + return status; + } + + if (out) { + *out = ar->buffer[index]; + } + + ar->buffer[index] = ar->buffer[ar->size - 1]; + ar->size--; + + return CC_OK; +} + +/** + * Removes an CC_Array element from the specified index and optionally sets the + * out parameter to the value of the removed element. The index must be within + * the bounds of the array. + * + * @param[in] ar the array from which the element is being removed + * @param[in] index the index of the element being removed. + * @param[out] out pointer to where the removed value is stored, + * or NULL if it is to be ignored + * + * @return CC_OK if the element was successfully removed, or CC_ERR_OUT_OF_RANGE + * if the index was out of range. + */ +enum cc_stat cc_array_remove_at(CC_Array *ar, size_t index, void **out) { + if (index >= ar->size) { + return CC_ERR_OUT_OF_RANGE; + } + + if (out) { + *out = ar->buffer[index]; + } + + if (index != ar->size - 1) { + size_t block_size = (ar->size - 1 - index) * sizeof(void *); + + memmove(&(ar->buffer[index]), + &(ar->buffer[index + 1]), + block_size); + } + ar->size--; + + return CC_OK; +} + +/** + * Removes a CC_Array element from the specified index and optionally sets the + * out parameter to the value of the removed element without preserving ordering. + * The last element of the array is moved to the index of the element being removed, + * and the last element is removed. The index must be within the bounds of the array. + * + * @param[in] ar the array from which the element is being removed + * @param[in] index the index of the element being removed. + * @param[out] out pointer to where the removed value is stored, + * or NULL if it is to be ignored + * + * @return CC_OK if the element was successfully removed, or CC_ERR_OUT_OF_RANGE + * if the index was out of range. + */ +enum cc_stat cc_array_remove_fast_at(CC_Array *ar, size_t index, void **out) { + if (index >= ar->size) { + return CC_ERR_OUT_OF_RANGE; + } + + if (out) { + *out = ar->buffer[index]; + } + + ar->buffer[index] = ar->buffer[ar->size - 1]; + ar->size--; + + return CC_OK; +} + +/** + * Removes an CC_Array element from the end of the array and optionally sets the + * out parameter to the value of the removed element. + * + * @param[in] ar the array whose last element is being removed + * @param[out] out pointer to where the removed value is stored, or NULL if it is + * to be ignored + * + * @return CC_OK if the element was successfully removed, or CC_ERR_OUT_OF_RANGE + * if the CC_Array is already empty. + */ +enum cc_stat cc_array_remove_last(CC_Array *ar, void **out) { + return cc_array_remove_at(ar, ar->size - 1, out); +} + +/** + * Removes all elements from the specified array. This function does not shrink + * the array capacity. + * + * @param[in] ar array from which all elements are to be removed + */ +void cc_array_remove_all(CC_Array *ar) { + ar->size = 0; +} + +/** + * Removes and frees all elements from the specified array. This function does + * not shrink the array capacity. + * + * @param[in] ar array from which all elements are to be removed + */ +void cc_array_remove_all_free(CC_Array *ar) { + size_t i; + for (i = 0; i < ar->size; i++) { + free(ar->buffer[i]); + } + + cc_array_remove_all(ar); +} + +/** + * Gets an CC_Array element from the specified index and sets the out parameter to + * its value. The specified index must be within the bounds of the array. + * + * @param[in] ar the array from which the element is being retrieved + * @param[in] index the index of the array element + * @param[out] out pointer to where the element is stored + * + * @return CC_OK if the element was found, or CC_ERR_OUT_OF_RANGE if the index + * was out of range. + */ +enum cc_stat cc_array_get_at(const CC_Array *ar, size_t index, void **out) { + if (index >= ar->size) { + return CC_ERR_OUT_OF_RANGE; + } + + *out = ar->buffer[index]; + return CC_OK; +} + +/** + * Gets the last element of the array or the element at the highest index + * and sets the out parameter to its value. + * + * @param[in] ar the array whose last element is being returned + * @param[out] out pointer to where the element is stored + * + * @return CC_OK if the element was found, or CC_ERR_VALUE_NOT_FOUND if the + * CC_Array is empty. + */ +enum cc_stat cc_array_get_last(const CC_Array *ar, void **out) { + if (ar->size == 0) { + return CC_ERR_VALUE_NOT_FOUND; + } + + return cc_array_get_at(ar, ar->size - 1, out); +} + +/** + * Returns the underlying array buffer. + * + * @note Any direct modification of the buffer may invalidate the CC_Array. + * + * @param[in] ar array whose underlying buffer is being returned + * + * @return array's internal buffer. + */ +const void *const *cc_array_get_buffer(CC_Array *ar) { + return (const void *const *)ar->buffer; +} + +/** + * Gets the index of the specified element. The returned index is the index + * of the first occurrence of the element starting from the beginning of the + * CC_Array. + * + * @param[in] ar array being searched + * @param[in] element the element whose index is being looked up + * @param[out] index pointer to where the index is stored + * + * @return CC_OK if the index was found, or CC_OUT_OF_RANGE if not. + */ +enum cc_stat cc_array_index_of(const CC_Array *ar, void *element, size_t *index) { + size_t i; + for (i = 0; i < ar->size; i++) { + if (ar->buffer[i] == element) { + *index = i; + return CC_OK; + } + } + return CC_ERR_OUT_OF_RANGE; +} + +/** + * Creates a subarray of the specified CC_Array, ranging from b + * index (inclusive) to e index (inclusive). The range indices + * must be within the bounds of the CC_Array, while the e index + * must be greater or equal to the b index. + * + * @note The new CC_Array is allocated using the original CC_Array's allocators + * and it also inherits the configuration of the original CC_Array. + * + * @param[in] ar array from which the subarray is being created + * @param[in] b the beginning index (inclusive) of the subarray that must be + * within the bounds of the array and must not exceed the + * the end index + * @param[in] e the end index (inclusive) of the subarray that must be within + * the bounds of the array and must be greater or equal to the + * beginning index + * @param[out] out pointer to where the new sublist is stored + * + * @return CC_OK if the subarray was successfully created, CC_ERR_INVALID_RANGE + * if the specified index range is invalid, or CC_ERR_ALLOC if the memory allocation + * for the new subarray failed. + */ +enum cc_stat cc_array_subarray(CC_Array *ar, size_t b, size_t e, CC_Array **out) { + if (b > e || e >= ar->size) { + return CC_ERR_INVALID_RANGE; + } + + CC_Array *sub_ar = (CC_Array *)ar->mem_calloc(1, sizeof(CC_Array)); + + if (!sub_ar) { + return CC_ERR_ALLOC; + } + + /* Try to allocate the buffer */ + if (!(sub_ar->buffer = (void **)ar->mem_alloc(ar->capacity * sizeof(void *)))) { + ar->mem_free(sub_ar); + return CC_ERR_ALLOC; + } + + sub_ar->mem_alloc = ar->mem_alloc; + sub_ar->mem_calloc = ar->mem_calloc; + sub_ar->mem_free = ar->mem_free; + sub_ar->size = e - b + 1; + sub_ar->capacity = sub_ar->size; + + memcpy(sub_ar->buffer, + &(ar->buffer[b]), + sub_ar->size * sizeof(void *)); + + *out = sub_ar; + return CC_OK; +} + +/** + * Creates a shallow copy of the specified CC_Array. A shallow copy is a copy of + * the CC_Array structure, but not the elements it holds. + * + * @note The new CC_Array is allocated using the original CC_Array's allocators + * and it also inherits the configuration of the original array. + * + * @param[in] ar the array to be copied + * @param[out] out pointer to where the newly created copy is stored + * + * @return CC_OK if the copy was successfully created, or CC_ERR_ALLOC if the + * memory allocation for the copy failed. + */ +enum cc_stat cc_array_copy_shallow(CC_Array *ar, CC_Array **out) { + CC_Array *copy = (CC_Array *)ar->mem_alloc(sizeof(CC_Array)); + + if (!copy) { + return CC_ERR_ALLOC; + } + + if (!(copy->buffer = (void **)ar->mem_calloc(ar->capacity, sizeof(void *)))) { + ar->mem_free(copy); + return CC_ERR_ALLOC; + } + copy->exp_factor = ar->exp_factor; + copy->size = ar->size; + copy->capacity = ar->capacity; + copy->mem_alloc = ar->mem_alloc; + copy->mem_calloc = ar->mem_calloc; + copy->mem_free = ar->mem_free; + + memcpy(copy->buffer, + ar->buffer, + copy->size * sizeof(void *)); + + *out = copy; + return CC_OK; +} + +/** + * Creates a deep copy of the specified CC_Array. A deep copy is a copy of + * both the CC_Array structure and the data it holds. + * + * @note The new CC_Array is allocated using the original CC_Array's allocators + * and it also inherits the configuration of the original CC_Array. + * + * @param[in] ar array to be copied + * @param[in] cp the copy function that should return a pointer to the copy of + * the data + * @param[out] out pointer to where the newly created copy is stored + * + * @return CC_OK if the copy was successfully created, or CC_ERR_ALLOC if the + * memory allocation for the copy failed. + */ +enum cc_stat cc_array_copy_deep(CC_Array *ar, void *(*cp)(void *), CC_Array **out) { + CC_Array *copy = (CC_Array *)ar->mem_alloc(sizeof(CC_Array)); + + if (!copy) { + return CC_ERR_ALLOC; + } + + if (!(copy->buffer = (void **)ar->mem_calloc(ar->capacity, sizeof(void *)))) { + ar->mem_free(copy); + return CC_ERR_ALLOC; + } + + copy->exp_factor = ar->exp_factor; + copy->size = ar->size; + copy->capacity = ar->capacity; + copy->mem_alloc = ar->mem_alloc; + copy->mem_calloc = ar->mem_calloc; + copy->mem_free = ar->mem_free; + + size_t i; + for (i = 0; i < copy->size; i++) { + copy->buffer[i] = cp(ar->buffer[i]); + } + + *out = copy; + + return CC_OK; +} + +/** + * Filters the CC_Array by modifying it. It removes all elements that don't + * return true on pred(element). + * + * @param[in] ar array that is to be filtered + * @param[in] pred predicate function which returns true if the element should + * be kept in the CC_Array + * + * @return CC_OK if the CC_Array was filtered successfully, or CC_ERR_OUT_OF_RANGE + * if the CC_Array is empty. + */ +enum cc_stat cc_array_filter_mut(CC_Array *ar, bool (*pred)(const void *)) { + if (ar->size == 0) { + return CC_ERR_OUT_OF_RANGE; + } + + size_t rm = 0; + size_t keep = 0; + + /* Look for clusters of non matching elements before moving + * in order to minimize the number of memmoves */ + for (size_t i = ar->size - 1; i != ((size_t)-1); i--) { + if (!pred(ar->buffer[i])) { + rm++; + continue; + } + if (rm > 0) { + if (keep > 0) { + size_t block_size = keep * sizeof(void *); + memmove(&(ar->buffer[i + 1]), + &(ar->buffer[i + 1 + rm]), + block_size); + } + ar->size -= rm; + rm = 0; + } + keep++; + } + /* Remove any remaining elements*/ + if (rm > 0) { + size_t block_size = keep * sizeof(void *); + memmove(&(ar->buffer[0]), + &(ar->buffer[rm]), + block_size); + + ar->size -= rm; + } + return CC_OK; +} + +/** + * Filters the CC_Array by creating a new CC_Array that contains all elements from the + * original CC_Array that return true on pred(element) without modifying the original + * CC_Array. + * + * @param[in] ar array that is to be filtered + * @param[in] pred predicate function which returns true if the element should + * be kept in the filtered array + * @param[out] out pointer to where the new filtered CC_Array is to be stored + * + * @return CC_OK if the CC_Array was filtered successfully, CC_ERR_OUT_OF_RANGE + * if the CC_Array is empty, or CC_ERR_ALLOC if the memory allocation for the + * new CC_Array failed. + */ +enum cc_stat cc_array_filter(CC_Array *ar, bool (*pred)(const void *), CC_Array **out) { + if (ar->size == 0) { + return CC_ERR_OUT_OF_RANGE; + } + + CC_Array *filtered = (CC_Array *)ar->mem_alloc(sizeof(CC_Array)); + + if (!filtered) { + return CC_ERR_ALLOC; + } + + if (!(filtered->buffer = (void **)ar->mem_calloc(ar->capacity, sizeof(void *)))) { + ar->mem_free(filtered); + return CC_ERR_ALLOC; + } + + filtered->exp_factor = ar->exp_factor; + filtered->size = 0; + filtered->capacity = ar->capacity; + filtered->mem_alloc = ar->mem_alloc; + filtered->mem_calloc = ar->mem_calloc; + filtered->mem_free = ar->mem_free; + + size_t f = 0; + for (size_t i = 0; i < ar->size; i++) { + if (pred(ar->buffer[i])) { + filtered->buffer[f++] = ar->buffer[i]; + filtered->size++; + } + } + *out = filtered; + + return CC_OK; +} + +/** + * Reverses the order of elements in the specified array. + * + * @param[in] ar array that is being reversed + */ +void cc_array_reverse(CC_Array *ar) { + if (ar->size == 0) { + return; + } + + size_t i; + size_t j; + for (i = 0, j = ar->size - 1; i < ar->size / 2; i++, j--) { + void *tmp = ar->buffer[i]; + ar->buffer[i] = ar->buffer[j]; + ar->buffer[j] = tmp; + } +} + +/** + * Trims the array's capacity, in other words, it shrinks the capacity to match + * the number of elements in the CC_Array, however the capacity will never shrink + * below 1. + * + * @param[in] ar array whose capacity is being trimmed + * + * @return CC_OK if the capacity was trimmed successfully, or CC_ERR_ALLOC if + * the reallocation failed. + */ +enum cc_stat cc_array_trim_capacity(CC_Array *ar) { + if (ar->size == ar->capacity) { + return CC_OK; + } + + void **new_buff = (void **)ar->mem_calloc(ar->size, sizeof(void *)); + + if (!new_buff) { + return CC_ERR_ALLOC; + } + + size_t size = ar->size < 1 ? 1 : ar->size; + + memcpy(new_buff, ar->buffer, size * sizeof(void *)); + ar->mem_free(ar->buffer); + + ar->buffer = new_buff; + ar->capacity = ar->size; + + return CC_OK; +} + +/** + * Returns the number of occurrences of the element within the specified CC_Array. + * + * @param[in] ar array that is being searched + * @param[in] element the element that is being searched for + * + * @return the number of occurrences of the element. + */ +size_t cc_array_contains(const CC_Array *ar, void *element) { + size_t o = 0; + size_t i; + for (i = 0; i < ar->size; i++) { + if (ar->buffer[i] == element) { + o++; + } + } + return o; +} + +/** + * Returns the number of occurrences of the value pointed to by e + * within the specified CC_Array. + * + * @param[in] ar array that is being searched + * @param[in] element the element that is being searched for + * @param[in] cmp comparator function which returns 0 if the values passed to it are equal + * + * @return the number of occurrences of the value. + */ +size_t cc_array_contains_value(const CC_Array *ar, void *element, int (*cmp)(const void *, const void *)) { + size_t o = 0; + size_t i; + for (i = 0; i < ar->size; i++) { + if (cmp(element, ar->buffer[i]) == 0) { + o++; + } + } + return o; +} + +/** + * Returns the size of the specified CC_Array. The size of the array is the + * number of elements contained within the CC_Array. + * + * @param[in] ar array whose size is being returned + * + * @return the the number of element within the CC_Array. + */ +size_t cc_array_size(const CC_Array *ar) { + return ar->size; +} + +/** + * Returns the capacity of the specified CC_Array. The capacity of the CC_Array is + * the maximum number of elements an CC_Array can hold before it has to be resized. + * + * @param[in] ar array whose capacity is being returned + * + * @return the capacity of the CC_Array. + */ +size_t cc_array_capacity(const CC_Array *ar) { + return ar->capacity; +} + +/** + * Sorts the specified array. + * + * @note + * Pointers passed to the comparator function will be pointers to the array + * elements that are of type (void*) ie. void**. So an extra step of + * dereferencing will be required before the data can be used for comparison: + * eg. my_type e = *(*((my_type**) ptr));. + * + * @code + * enum cc_stat mycmp(const void *e1, const void *e2) { + * MyType el1 = *(*((enum cc_stat**) e1)); + * MyType el2 = *(*((enum cc_stat**) e2)); + * + * if (el1 < el2) return -1; + * if (el1 > el2) return 1; + * return 0; + * } + * + * ... + * + * cc_array_sort(array, mycmp); + * @endcode + * + * @param[in] ar array to be sorted + * @param[in] cmp the comparator function that must be of type + * enum cc_stat cmp(const void e1*, const void e2*) that + * returns < 0 if the first element goes before the second, + * 0 if the elements are equal and > 0 if the second goes + * before the first + */ +void cc_array_sort(CC_Array *ar, int (*cmp)(const void *, const void *)) { + qsort(ar->buffer, ar->size, sizeof(void *), cmp); +} + +/** + * Expands the CC_Array capacity. This might fail if the the new buffer + * cannot be allocated. In case the expansion would overflow the index + * range, a maximum capacity buffer is allocated instead. If the capacity + * is already at the maximum capacity, no new buffer is allocated. + * + * @param[in] ar array whose capacity is being expanded + * + * @return CC_OK if the buffer was expanded successfully, CC_ERR_ALLOC if + * the memory allocation for the new buffer failed, or CC_ERR_MAX_CAPACITY + * if the array is already at maximum capacity. + */ +static enum cc_stat expand_array_capacity(CC_Array *ar) { + if (ar->capacity == CC_MAX_ELEMENTS) { + return CC_ERR_MAX_CAPACITY; + } + + size_t new_capacity = (size_t)(ar->capacity * ar->exp_factor); + + /* As long as the capacity is greater that the expansion factor + * at the point of overflow, this is check is valid. */ + if (new_capacity <= ar->capacity) { + ar->capacity = CC_MAX_ELEMENTS; + } else { + ar->capacity = new_capacity; + } + + void **new_buff = (void **)ar->mem_alloc(ar->capacity * sizeof(void *)); + + if (!new_buff) { + return CC_ERR_ALLOC; + } + + memcpy(new_buff, ar->buffer, ar->size * sizeof(void *)); + + ar->mem_free(ar->buffer); + ar->buffer = new_buff; + + return CC_OK; +} + +/** + * Applies the function fn to each element of the CC_Array. + * + * @param[in] ar array on which this operation is performed + * @param[in] fn operation function that is to be invoked on each CC_Array + * element + */ +void cc_array_map(CC_Array *ar, void (*fn)(void *e)) { + size_t i; + for (i = 0; i < ar->size; i++) { + fn(ar->buffer[i]); + } +} + +/** + * A fold/reduce function that collects all of the elements in the array + * together. For example, if we have an array of [a,b,c...] the end result + * will be (...((a+b)+c)+...). + * + * @param[in] ar the array on which this operation is performed + * @param[in] fn the operation function that is to be invoked on each array + * element + * @param[in] result the pointer which will collect the end result + */ +void cc_array_reduce(CC_Array *ar, void (*fn)(void *, void *, void *), void *result) { + if (ar->size == 1) { + fn(ar->buffer[0], NULL, result); + return; + } + if (ar->size > 1) { + fn(ar->buffer[0], ar->buffer[1], result); + } + + for (size_t i = 2; i < ar->size; i++) { + fn(result, ar->buffer[i], result); + } +} + +/** + * Initializes the iterator. + * + * @param[in] iter the iterator that is being initialized + * @param[in] ar the array to iterate over + */ +void cc_array_iter_init(CC_ArrayIter *iter, CC_Array *ar) { + iter->ar = ar; + iter->index = 0; + iter->last_removed = false; +} + +/** + * Advances the iterator and sets the out parameter to the value of the + * next element in the sequence. + * + * @param[in] iter the iterator that is being advanced + * @param[out] out pointer to where the next element is set + * + * @return CC_OK if the iterator was advanced, or CC_ITER_END if the + * end of the CC_Array has been reached. + */ +enum cc_stat cc_array_iter_next(CC_ArrayIter *iter, void **out) { + if (iter->index >= iter->ar->size) { + return CC_ITER_END; + } + + *out = iter->ar->buffer[iter->index]; + + iter->index++; + iter->last_removed = false; + + return CC_OK; +} + +/** + * Removes the last returned element by cc_array_iter_next() + * function without invalidating the iterator and optionally sets the out + * parameter to the value of the removed element. + * + * @note This function should only ever be called after a call to + * cc_array_iter_next(). + + * @param[in] iter the iterator on which this operation is being performed + * @param[out] out pointer to where the removed element is stored, or NULL + * if it is to be ignored + * + * @return CC_OK if the element was successfully removed, or + * CC_ERR_VALUE_NOT_FOUND. + */ +enum cc_stat cc_array_iter_remove(CC_ArrayIter *iter, void **out) { + enum cc_stat status = CC_ERR_VALUE_NOT_FOUND; + + if (!iter->last_removed) { + status = cc_array_remove_at(iter->ar, iter->index - 1, out); + if (status != CC_OK) { + return status; + } + + iter->last_removed = true; + if (iter->index > 0) { + iter->index--; + } + } + return status; +} + +/** + * Removes the last returned element by cc_array_iter_next() + * function without invalidating the iterator and optionally sets the out + * parameter to the value of the removed element. The order of the array + * is not preserved, the last element of the array is moved to the index + * of the last returned element and the last element is removed. + * + * @note This function should only ever be called after a call to + * cc_array_iter_next(). + + * @param[in] iter the iterator on which this operation is being performed + * @param[out] out pointer to where the removed element is stored, or NULL + * if it is to be ignored + * + * @return CC_OK if the element was successfully removed, or + * CC_ERR_VALUE_NOT_FOUND. + */ +enum cc_stat cc_array_iter_remove_fast(CC_ArrayIter *iter, void **out) { + enum cc_stat status = CC_ERR_VALUE_NOT_FOUND; + + if (!iter->last_removed) { + status = cc_array_remove_fast_at(iter->ar, iter->index - 1, out); + if (status != CC_OK) { + return status; + } + + iter->last_removed = true; + if (iter->index > 0) { + iter->index--; + } + } + return status; +} + +/** + * Adds a new element to the CC_Array after the last returned element by + * cc_array_iter_next() function without invalidating the + * iterator. + * + * @note This function should only ever be called after a call to + * cc_array_iter_next(). + * + * @param[in] iter the iterator on which this operation is being performed + * @param[in] element the element being added + * + * @return CC_OK if the element was successfully added, CC_ERR_ALLOC if the + * memory allocation for the new element failed, or CC_ERR_MAX_CAPACITY if + * the array is already at maximum capacity. + */ +enum cc_stat cc_array_iter_add(CC_ArrayIter *iter, void *element) { + return cc_array_add_at(iter->ar, element, iter->index++); +} + +/** + * Replaces the last returned element by cc_array_iter_next() + * with the specified element and optionally sets the out parameter to + * the value of the replaced element. + * + * @note This function should only ever be called after a call to + * cc_array_iter_next(). + * + * @param[in] iter the iterator on which this operation is being performed + * @param[in] element the replacement element + * @param[out] out pointer to where the replaced element is stored, or NULL + * if it is to be ignored + * + * @return CC_OK if the element was replaced successfully, or + * CC_ERR_OUT_OF_RANGE. + */ +enum cc_stat cc_array_iter_replace(CC_ArrayIter *iter, void *element, void **out) { + return cc_array_replace_at(iter->ar, element, iter->index - 1, out); +} + +/** + * Returns the index of the last returned element by cc_array_iter_next() + * . + * + * @note + * This function should not be called before a call to cc_array_iter_next() + * . + * + * @param[in] iter the iterator on which this operation is being performed + * + * @return the index. + */ +size_t cc_array_iter_index(CC_ArrayIter *iter) { + return iter->index - 1; +} + +/** + * Initializes the zip iterator. + * + * @param[in] iter iterator that is being initialized + * @param[in] ar1 first array + * @param[in] ar2 second array + */ +void cc_array_zip_iter_init(CC_ArrayZipIter *iter, CC_Array *ar1, CC_Array *ar2) { + iter->ar1 = ar1; + iter->ar2 = ar2; + iter->index = 0; + iter->last_removed = false; +} + +/** + * Outputs the next element pair in the sequence and advances the iterator. + * + * @param[in] iter iterator that is being advanced + * @param[out] out1 output of the first array element + * @param[out] out2 output of the second array element + * + * @return CC_OK if a next element pair is returned, or CC_ITER_END if the end of one + * of the arrays has been reached. + */ +enum cc_stat cc_array_zip_iter_next(CC_ArrayZipIter *iter, void **out1, void **out2) { + if (iter->index >= iter->ar1->size || iter->index >= iter->ar2->size) { + return CC_ITER_END; + } + + *out1 = iter->ar1->buffer[iter->index]; + *out2 = iter->ar2->buffer[iter->index]; + + iter->index++; + iter->last_removed = false; + + return CC_OK; +} + +/** + * Removes and outputs the last returned element pair by cc_array_zip_iter_next() + * without invalidating the iterator. + * + * @param[in] iter iterator on which this operation is being performed + * @param[out] out1 output of the removed element from the first array + * @param[out] out2 output of the removed element from the second array + * + * @return CC_OK if the element was successfully removed, CC_ERR_OUT_OF_RANGE if the + * state of the iterator is invalid, or CC_ERR_VALUE_NOT_FOUND if the element was + * already removed. + */ +enum cc_stat cc_array_zip_iter_remove(CC_ArrayZipIter *iter, void **out1, void **out2) { + if ((iter->index - 1) >= iter->ar1->size || (iter->index - 1) >= iter->ar2->size) { + return CC_ERR_OUT_OF_RANGE; + } + + if (!iter->last_removed) { + cc_array_remove_at(iter->ar1, iter->index - 1, out1); + cc_array_remove_at(iter->ar2, iter->index - 1, out2); + iter->last_removed = true; + return CC_OK; + } + return CC_ERR_VALUE_NOT_FOUND; +} + +/** + * Adds a new element pair to the arrays after the last returned element pair by + * cc_array_zip_iter_next() and immediately before an element pair + * that would be returned by a subsequent call to cc_array_zip_iter_next() + * without invalidating the iterator. + * + * @param[in] iter iterator on which this operation is being performed + * @param[in] e1 element added to the first array + * @param[in] e2 element added to the second array + * + * @return CC_OK if the element pair was successfully added to the arrays, or + * CC_ERR_ALLOC if the memory allocation for the new elements failed. + */ +enum cc_stat cc_array_zip_iter_add(CC_ArrayZipIter *iter, void *e1, void *e2) { + size_t index = iter->index++; + CC_Array *ar1 = iter->ar1; + CC_Array *ar2 = iter->ar2; + + /* Make sure both array buffers have room */ + if ((ar1->size == ar1->capacity && (expand_array_capacity(ar1) != CC_OK)) || + (ar2->size == ar2->capacity && (expand_array_capacity(ar2) != CC_OK))) { + return CC_ERR_ALLOC; + } + + cc_array_add_at(ar1, e1, index); + cc_array_add_at(ar2, e2, index); + + return CC_OK; +} + +/** + * Replaces the last returned element pair by cc_array_zip_iter_next() + * with the specified replacement element pair. + * + * @param[in] iter iterator on which this operation is being performed + * @param[in] e1 first array's replacement element + * @param[in] e2 second array's replacement element + * @param[out] out1 output of the replaced element from the first array + * @param[out] out2 output of the replaced element from the second array + * + * @return CC_OK if the element was successfully replaced, or CC_ERR_OUT_OF_RANGE. + */ +enum cc_stat cc_array_zip_iter_replace(CC_ArrayZipIter *iter, void *e1, void *e2, void **out1, void **out2) { + if ((iter->index - 1) >= iter->ar1->size || (iter->index - 1) >= iter->ar2->size) { + return CC_ERR_OUT_OF_RANGE; + } + + cc_array_replace_at(iter->ar1, e1, iter->index - 1, out1); + cc_array_replace_at(iter->ar2, e2, iter->index - 1, out2); + + return CC_OK; +} + +/** + * Returns the index of the last returned element pair by cc_array_zip_iter_next(). + * + * @param[in] iter iterator on which this operation is being performed + * + * @return current iterator index. + */ +size_t cc_array_zip_iter_index(CC_ArrayZipIter *iter) { + return iter->index - 1; +} + +size_t cc_array_struct_size() { + return sizeof(CC_Array); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/vendor/cc_common.h b/vendor/cc_common.h new file mode 100644 index 0000000000..1740460646 --- /dev/null +++ b/vendor/cc_common.h @@ -0,0 +1,75 @@ +/* + * Collections-C + * Copyright (C) 2013-2014 Srđan Panić + * + * This file is part of Collections-C. + * + * Collections-C is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Collections-C is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Collections-C. If not, see . + */ + +#ifndef CC_COMMON_H +#define CC_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +#ifdef ARCH_64 +#define MAX_POW_TWO (((size_t)1) << 63) +#else +#define MAX_POW_TWO (((size_t)1) << 31) +#endif /* ARCH_64 */ + +enum cc_stat { + CC_OK = 0, + + CC_ERR_ALLOC = 1, + CC_ERR_INVALID_CAPACITY = 2, + CC_ERR_INVALID_RANGE = 3, + CC_ERR_MAX_CAPACITY = 4, + CC_ERR_KEY_NOT_FOUND = 6, + CC_ERR_VALUE_NOT_FOUND = 7, + CC_ERR_OUT_OF_RANGE = 8, + + CC_ITER_END = 9, +}; + +#define CC_MAX_ELEMENTS ((size_t) - 2) + +#if defined(_MSC_VER) + +#define INLINE __inline +#define FORCE_INLINE __forceinline + +#else + +#define INLINE inline +#define FORCE_INLINE inline __attribute__((always_inline)) + +#endif /* _MSC_VER */ + +int cc_common_cmp_str(const void *key1, const void *key2); + +#define CC_CMP_STRING cc_common_cmp_str + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/vendor/rlights.h b/vendor/rlights.h index aba907b5fb..cee06360ee 100644 --- a/vendor/rlights.h +++ b/vendor/rlights.h @@ -167,4 +167,4 @@ void UpdateLightValues(Shader shader, Light light) SetShaderValue(shader, light.colorLoc, color, SHADER_UNIFORM_VEC4); } -#endif // RLIGHTS_IMPLEMENTATION \ No newline at end of file +#endif // RLIGHTS_IMPLEMENTATION