From fa78740dcda7f0f62e6807eaf2a4e220eedf2ee7 Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Thu, 9 Nov 2023 17:52:19 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20an=20example=20using=20the=20?= =?UTF-8?q?cute=20framework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This shows off the very basic requirements of what's needed to embed a Dart as a game scripting language --- README.md | 58 +++--- examples/CMakeLists.txt | 3 +- examples/realtime_example/.gitignore | 2 + examples/realtime_example/CMakeLists.txt | 39 ++++ .../realtime_example/dart/.vscode/launch.json | 14 ++ examples/realtime_example/dart/drawable.dart | 31 ++++ examples/realtime_example/dart/ffi_calls.dart | 28 +++ examples/realtime_example/dart/main.dart | 66 +++++++ examples/realtime_example/dart/pubspec.yaml | 6 + examples/realtime_example/drawable.cpp | 4 + examples/realtime_example/drawable.h | 15 ++ examples/realtime_example/main.cpp | 170 ++++++++++++++++++ 12 files changed, 404 insertions(+), 32 deletions(-) create mode 100644 examples/realtime_example/.gitignore create mode 100644 examples/realtime_example/CMakeLists.txt create mode 100644 examples/realtime_example/dart/.vscode/launch.json create mode 100644 examples/realtime_example/dart/drawable.dart create mode 100644 examples/realtime_example/dart/ffi_calls.dart create mode 100644 examples/realtime_example/dart/main.dart create mode 100644 examples/realtime_example/dart/pubspec.yaml create mode 100644 examples/realtime_example/drawable.cpp create mode 100644 examples/realtime_example/drawable.h create mode 100644 examples/realtime_example/main.cpp diff --git a/README.md b/README.md index fae1bf3..6220c31 100644 --- a/README.md +++ b/README.md @@ -15,50 +15,46 @@ Additionally we may have a static target that uses the same interface as the dyn I also hope to support all platforms that Dart currently supports, plus a few extra. -## Current Progress - -A very, very simple Dart script works. - ## Using -`TODO:` +Lastest builds of the libraries are now available for certain targets as artifacts from [Github Actions](https://github.com/fuzzybinary/dart_shared_libray/actions) as well as all the headers necessary. + +Github Actions currently builds a Windows x64 `.dll`, A Linux x64 `.so`, and a macOS x64 `.dylib`. You can build a M-series dylib for macOS, but Github Actions does not currently support it. ## Building ### Prerequisets -You need +You need: * git -* All the things to get Dart source - https://github.com/dart-lang/sdk/wiki/Building#source +* Dart 3+ * C++ build tools for your platform (Visual Studio, XCode, gcc, etc) * CMake -## Contributing +Optionally, I recommend installing [`depot_tools`](https://www.chromium.org/developers/how-tos/depottools/) and making sure it is on your path before running setup scripts. Without depot_tools, the scripts will download them anyway, but having them already set up will save you some time with subsequent builds. -`TODO:` +### Patching and Building Dart -# What I Did +> NOTE: If you are building on Windows, I recommend running `.\setup_env.ps1` before executing any other scripts. +> This will set up some environment variables that will be needed to build Dart properly. -This section is as much for my benefit as for yours. Eventually, I hope to make a script that will do most of this. +The first step is to build a statically linkable verison of Dart. This requires that we download Dart, patch some of the Dart build files, and then run the actual build. Thankfully there is a Dart script to do this. -* Modify the BUILD.gn files to add a `libdart` target. This is done by applying the patch in `./dart_sdk.patch` -* Make sure all correct environment variables are set - * On Windows, these are set with the `setup_env.ps1` script. Specifically, you need to set `GYP_MSVS_OVERRIDE_PATH`, `GYP_MSVS_VERSION`, and `DEPOT_TOOLS_WIN_TOOLCHAIN=0` -* Builds libdart with: - ```bash - # On windows, you will need to add `python` in front - ./tools/build.py --no-goma -m release libdart - ``` -* Revert the changes made to build files to leave a clean copy of the dart-sdk (this makes updating easier) -* Build with CMake: -``` -cmake -B ./.build . -cmake --build ./.build +```bash +dart ./scripts/build_helpers/bin/build_dart.dart ``` -Updating the dart-sdk -* Make sure environment variables are set (`setup_env.ps1`) -* From `dart-sdk/sdk`: - * `git checkout tags/[version]` - * `gclient sync -D` - * reapply dart_sdk.patch (`git apply ../../dart_sdk.patch`) - * `python ./tools/build.py --no-goma -m release libdart` +This script does the following: + * Pulls down `depot_tools` if needed. + * Clones a fresh copy of the Dart sdk git repo using `fetch` if needed. + * Uses `gsync` to syncs the repo the the version of dart specificed in `.dart_version`. + * Applies `dart_sdk.patch` to the repo to create the statically linkable `libdart` library + * Builds `libdart` + +### CMake + +Once Dart is built, you can use CMake to generate build files and / or build the libraries and examples + +```bash +cmake -B ./.build . +cmake --build .\.build\ --config release +``` \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 608b4a5..8a5db2c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.21) add_subdirectory(simple_example) -add_subdirectory(simple_example_ffi) \ No newline at end of file +add_subdirectory(simple_example_ffi) +add_subdirectory(realtime_example) \ No newline at end of file diff --git a/examples/realtime_example/.gitignore b/examples/realtime_example/.gitignore new file mode 100644 index 0000000..db8373b --- /dev/null +++ b/examples/realtime_example/.gitignore @@ -0,0 +1,2 @@ +.dart_tool/ +pubspec.lock \ No newline at end of file diff --git a/examples/realtime_example/CMakeLists.txt b/examples/realtime_example/CMakeLists.txt new file mode 100644 index 0000000..db8340c --- /dev/null +++ b/examples/realtime_example/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.21) + +project(realtime_example) +set(CMAKE_CXX_STANDARD 17) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +set(CUTE_FRAMEWORK_STATIC ON) + +include(FetchContent) +FetchContent_Declare( + cute + GIT_REPOSITORY https://github.com/RandyGaul/cute_framework +) +FetchContent_MakeAvailable(cute) + +add_executable(realtime_example + main.cpp + drawable.cpp +) + +target_include_directories(realtime_example PRIVATE + "." + "${DART_DLL_DIR}" + "${DART_DIR}/runtime/include" +) + +add_custom_target(ALWAYS_DO_POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy -t $ $ + COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/dart $/dart + COMMAND_EXPAND_LISTS +) +add_dependencies(realtime_example ALWAYS_DO_POST_BUILD) + +target_link_libraries(realtime_example PUBLIC dart_dll cute) + +if (MSVC) + set_property(TARGET realtime_example PROPERTY VS_DEBUGGER_WORKING_DIRECTORY $) +endif() \ No newline at end of file diff --git a/examples/realtime_example/dart/.vscode/launch.json b/examples/realtime_example/dart/.vscode/launch.json new file mode 100644 index 0000000..0f0d8c0 --- /dev/null +++ b/examples/realtime_example/dart/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Dart: Attach to Process", + "type": "dart", + "request": "attach", + "vmServiceUri": "http://127.0.0.1:5858/" + } + ] +} \ No newline at end of file diff --git a/examples/realtime_example/dart/drawable.dart b/examples/realtime_example/dart/drawable.dart new file mode 100644 index 0000000..7d59f5f --- /dev/null +++ b/examples/realtime_example/dart/drawable.dart @@ -0,0 +1,31 @@ +import 'dart:ffi'; + +class CF_Color extends Struct { + @Float() + external double r; + + @Float() + external double g; + + @Float() + external double b; + + @Float() + external double a; +} + +class Drawable extends Struct { + @Int32() + external int x; + + @Int32() + external int y; + + @Int32() + external int width; + + @Int32() + external int height; + + external CF_Color color; +} diff --git a/examples/realtime_example/dart/ffi_calls.dart b/examples/realtime_example/dart/ffi_calls.dart new file mode 100644 index 0000000..53a27af --- /dev/null +++ b/examples/realtime_example/dart/ffi_calls.dart @@ -0,0 +1,28 @@ +import 'dart:ffi'; + +import 'drawable.dart'; + +class WorkFfiCalls { + final DynamicLibrary processLib = DynamicLibrary.process(); + + late final createEntity = processLib + .lookup>( + 'create_entity') + .asFunction(isLeaf: true); + late final destroyEntity = processLib + .lookup>('destroy_entity') + .asFunction(isLeaf: true); + late final getDrawable = processLib + .lookup Function(Uint32)>>( + 'get_drawable') + .asFunction Function(int)>(isLeaf: true); + + late final getKeyJustPressed = processLib + .lookup>('get_key_just_pressed') + .asFunction(isLeaf: true); +} + +const int CF_KEY_RIGHT = 145; +const int CF_KEY_LEFT = 146; +const int CF_KEY_DOWN = 147; +const int CF_KEY_UP = 148; diff --git a/examples/realtime_example/dart/main.dart b/examples/realtime_example/dart/main.dart new file mode 100644 index 0000000..7ec246c --- /dev/null +++ b/examples/realtime_example/dart/main.dart @@ -0,0 +1,66 @@ +import 'dart:ffi'; + +import 'ffi_calls.dart'; + +WorkFfiCalls ffi = new WorkFfiCalls(); + +class Wall { + late int entity; + + Wall(int x, int y, int width, int height) { + entity = ffi.createEntity(x, y, width, height); + final drawable = ffi.getDrawable(entity); + drawable.ref.color.r = 1.0; + drawable.ref.color.g = 0.0; + drawable.ref.color.b = 0.0; + drawable.ref.color.a = 1.0; + } +} + +List walls = []; + +// Dot! +int dotEntity = 0; +int dotX = 0; +int dotY = 0; +bool movingLeft = true; + +void main() { + print('main'); + walls.add( + Wall(-320, -240, 5, 480), + ); + walls.add( + Wall(315, -240, 5, 480), + ); + walls.add( + Wall(-320, -240, 640, 5), + ); + walls.add( + Wall(-320, 235, 640, 5), + ); + + dotEntity = ffi.createEntity(0, 0, 10, 10); + final drawable = ffi.getDrawable(dotEntity); + drawable.ref.color.g = 1.0; +} + +void frame(double dt) { + if (movingLeft) { + dotX -= 3; + if (dotX < -200) { + dotX = -200; + movingLeft = false; + } + } else { + dotX += 3; + if (dotX > 200) { + dotX = 200; + movingLeft = true; + } + } + + final dotDrawable = ffi.getDrawable(dotEntity); + dotDrawable.ref.x = dotX; + dotDrawable.ref.y = dotY; +} diff --git a/examples/realtime_example/dart/pubspec.yaml b/examples/realtime_example/dart/pubspec.yaml new file mode 100644 index 0000000..98cc45d --- /dev/null +++ b/examples/realtime_example/dart/pubspec.yaml @@ -0,0 +1,6 @@ +name: worm_example +environment: + sdk: ">=2.17.0 <3.0.0" + +dependencies: + ffi: ^2.0.1 \ No newline at end of file diff --git a/examples/realtime_example/drawable.cpp b/examples/realtime_example/drawable.cpp new file mode 100644 index 0000000..8edc3a4 --- /dev/null +++ b/examples/realtime_example/drawable.cpp @@ -0,0 +1,4 @@ +#include "drawable.h" + +#include "cute.h" +using namespace Cute; \ No newline at end of file diff --git a/examples/realtime_example/drawable.h b/examples/realtime_example/drawable.h new file mode 100644 index 0000000..ebb977c --- /dev/null +++ b/examples/realtime_example/drawable.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +using namespace Cute; + +class Drawable { + public: + int x; + int y; + int width; + int height; + + Color color; +}; \ No newline at end of file diff --git a/examples/realtime_example/main.cpp b/examples/realtime_example/main.cpp new file mode 100644 index 0000000..eb22d01 --- /dev/null +++ b/examples/realtime_example/main.cpp @@ -0,0 +1,170 @@ +#include +using namespace Cute; + +#include + +#include +#include +#include +#include "drawable.h" + +static Dart_Isolate _dart_isolate = nullptr; +static unsigned int _dart_pending_messages = 0; + +void dart_message_notify_callback(Dart_Isolate isolate) { + _dart_pending_messages++; +} + +Dart_PersistentHandle _root_library; +bool init_dart() { + DartDllConfig config; + DartDll_Initialize(config); + + _dart_isolate = DartDll_LoadScript("dart/main.dart", + "dart/.dart_tool/package_config.json"); + if (_dart_isolate == nullptr) { + return false; + } + + Dart_EnterIsolate(_dart_isolate); + Dart_SetMessageNotifyCallback(dart_message_notify_callback); + + Dart_EnterScope(); + Dart_Handle root_library = Dart_RootLibrary(); + _root_library = Dart_NewPersistentHandle(root_library); + Dart_Handle init_function_name = Dart_NewStringFromCString("main"); + Dart_Handle result = + Dart_Invoke(root_library, init_function_name, 0, nullptr); + if (Dart_IsError(result)) { + Dart_ExitScope(); + return false; + } + + Dart_ExitScope(); + + return true; +} + +void dart_frame(float delta_time) { + Dart_EnterScope(); + + Dart_Handle frame_function_name = Dart_NewStringFromCString("frame"); + Dart_Handle args[1] = { + Dart_NewDouble(delta_time), + }; + Dart_Handle root_library = Dart_HandleFromPersistent(_root_library); + Dart_Handle result = + Dart_Invoke(root_library, frame_function_name, 1, args); + + Dart_ExitScope(); +} + +void dart_frame_maintanance() { + // Drain the Dart microtask queue. This allows Futures and async methods + // to complete and execute any pending code. + DartDll_DrainMicrotaskQueue(); + + // Handle any pending messages. This includes handling Finalizers. + Dart_EnterScope(); + + while (_dart_pending_messages > 0) { + + Dart_HandleMessage(); + _dart_pending_messages--; + } + + uint64_t currentTime = Dart_TimelineGetMicros(); + // Notify Dart we're likely to be idle for the next few ms, + // this allows the garbage collector to fire if needed. + Dart_NotifyIdle(currentTime + 3000); + + Dart_ExitScope(); +} + +std::unordered_map entity_drawable_map; +unsigned int next_entity_id = 1; +void render_drawables() { + for (const auto& pair : entity_drawable_map) { + draw_push_color(pair.second->color); + draw_quad_fill(make_aabb(V2(pair.second->x, pair.second->y), + V2(pair.second->x + pair.second->width, + pair.second->y + pair.second->height))); + draw_pop_color(); + } +} + +// +// Dart accessible funcitons +// These need to be exposed as "C" functions and be exported +// +#if defined(_WIN32) +#define WORM_EXPORT __declspec(dllexport) +#elif defined(__GNUC__) +#define WORM_EXPORT __attribute__((visibility("default"))) +#else +#define WORM_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +WORM_EXPORT unsigned int create_entity(int x, int y, int width, int height) { + Drawable* d = new Drawable{x, y, width, height, color_blue() }; + unsigned int entity_id = next_entity_id; + entity_drawable_map[entity_id] = d; + + next_entity_id++; + + return entity_id; +} + +WORM_EXPORT void destroy_entity(unsigned int entity_id) { + const auto& itr = entity_drawable_map.find(entity_id); + if (itr != entity_drawable_map.end()) { + delete itr->second; + entity_drawable_map.erase(itr); + } +} + +WORM_EXPORT Drawable* get_drawable(unsigned int entity_id) { + const auto& itr = entity_drawable_map.find(entity_id); + if (itr != entity_drawable_map.end()) { + return itr->second; + } + return nullptr; +} + +WORM_EXPORT bool get_key_just_pressed(int key_code) { + return cf_key_just_pressed((CF_KeyButton)key_code); +} + +#ifdef __cplusplus +} +#endif + +int main(int argc, char* argv[]) { + // Create a window with a resolution of 640 x 480. + int options = + APP_OPTIONS_DEFAULT_GFX_CONTEXT | APP_OPTIONS_WINDOW_POS_CENTERED; + Result result = + make_app("Fancy Window Title", 0, 0, 640, 480, options, argv[0]); + if (is_error(result)) return -1; + + if (!init_dart()) return -1; + + while (app_is_running()) { + app_update(); + + dart_frame(DELTA_TIME); + dart_frame_maintanance(); + + render_drawables(); + + app_draw_onto_screen(); + } + + destroy_app(); + + return 0; +} \ No newline at end of file