diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ee10587..ff4ff41a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.22) project(Kinoko CXX) # Compiler and flags @@ -8,10 +8,10 @@ set(CMAKE_CXX_EXTENSIONS OFF) # Common flags set(COMMON_CXX_FLAGS + -fPIC -DREVOLUTION -fno-asynchronous-unwind-tables -fno-exceptions - -fno-rtti -fshort-wchar -fstack-protector-strong -Wall @@ -22,14 +22,32 @@ set(COMMON_CXX_FLAGS -Wsuggest-override ) +# Bindings flags +set(BINDINGS_CXX_FLAGS + -DREVOLUTION + -fstack-protector-strong + -Wall + -Werror + -Wextra + -Wno-delete-non-virtual-dtor + -Wno-packed-bitfield-compat + -Wsuggest-override +) + set(RK_INCLUDE_DIRS include source ) + +# +-------------------------------------------+ +# - LIBKINOKO - +# +-------------------------------------------+ + # Source files file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/**/*.cc) list(FILTER SOURCE_FILES EXCLUDE REGEX ".*/host/main\\.cc$") +list(FILTER SOURCE_FILES EXCLUDE REGEX ".*/host/KBind.*\\.cc$") add_library(libkinoko ${SOURCE_FILES}) target_include_directories(libkinoko SYSTEM @@ -43,13 +61,42 @@ add_executable(kinoko source/host/main.cc) target_link_libraries(kinoko libkinoko) target_compile_options(kinoko PRIVATE ${COMMON_CXX_FLAGS}) +# +-------------------------------------------+ +# - BINDINGS - +# +-------------------------------------------+ + +# Include Python +find_package(Python REQUIRED COMPONENTS Development) + +# Fetch Nanobind +include(FetchContent) +FetchContent_Declare( + nanobind + GIT_REPOSITORY https://github.com/wjakob/nanobind.git +) +FetchContent_MakeAvailable(nanobind) + +# Add a target +nanobind_add_module(bindings + source/host/KBindSystem.cc + source/host/KBindings.cc +) + +# Link libraries +target_link_libraries(bindings PRIVATE libkinoko Python::Python) +target_compile_options(bindings PRIVATE ${BINDINGS_CXX_FLAGS}) + +# +-------------------------------------------+ +# - TEST CASES - +# +-------------------------------------------+ + # Add a custom target to generate testCases.json set(TEST_JSON ${CMAKE_CURRENT_SOURCE_DIR}/testCases.json) set(TEST_BIN ${CMAKE_CURRENT_BINARY_DIR}/testCases.bin) add_custom_command( OUTPUT ${TEST_BIN} # The file generated by this rule DEPENDS ${TEST_JSON} # Dependency - COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_tests.py ${TEST_JSON} ${TEST_BIN} + COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_tests.py ${TEST_JSON} ${TEST_BIN} COMMENT "Running generate_tests.py to create test cases" ) add_custom_target( diff --git a/configure.py b/configure.py index 3148e8bd..2bbaef58 100755 --- a/configure.py +++ b/configure.py @@ -1,154 +1,188 @@ #!/usr/bin/env python3 -from glob import glob -import io +import glob import os +import re +import io import sys -from tools.generate_tests import generate_tests +import sysconfig +import subprocess +from dataclasses import dataclass, field from vendor.ninja_syntax import Writer +from tools.generate_tests import generate_tests generate_tests() +ON_WINDOWS = True if sys.platform.startswith("win32") else False + +# Ninja variables and rules out_buf = io.StringIO() n = Writer(out_buf) -file_extension = '' -if sys.platform.startswith('win32'): - file_extension = '.exe' +build_dir = "build" -n.variable('ninja_required_version', '1.3') +n.variable("ninja_required_version", "1.3") +n.variable("builddir", build_dir) +n.variable("outdir", "out") +n.variable("compiler", "g++") +n.variable("ar", "ar") n.newline() - -n.variable('builddir', 'build') -n.variable('outdir', 'out') +n.rule("cc", command="$compiler -MD -MT $out -MF $out.d $ccflags -c $in -o $out", depfile="$out.d", deps="gcc", description="CC $out") +n.rule("ar", command="$ar rcs $out $in", description="AR $out") n.newline() - -n.variable('compiler', 'g++') +n.rule("ld", command="$compiler $in $ldflags -o $out", description="LD $out") +n.newline() +n.rule("ld_shared", command="$compiler -shared $in $ldflags -o $out", description="LD_SHARED $out") n.newline() +# Download dependancies +deps_path = os.path.join(build_dir, "_deps") +nanobind_path = os.path.join(deps_path, "nanobind") +robin_path = os.path.join(deps_path, "robin-map") + +os.makedirs(deps_path, exist_ok=True) + +if not os.path.isdir(nanobind_path): + subprocess.run(["git", "clone", "https://github.com/wjakob/nanobind", nanobind_path], check=True) + +if not os.path.isdir(robin_path): + subprocess.run(["git", "clone", "https://github.com/Tessil/robin-map.git", robin_path], check=True) + +python_include_path = sysconfig.get_paths()["include"] +python_lib_path = os.path.join(sysconfig.get_paths()["data"], "libs", f"libpython{sys.version_info.major}.{sys.version_info.minor}.a") +nanobind_include_path = os.path.join(nanobind_path, "include") +nanobind_src_path = os.path.join(nanobind_path, "src") +robin_include_path = os.path.join(robin_path, "include") + +# Flags common_ccflags = [ '-DREVOLUTION', '-fno-asynchronous-unwind-tables', '-fno-exceptions', - '-fno-rtti', '-fshort-wchar', '-fstack-protector-strong', - '-isystem', '.', '-isystem', 'include', '-isystem', 'source', - '-isystem', 'vendor', - '-isystem', 'build', - '-std=c++23', '-Wall', - '-Wdouble-promotion', '-Werror', '-Wextra', '-Wno-delete-non-virtual-dtor', '-Wno-packed-bitfield-compat', '-Wsuggest-override', + '-std=c++23', + '-fPIC', + '-fno-exceptions', ] -target_cflags = [ - '-O3', +bindings_ccflags = [ + '-DREVOLUTION', + '-fstack-protector-strong', + '-isystem', 'include', + '-isystem', 'source', + '-isystem', python_include_path, + '-isystem', nanobind_include_path, + '-isystem', robin_include_path, + '-Wall', + '-Werror', + '-Wextra', + '-Wno-format', + '-Wno-delete-non-virtual-dtor', + '-Wno-packed-bitfield-compat', + '-Wsuggest-override', + '-std=c++23', + '-fPIC', ] -debug_cflags = [ - '-DBUILD_DEBUG', - '-O0', - '-ggdb', +# Define build types +@dataclass +class Build: + name: str + suffix: str + flags: list[str] + obj_files: dict = field(default_factory=lambda: { + "common": [], + "main": [], + "bindings": [] + }) + +builds = [ + Build(name="target", suffix="", flags=["-O3"]), + Build(name="debug", suffix="D", flags=["-DBUILD_DEBUG", "-O0", "-ggdb"]) ] -common_ldflags = [] +# Compile files +def get_files(): + sep = re.escape(os.path.sep) + main_regex = re.compile(rf".*{sep}host{sep}main\.cc$") + bindings_regex = re.compile(rf".*{sep}host{sep}KBind.*\.cc$") -n.rule( - 'cc', - command='$compiler -MD -MT $out -MF $out.d $ccflags -c $in -o $out', - depfile='$out.d', - deps='gcc', - description='CC $out', -) -n.newline() + main_files = [] + bindings_files = [] + common_files = [] -n.rule( - 'ld', - command='$compiler $ldflags $in -o $out', - description='LD $out', -) + for file in glob.glob(os.path.join("source", "**", "*.cc"), recursive=True): + if main_regex.search(file): + main_files.append(file) + continue -code_in_files = [file for file in glob('**/*.cc', recursive=True)] + if bindings_regex.search(file): + bindings_files.append(file) + continue -target_code_out_files = [] -debug_code_out_files = [] + common_files.append(file) -for in_file in code_in_files: - _, ext = os.path.splitext(in_file) + bindings_files.append(os.path.join(nanobind_src_path, "nb_combined.cpp")) - target_out_file = os.path.join('$builddir', in_file + '.o') - target_code_out_files.append(target_out_file) + return common_files, main_files, bindings_files - debug_out_file = os.path.join('$builddir', in_file + 'D.o') - debug_code_out_files.append(debug_out_file) +def compile(files, builds, obj_key, flags): + for in_file in files: + base_name, _ = os.path.splitext(in_file) + for build in builds: + out_file = os.path.join("$builddir", f"{base_name}{build.suffix}.o") + build.obj_files[obj_key].append(out_file) + n.build(out_file, "cc", in_file, variables={"ccflags": " ".join([*flags, *build.flags])}) - n.build( - target_out_file, - ext[1:], - in_file, - variables={ - 'ccflags': ' '.join([*common_ccflags, *target_cflags]) - } - ) - n.newline() +common_files, main_files, bindings_files = get_files() +compile(common_files, builds, "common", common_ccflags) +compile(main_files, builds, "main", common_ccflags) +compile(bindings_files, builds, "bindings", bindings_ccflags) +n.newline() - n.build( - debug_out_file, - ext[1:], - in_file, - variables={ - 'ccflags': ' '.join([*common_ccflags, *debug_cflags]) - } - ) +# Link files +binary_extension = ".exe" if ON_WINDOWS else "" +lib_extension = ".pyd" if ON_WINDOWS else ".so" + +for build in builds: + # libkinoko + libkinoko_name = f"libkinoko{build.suffix}.a" + libkinoko_path = os.path.join("$builddir", libkinoko_name) + libkinoko_objs = build.obj_files["common"] + n.build(libkinoko_path, "ar", libkinoko_objs) n.newline() + # kinoko + exe_name = f"kinoko{build.suffix}{binary_extension}" + exe_path = os.path.join("$outdir", exe_name) + exe_obj = build.obj_files["main"] + [libkinoko_path] + n.build(exe_path, "ld", exe_obj) + + # bindings + lib_name = f"bindings{build.suffix}{lib_extension}" + lib_path = os.path.join("$outdir", lib_name) + lib_objs = build.obj_files["bindings"] + [libkinoko_path] + + # Python library + if ON_WINDOWS: + n.build(lib_path, "ld_shared", lib_objs, variables={"ldflags": f"{sysconfig.get_config_var('LDFLAGS')} -L{os.path.dirname(python_lib_path)} -lpython{sys.version_info.major}.{sys.version_info.minor}"}) + else: + n.build(lib_path, "ld_shared", lib_objs, variables={"ldflags": f"{sysconfig.get_config_var('LDFLAGS')} -L{os.path.dirname(python_lib_path)}"}) + n.newline() -n.build( - os.path.join('$outdir', f'kinoko{file_extension}'), - 'ld', - target_code_out_files, - variables={ - 'ldflags': ' '.join([ - *common_ldflags, - ]) - }, -) - -n.build( - os.path.join('$outdir', f'kinokoD{file_extension}'), - 'ld', - debug_code_out_files, - variables={ - 'ldflags': ' '.join([ - *common_ldflags, - ]) - }, -) - -n.variable('configure', 'configure.py') +n.rule("configure", command=f"{sys.executable} configure.py", generator=True) +n.build("build.ninja", "configure", implicit=["configure.py", os.path.join("vendor", "ninja_syntax.py")]) n.newline() -n.rule( - 'configure', - command=f'{sys.executable} $configure', - generator=True, -) -n.build( - 'build.ninja', - 'configure', - implicit=[ - '$configure', - os.path.join('vendor', 'ninja_syntax.py'), - ], -) - -with open('build.ninja', 'w') as out_file: - out_file.write(out_buf.getvalue()) -n.close() +with open("build.ninja", "w") as f: + f.write(out_buf.getvalue()) +n.close() \ No newline at end of file diff --git a/source/egg/core/Heap.hh b/source/egg/core/Heap.hh index 25b73de4..41265653 100644 --- a/source/egg/core/Heap.hh +++ b/source/egg/core/Heap.hh @@ -128,11 +128,11 @@ protected: } // namespace EGG -[[nodiscard]] void *operator new(size_t size) noexcept; -[[nodiscard]] void *operator new(size_t size, int align) noexcept; -[[nodiscard]] void *operator new(size_t size, EGG::Heap *heap, int align) noexcept; -[[nodiscard]] void *operator new[](size_t size) noexcept; -[[nodiscard]] void *operator new[](size_t size, int align) noexcept; -[[nodiscard]] void *operator new[](size_t size, EGG::Heap *heap, int align) noexcept; +[[nodiscard]] void *operator new(size_t size); +[[nodiscard]] void *operator new(size_t size, int align); +[[nodiscard]] void *operator new(size_t size, EGG::Heap *heap, int align); +[[nodiscard]] void *operator new[](size_t size); +[[nodiscard]] void *operator new[](size_t size, int align); +[[nodiscard]] void *operator new[](size_t size, EGG::Heap *heap, int align); void operator delete(void *block) noexcept; void operator delete[](void *block) noexcept; diff --git a/source/game/scene/RaceScene.cc b/source/game/scene/RaceScene.cc index cd8f2b3e..1d1f1480 100644 --- a/source/game/scene/RaceScene.cc +++ b/source/game/scene/RaceScene.cc @@ -85,7 +85,7 @@ void RaceScene::initEngines() { Field::ObjectDirector::Instance()->init(); } - m_heap->disableAllocation(); + // m_heap->disableAllocation(); } /// @addr{0x80554E6C} diff --git a/source/game/system/RaceConfig.hh b/source/game/system/RaceConfig.hh index dc019006..e4c16d31 100644 --- a/source/game/system/RaceConfig.hh +++ b/source/game/system/RaceConfig.hh @@ -17,7 +17,7 @@ public: struct Player { public: enum class Type { - Local = 0, // Inputs managed by ML algorithm + Local = 0, // Inputs managed externally Ghost = 3, // Inputs managed by ghost None = 5, }; diff --git a/source/host/KBindSystem.cc b/source/host/KBindSystem.cc new file mode 100644 index 00000000..6080ec32 --- /dev/null +++ b/source/host/KBindSystem.cc @@ -0,0 +1,72 @@ +#include "KBindSystem.hh" + +#include "host/SceneCreatorDynamic.hh" + +#include +#include +#include + +#include +#include + +KBindSystem::KBindSystem() : m_sceneMgr(nullptr) {} + +KBindSystem::~KBindSystem() { + delete m_sceneMgr; +} + +KBindSystem *KBindSystem::CreateInstance() { + ASSERT(!s_instance); + s_instance = new KBindSystem; + return s_instance; +} + +void KBindSystem::DestroyInstance() { + ASSERT(s_instance); + delete s_instance; + s_instance = nullptr; +} + +void KBindSystem::init() { + // Create a new sceneCreator (needed to make RaceScene) + auto *sceneCreator = new Host::SceneCreatorDynamic; + m_sceneMgr = new EGG::SceneManager(sceneCreator); + + // Register a callback to configure RaceConfig once it's created + System::RaceConfig::RegisterInitCallback( + [this](System::RaceConfig *config, void * /* arg */) { + config->raceScenario() = m_scenario; + }, + nullptr); + + m_sceneMgr->changeScene(static_cast(Host::SceneId::Root)); +} + +void KBindSystem::calc() { + if (m_sceneMgr) { + m_sceneMgr->calc(); + } +} + +System::KPadHostController *KBindSystem::GetHostController() { + return System::KPadDirector::Instance()->hostController(); +} + +Kart::KartObjectProxy *KBindSystem::GetKart(int slot) { + return Kart::KartObjectManager::Instance()->object(slot); +} + +void KBindSystem::SetPlayer(int slot, Character character, Vehicle vehicle, bool driftIsAuto) { + if (slot < 0 || slot > 12) { + return; + } + + m_scenario.players[slot] = {character, vehicle, System::RaceConfig::Player::Type::Local, + driftIsAuto}; + + if (m_scenario.playerCount <= slot) { + m_scenario.playerCount = slot + 1; + } +} + +KBindSystem *KBindSystem::s_instance = nullptr; diff --git a/source/host/KBindSystem.hh b/source/host/KBindSystem.hh new file mode 100644 index 00000000..af76a1b5 --- /dev/null +++ b/source/host/KBindSystem.hh @@ -0,0 +1,58 @@ +#pragma once + +#include "Common.hh" + +#include "game/kart/KartObjectManager.hh" + +#include "game/system/KPadController.hh" +#include "game/system/RaceConfig.hh" + +#include + +#include + +class KBindSystem final : public KSystem { +public: + KBindSystem(); + ~KBindSystem() override; + + void init() override; + void calc() override; + bool run() override { + return true; + } + void parseOptions(int /*argc*/, char ** /*argv*/) override {} + + /// @brief Sets the course for the race. Must be called before init() + void SetCourse(Course course) { + m_scenario.course = course; + } + + /// @brief Configures a racer. Must be called before init() + void SetPlayer(int slot, Character character, Vehicle vehicle, bool driftIsAuto); + + /// @brief Gets the host controller for changing controller state + /// At the moment Kinoko only supports one racer, so this returns the controller for player 0 + /// @return A pointer to the KPadHostController for the local player + [[nodiscard]] static System::KPadHostController *GetHostController(); + + /// @brief Gets the KartObjectProxy instance for a specific slot + /// @return A pointer to the KartObjectInstance for the specified slot + [[nodiscard]] Kart::KartObjectProxy *GetKart(int slot); + + static KBindSystem *CreateInstance(); + + static void DestroyInstance(); + + [[nodiscard]] static KBindSystem *Instance() { + return s_instance; + } + +private: + KBindSystem(const KBindSystem &) = delete; + KBindSystem(KBindSystem &&) = delete; + + System::RaceConfig::Scenario m_scenario; + EGG::SceneManager *m_sceneMgr; + static KBindSystem *s_instance; +}; diff --git a/source/host/KBindings.cc b/source/host/KBindings.cc new file mode 100644 index 00000000..927282c3 --- /dev/null +++ b/source/host/KBindings.cc @@ -0,0 +1,245 @@ +#include "Common.hh" + +#include "game/system/KPadController.hh" + +#include "host/KBindSystem.hh" + +#include + +#include +#include +#include +#include + +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +static void InitMemory(); +static void FlushDenormalsToZero(); + +struct KinokoInitializer { + KinokoInitializer() { + FlushDenormalsToZero(); + InitMemory(); + } +}; +static KinokoInitializer s_KinokoInitializer_instance; + +// clang-format off +NB_MODULE(bindings, m) { + m.doc() = "Python bindings for Kinoko"; + + nb::enum_(m, "Character") + .value("Mario", Character::Mario) + .value("Baby_Peach", Character::Baby_Peach) + .value("Waluigi", Character::Waluigi) + .value("Bowser", Character::Bowser) + .value("Baby_Daisy", Character::Baby_Daisy) + .value("Dry_Bones", Character::Dry_Bones) + .value("Baby_Mario", Character::Baby_Mario) + .value("Luigi", Character::Luigi) + .value("Peach", Character::Peach) + .value("Yoshi", Character::Yoshi) + .value("Donkey_Kong", Character::Donkey_Kong) + .value("Wario", Character::Wario) + .value("Baby_Luigi", Character::Baby_Luigi) + .value("Toad", Character::Toad) + .value("Koopa_Troopa", Character::Koopa_Troopa) + .value("Daisy", Character::Daisy) + .value("Toadette", Character::Toadette) + .value("Birdo", Character::Birdo) + .value("Diddy_Kong", Character::Diddy_Kong) + .value("King_Boo", Character::King_Boo) + .value("Bowser_Jr", Character::Bowser_Jr) + .value("Dry_Bowser", Character::Dry_Bowser) + .value("Funky_Kong", Character::Funky_Kong) + .value("Rosalina", Character::Rosalina) + .value("Small_Mii_Outfit_A_Male", Character::Small_Mii_Outfit_A_Male) + .value("Small_Mii_Outfit_A_Female", Character::Small_Mii_Outfit_A_Female) + .value("Small_Mii_Outfit_B_Male", Character::Small_Mii_Outfit_B_Male) + .value("Small_Mii_Outfit_B_Female", Character::Small_Mii_Outfit_B_Female) + .value("Small_Mii_Outfit_C_Male", Character::Small_Mii_Outfit_C_Male) + .value("Small_Mii_Outfit_C_Female", Character::Small_Mii_Outfit_C_Female) + .value("Medium_Mii_Outfit_A_Male", Character::Medium_Mii_Outfit_A_Male) + .value("Medium_Mii_Outfit_A_Female", Character::Medium_Mii_Outfit_A_Female) + .value("Medium_Mii_Outfit_B_Male", Character::Medium_Mii_Outfit_B_Male) + .value("Medium_Mii_Outfit_B_Female", Character::Medium_Mii_Outfit_B_Female) + .value("Medium_Mii_Outfit_C_Male", Character::Medium_Mii_Outfit_C_Male) + .value("Medium_Mii_Outfit_C_Female", Character::Medium_Mii_Outfit_C_Female) + .value("Large_Mii_Outfit_A_Male", Character::Large_Mii_Outfit_A_Male) + .value("Large_Mii_Outfit_A_Female", Character::Large_Mii_Outfit_A_Female) + .value("Large_Mii_Outfit_B_Male", Character::Large_Mii_Outfit_B_Male) + .value("Large_Mii_Outfit_B_Female", Character::Large_Mii_Outfit_B_Female) + .value("Large_Mii_Outfit_C_Male", Character::Large_Mii_Outfit_C_Male) + .value("Large_Mii_Outfit_C_Female", Character::Large_Mii_Outfit_C_Female) + .value("Medium_Mii", Character::Medium_Mii) + .value("Small_Mii", Character::Small_Mii) + .value("Large_Mii", Character::Large_Mii) + .value("Max", Character::Max) + .export_values(); + + nb::enum_(m, "Vehicle") + .value("Standard_Kart_S", Vehicle::Standard_Kart_S) + .value("Standard_Kart_M", Vehicle::Standard_Kart_M) + .value("Standard_Kart_L", Vehicle::Standard_Kart_L) + .value("Baby_Booster", Vehicle::Baby_Booster) + .value("Classic_Dragster", Vehicle::Classic_Dragster) + .value("Offroader", Vehicle::Offroader) + .value("Mini_Beast", Vehicle::Mini_Beast) + .value("Wild_Wing", Vehicle::Wild_Wing) + .value("Flame_Flyer", Vehicle::Flame_Flyer) + .value("Cheep_Charger", Vehicle::Cheep_Charger) + .value("Super_Blooper", Vehicle::Super_Blooper) + .value("Piranha_Prowler", Vehicle::Piranha_Prowler) + .value("Tiny_Titan", Vehicle::Tiny_Titan) + .value("Daytripper", Vehicle::Daytripper) + .value("Jetsetter", Vehicle::Jetsetter) + .value("Blue_Falcon", Vehicle::Blue_Falcon) + .value("Sprinter", Vehicle::Sprinter) + .value("Honeycoupe", Vehicle::Honeycoupe) + .value("Standard_Bike_S", Vehicle::Standard_Bike_S) + .value("Standard_Bike_M", Vehicle::Standard_Bike_M) + .value("Standard_Bike_L", Vehicle::Standard_Bike_L) + .value("Bullet_Bike", Vehicle::Bullet_Bike) + .value("Mach_Bike", Vehicle::Mach_Bike) + .value("Flame_Runner", Vehicle::Flame_Runner) + .value("Bit_Bike", Vehicle::Bit_Bike) + .value("Sugarscoot", Vehicle::Sugarscoot) + .value("Wario_Bike", Vehicle::Wario_Bike) + .value("Quacker", Vehicle::Quacker) + .value("Zip_Zip", Vehicle::Zip_Zip) + .value("Shooting_Star", Vehicle::Shooting_Star) + .value("Magikruiser", Vehicle::Magikruiser) + .value("Sneakster", Vehicle::Sneakster) + .value("Spear", Vehicle::Spear) + .value("Jet_Bubble", Vehicle::Jet_Bubble) + .value("Dolphin_Dasher", Vehicle::Dolphin_Dasher) + .value("Phantom", Vehicle::Phantom) + .value("Max", Vehicle::Max) + .export_values(); + + nb::enum_(m, "Course") + .value("Luigi_Circuit", Course::Luigi_Circuit) + .value("Moo_Moo_Meadows", Course::Moo_Moo_Meadows) + .value("Mushroom_Gorge", Course::Mushroom_Gorge) + .value("Toads_Factory", Course::Toads_Factory) + .value("Mario_Circuit", Course::Mario_Circuit) + .value("Coconut_Mall", Course::Coconut_Mall) + .value("DK_Summit", Course::DK_Summit) + .value("Wario_Gold_Mine", Course::Wario_Gold_Mine) + .value("Daisy_Circuit", Course::Daisy_Circuit) + .value("Koopa_Cape", Course::Koopa_Cape) + .value("Maple_Treeway", Course::Maple_Treeway) + .value("Grumble_Volcano", Course::Grumble_Volcano) + .value("Dry_Dry_Ruins", Course::Dry_Dry_Ruins) + .value("Moonview_Highway", Course::Moonview_Highway) + .value("Bowsers_Castle", Course::Bowsers_Castle) + .value("Rainbow_Road", Course::Rainbow_Road) + .value("GCN_Peach_Beach", Course::GCN_Peach_Beach) + .value("DS_Yoshi_Falls", Course::DS_Yoshi_Falls) + .value("SNES_Ghost_Valley_2", Course::SNES_Ghost_Valley_2) + .value("N64_Mario_Raceway", Course::N64_Mario_Raceway) + .value("N64_Sherbet_Land", Course::N64_Sherbet_Land) + .value("GBA_Shy_Guy_Beach", Course::GBA_Shy_Guy_Beach) + .value("DS_Delfino_Square", Course::DS_Delfino_Square) + .value("GCN_Waluigi_Stadium", Course::GCN_Waluigi_Stadium) + .value("DS_Desert_Hills", Course::DS_Desert_Hills) + .value("GBA_Bowser_Castle_3", Course::GBA_Bowser_Castle_3) + .value("N64_DKs_Jungle_Parkway", Course::N64_DKs_Jungle_Parkway) + .value("GCN_Mario_Circuit", Course::GCN_Mario_Circuit) + .value("SNES_Mario_Circuit_3", Course::SNES_Mario_Circuit_3) + .value("DS_Peach_Gardens", Course::DS_Peach_Gardens) + .value("GCN_DK_Mountain", Course::GCN_DK_Mountain) + .value("N64_Bowsers_Castle", Course::N64_Bowsers_Castle) + .export_values(); + + nb::class_(m, "Vector3f") + .def(nb::init()) + .def_rw("x", &EGG::Vector3f::x) + .def_rw("y", &EGG::Vector3f::y) + .def_rw("z", &EGG::Vector3f::z); + + nb::class_(m, "Quatf") + .def(nb::init(), "w"_a, "x"_a, "y"_a, "z"_a) + .def_rw("v", &EGG::Quatf::v) + .def_rw("w", &EGG::Quatf::w); + + nb::class_(m, "KartObjectProxy") + .def("speed", &Kart::KartObjectProxy::speed) + .def("acceleration", &Kart::KartObjectProxy::acceleration) + .def("speedRatio", &Kart::KartObjectProxy::speedRatio) + .def("speedRatioCapped", &Kart::KartObjectProxy::speedRatioCapped) + .def("softSpeedLimit", &Kart::KartObjectProxy::softSpeedLimit) + .def("pos", &Kart::KartObjectProxy::pos, nb::rv_policy::reference_internal) + .def("extVel", &Kart::KartObjectProxy::extVel, nb::rv_policy::reference_internal) + .def("intVel", &Kart::KartObjectProxy::intVel, nb::rv_policy::reference_internal) + .def("velocity", &Kart::KartObjectProxy::velocity, nb::rv_policy::reference_internal) + .def("mainRot", &Kart::KartObjectProxy::mainRot, nb::rv_policy::reference_internal) + .def("fullRot", &Kart::KartObjectProxy::fullRot, nb::rv_policy::reference_internal); + + nb::enum_(m, "Trick") + .value("Neutral", System::Trick::None) // None is reserved + .value("Up", System::Trick::Up) + .value("Down", System::Trick::Down) + .value("Left", System::Trick::Left) + .value("Right", System::Trick::Right) + .export_values(); + + nb::class_(m, "KBindSystem") + .def(nb::init<>()) + .def("init", &KBindSystem::init) + .def("calc", &KBindSystem::calc) + .def("set_course", &KBindSystem::SetCourse, "course"_a) + .def("set_player", &KBindSystem::SetPlayer, "slot"_a, "character"_a, "vehicle"_a, "drift_is_auto"_a) + .def("get_kart", &KBindSystem::GetKart, nb::rv_policy::reference) + .def_static("get_host_controller", &KBindSystem::GetHostController, nb::rv_policy::reference) + .def_static("create_instance", &KBindSystem::CreateInstance, nb::rv_policy::reference) + .def_static("destroy_instance", &KBindSystem::DestroyInstance) + .def_static("instance", &KBindSystem::Instance, nb::rv_policy::reference); + + nb::class_(m, "KPadHostController") + .def("set_inputs", nb::overload_cast(&System::KPadHostController::setInputs), + "buttons"_a, "stick_x"_a, "stick_y"_a, "trick"_a) + .def("set_inputs_raw_stick", &System::KPadHostController::setInputsRawStick, + "buttons"_a, "stick_x_raw"_a, "stick_y_raw"_a, "trick"_a) + .def("set_inputs_raw_stick_zero_center", &System::KPadHostController::setInputsRawStickZeroCenter, + "buttons"_a, "stick_x_raw"_a, "stick_y_raw"_a, "trick"_a); +} +// clang-format on + +#if defined(__arm64__) || defined(__aarch64__) +static void FlushDenormalsToZero() { + uint64_t fpcr; + asm("mrs %0, fpcr" : "=r"(fpcr)); + asm("msr fpcr, %0" ::"r"(fpcr | (1 << 24))); +} +#elif defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) +#include + +static void FlushDenormalsToZero() { + _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); +} +#endif + +static void *s_memorySpace = nullptr; +static EGG::Heap *s_rootHeap = nullptr; + +static void InitMemory() { + constexpr size_t MEMORY_SPACE_SIZE = 0x8000000; // Arbitrary number + Abstract::Memory::MEMiHeapHead::OptFlag opt; + opt.setBit(Abstract::Memory::MEMiHeapHead::eOptFlag::ZeroFillAlloc); + +#ifdef BUILD_DEBUG + opt.setBit(Abstract::Memory::MEMiHeapHead::eOptFlag::DebugFillAlloc); +#endif + + s_memorySpace = malloc(MEMORY_SPACE_SIZE); + s_rootHeap = EGG::ExpHeap::create(s_memorySpace, MEMORY_SPACE_SIZE, opt); + s_rootHeap->setName("EGGRoot"); + s_rootHeap->becomeCurrentHeap(); + + EGG::SceneManager::SetRootHeap(s_rootHeap); +} diff --git a/tools/bindings_test.py b/tools/bindings_test.py new file mode 100644 index 00000000..91af22db --- /dev/null +++ b/tools/bindings_test.py @@ -0,0 +1,23 @@ +import bindings as kinoko + +k = kinoko.KBindSystem.create_instance() +k.set_course(kinoko.Course.Mushroom_Gorge) +k.set_player(0, kinoko.Character.Funky_Kong, kinoko.Vehicle.Flame_Runner, False) +k.init() + +controller = k.get_host_controller() +kart = k.get_kart(0) + +for i in range(1000): + controller.set_inputs(buttons=1, stick_x=0.0, stick_y=1.0, trick=kinoko.Trick.Neutral) + k.calc() + + pos = kart.pos() + speed = kart.speed() + + print(f"Frame {i}:") + print(f"pos: {pos.x:.2f}, {pos.y:.2f}, {pos.z:.2f}") + print(f"speed: {speed:.2f}") + print("------------------------") + +k.destroy_instance() \ No newline at end of file