[M1-01] Initialize Monorepo, CMake C++20 Build, and .slnx Workspace (#22)
* [M1-01] Initialize monorepo: CMake C++20 build, .slnx workspace, vendored deps, CI - Root CMakeLists.txt (C++20 strict, PIC) + cmake/EngineHotFlags (-Wall/-Werror, -mavx2 x86-64 only, -fno-exceptions/-fno-rtti hot path) - CMakePresets.json: linux/macos/windows debug+release, ci-* (WERROR) - native/: engine_core shared lib (version API), core/ecs hot OBJECT libs, renderer/physics/audio/bridge stubs with owners - native/tests: engine_core_smoke CTest (version handshake) - deps/: flecs v4.1.6 + enkiTS pinned, README with re-vendor recipe - Engine.slnx + Directory.Build.props (net10.0, AllowUnsafeBlocks, TieredPGO, zero-warn) + Bindings/Runtime/Tests/abi_codegen stubs - .github/workflows/ci.yml: native (CMake+CTest) + managed (dotnet build+run) on ubuntu/macos/windows - Verified: configure+build+ctest green on linux-debug (Clang), AVX2/no-exceptions flags confirmed in compile_commands.json * fix(ci): AbiCodegen assembly name (CA1707), Windows Ninja+MSVC presets * fix(ci): fail-fast windows test (5min step timeout, 60s test timeout, verbose) * fix(ci): isolate windows hang — bootstrap control test + unbuffered smoke output * fix(ci): copy runtime DLLs next to windows test binaries * fix(ci): only copy DLLs for linked test target --------- Co-authored-by: hermes-agent <hermes-agent@local> Co-authored-by: hermes <[email protected]>
No files matched your search
@@ -0,0 +1,67 @@
|
||||
# M1-01 CI: clean native + managed compilation on all three OSes.
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["**"]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
native:
|
||||
name: "native (${{ matrix.os }})"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-24.04
|
||||
preset: ci-linux
|
||||
- os: macos-15
|
||||
preset: ci-macos
|
||||
- os: windows-2025
|
||||
preset: ci-windows
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Ninja + Clang (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get update && sudo apt-get install -y ninja-build clang
|
||||
|
||||
- name: Install Ninja (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install ninja
|
||||
|
||||
- name: Setup MSVC environment (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Configure
|
||||
run: cmake --preset ${{ matrix.preset }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build --preset ${{ matrix.preset }}
|
||||
|
||||
- name: Test (CTest)
|
||||
timeout-minutes: 5
|
||||
run: ctest --preset ${{ matrix.preset }} --output-on-failure -V
|
||||
|
||||
managed:
|
||||
name: "managed (${{ matrix.os }})"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-24.04, macos-15, windows-2025]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET 10 SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "10.0.x"
|
||||
|
||||
- name: Build Engine.slnx
|
||||
run: dotnet build Engine.slnx -c Release --nologo
|
||||
|
||||
- name: Run Engine.Tests gate
|
||||
run: dotnet run --project managed/Engine.Tests -c Release --no-build
|
||||
@@ -0,0 +1,66 @@
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GameEngine — root build.
|
||||
# M1-01: unified CMake C++20 pipeline (Ninja + Clang/MSVC) for libengine_core.
|
||||
#
|
||||
# Layout:
|
||||
# native/ libengine_core (core, ecs, renderer, physics, audio, bridge)
|
||||
# deps/ vendored third-party (flecs v4.x, enkiTS)
|
||||
# managed/ .NET 10 solution (Engine.slnx) — built by `dotnet`, not CMake
|
||||
# tools/ build-time utilities (abi_codegen, M1-04)
|
||||
# ---------------------------------------------------------------------------
|
||||
project(GameEngine
|
||||
VERSION 0.1.0
|
||||
DESCRIPTION "Native C++20 + .NET 10 zero-copy game engine"
|
||||
LANGUAGES C CXX
|
||||
)
|
||||
|
||||
# --- Global options ---------------------------------------------------------
|
||||
option(ENGINE_BUILD_TESTS "Build native smoke tests (CTest)" ON)
|
||||
option(ENGINE_ENABLE_AVX2 "Enable AVX2 codegen for x86-64 hot modules" ON)
|
||||
option(ENGINE_WERROR "Treat warnings as errors in engine targets" ON)
|
||||
option(ENGINE_BUILD_SHARED "Build engine_core as a shared library" ON)
|
||||
|
||||
# --- Module path (strict hot-path flags live here) --------------------------
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
include(EngineHotFlags)
|
||||
|
||||
# --- Language defaults ------------------------------------------------------
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF) # strict ISO C++20, no GNU extensions
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # clangd / tooling support
|
||||
|
||||
# Position-independent code everywhere (managed host loads us as a .so/.dll).
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
# --- Vendored dependencies --------------------------------------------------
|
||||
# flecs v4.x (pinned tag v4.1.6, see deps/README.md): static lib only, no
|
||||
# tests/examples — keeps configure fast and warnings out of our build.
|
||||
set(FLECS_STATIC ON CACHE BOOL "" FORCE)
|
||||
set(FLECS_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(FLECS_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(FLECS_PIC ON CACHE BOOL "" FORCE)
|
||||
add_subdirectory(deps/flecs)
|
||||
|
||||
# enkiTS (pinned HEAD 404a3bf8, see deps/README.md): static lib, C interface
|
||||
# on (M1-03 binds it to Flecs task hooks), examples off.
|
||||
set(ENKITS_BUILD_C_INTERFACE ON CACHE BOOL "" FORCE)
|
||||
set(ENKITS_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
set(ENKITS_BUILD_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(ENKITS_INSTALL OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(deps/enkiTS)
|
||||
|
||||
# --- Engine core ------------------------------------------------------------
|
||||
add_subdirectory(native)
|
||||
|
||||
# --- Tests ------------------------------------------------------------------
|
||||
if(ENGINE_BUILD_TESTS)
|
||||
include(CTest)
|
||||
enable_testing()
|
||||
add_subdirectory(native/tests)
|
||||
endif()
|
||||
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"version": 3,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "base",
|
||||
"hidden": true,
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "${sourceDir}/build/${presetName}",
|
||||
"installDir": "${sourceDir}/build/${presetName}/install",
|
||||
"cacheVariables": {
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-debug",
|
||||
"displayName": "Linux Debug (Clang)",
|
||||
"inherits": "base",
|
||||
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Linux" },
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_C_COMPILER": "clang",
|
||||
"CMAKE_CXX_COMPILER": "clang++",
|
||||
"ENGINE_BUILD_TESTS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-release",
|
||||
"displayName": "Linux Release (Clang)",
|
||||
"inherits": "base",
|
||||
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Linux" },
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"CMAKE_C_COMPILER": "clang",
|
||||
"CMAKE_CXX_COMPILER": "clang++",
|
||||
"ENGINE_BUILD_TESTS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "macos-debug",
|
||||
"displayName": "macOS Debug",
|
||||
"inherits": "base",
|
||||
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Darwin" },
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"ENGINE_BUILD_TESTS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "macos-release",
|
||||
"displayName": "macOS Release",
|
||||
"inherits": "base",
|
||||
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Darwin" },
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"ENGINE_BUILD_TESTS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-debug",
|
||||
"displayName": "Windows Debug (MSVC + Ninja)",
|
||||
"inherits": "base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"ENGINE_BUILD_TESTS": "ON"
|
||||
},
|
||||
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" }
|
||||
},
|
||||
{
|
||||
"name": "windows-release",
|
||||
"displayName": "Windows Release (MSVC + Ninja)",
|
||||
"inherits": "base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"ENGINE_BUILD_TESTS": "ON"
|
||||
},
|
||||
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" }
|
||||
},
|
||||
{
|
||||
"name": "ci-linux",
|
||||
"displayName": "CI Linux",
|
||||
"inherits": "linux-release",
|
||||
"cacheVariables": {
|
||||
"ENGINE_WERROR": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ci-macos",
|
||||
"displayName": "CI macOS",
|
||||
"inherits": "macos-release",
|
||||
"cacheVariables": {
|
||||
"ENGINE_WERROR": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ci-windows",
|
||||
"displayName": "CI Windows",
|
||||
"inherits": "windows-release",
|
||||
"cacheVariables": {
|
||||
"ENGINE_WERROR": "ON"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{ "name": "linux-debug", "configurePreset": "linux-debug" },
|
||||
{ "name": "linux-release", "configurePreset": "linux-release" },
|
||||
{ "name": "macos-debug", "configurePreset": "macos-debug" },
|
||||
{ "name": "macos-release", "configurePreset": "macos-release" },
|
||||
{ "name": "windows-debug", "configurePreset": "windows-debug" },
|
||||
{ "name": "windows-release","configurePreset": "windows-release" },
|
||||
{ "name": "ci-linux", "configurePreset": "ci-linux" },
|
||||
{ "name": "ci-macos", "configurePreset": "ci-macos" },
|
||||
{ "name": "ci-windows", "configurePreset": "ci-windows" }
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "default",
|
||||
"configurePreset": "linux-debug",
|
||||
"output": { "outputOnFailure": true }
|
||||
},
|
||||
{ "name": "ci-linux", "configurePreset": "ci-linux" },
|
||||
{ "name": "ci-macos", "configurePreset": "ci-macos" },
|
||||
{ "name": "ci-windows", "configurePreset": "ci-windows" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<!-- Directory.Build.props — repo-wide managed defaults (M1-01, per spec). -->
|
||||
<!-- Imported automatically by every .csproj under the repo root. -->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!-- .NET 10, unsafe required for zero-copy Span<T> over native memory. -->
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<TieredPGO>true</TieredPGO>
|
||||
|
||||
<!-- Strictness: zero warnings tolerance, modern analysis. -->
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<AnalysisMode>Recommended</AnalysisMode>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
||||
<!-- Deterministic, signable-friendly builds. -->
|
||||
<Deterministic>true</Deterministic>
|
||||
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">true</ContinuousIntegrationBuild>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,14 @@
|
||||
<Solution>
|
||||
<!-- Engine.slnx — .NET 10 managed solution (M1-01). Built by `dotnet`, not CMake. -->
|
||||
<!-- Projects land per milestone: Bindings+Runtime (M1-04/M1-05), Analyzers (M2), Editor/Cli (M5). -->
|
||||
<Folder Name="/src/">
|
||||
<Project Path="managed/Engine.Bindings/Engine.Bindings.csproj" />
|
||||
<Project Path="managed/Engine.Runtime/Engine.Runtime.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/tools/">
|
||||
<Project Path="tools/abi_codegen/abi_codegen.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/tests/">
|
||||
<Project Path="managed/Engine.Tests/Engine.Tests.csproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
@@ -0,0 +1,57 @@
|
||||
# EngineHotFlags.cmake — strict per-target compile flags (M1-01).
|
||||
#
|
||||
# Usage:
|
||||
# engine_apply_hot_flags(<target>)
|
||||
# C++20, wall of warnings (+ -Werror unless ENGINE_WERROR=OFF),
|
||||
# AVX2 on x86-64 hot modules (guarded: never emitted on ARM/Apple Silicon),
|
||||
# -fno-exceptions -fno-rtti (GCC/Clang) — frame path must not unwind.
|
||||
#
|
||||
# Non-hot / host-facing modules (bridge host shim, tests) should use
|
||||
# engine_apply_base_flags(<target>) instead, which keeps exceptions/RTTI.
|
||||
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
function(engine_apply_base_flags TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_20)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(${TARGET} PRIVATE /W4 /permissive- /Zc:__cplusplus)
|
||||
if(ENGINE_WERROR)
|
||||
target_compile_options(${TARGET} PRIVATE /WX)
|
||||
endif()
|
||||
else()
|
||||
target_compile_options(${TARGET} PRIVATE
|
||||
-Wall -Wextra -Wpedantic
|
||||
-Wconversion -Wsign-conversion -Wshadow -Wundef
|
||||
-fno-strict-aliasing
|
||||
)
|
||||
if(ENGINE_WERROR)
|
||||
target_compile_options(${TARGET} PRIVATE -Werror)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(engine_apply_hot_flags TARGET)
|
||||
engine_apply_base_flags(${TARGET})
|
||||
|
||||
# AVX2 codegen on x86-64 only. aarch64/ARM64 (Apple Silicon, phones) must
|
||||
# never see -mavx2; NEON paths are selected per-TU in M1-03.
|
||||
if(ENGINE_ENABLE_AVX2 AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|x64)$")
|
||||
if(MSVC)
|
||||
target_compile_options(${TARGET} PRIVATE /arch:AVX2)
|
||||
else()
|
||||
target_compile_options(${TARGET} PRIVATE -mavx2 -mfma)
|
||||
target_compile_definitions(${TARGET} PUBLIC ENGINE_HAS_AVX2=1)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# No unwinding, no RTTI on the frame path (AGENTS.md invariant).
|
||||
if(MSVC)
|
||||
# /GR- disables RTTI; exceptions stay on under MSVC (required for STL
|
||||
# correctness) but engine code must still be noexcept — enforced by
|
||||
# review, not by flag, on this toolchain.
|
||||
target_compile_options(${TARGET} PRIVATE /GR-)
|
||||
else()
|
||||
target_compile_options(${TARGET} PRIVATE -fno-exceptions -fno-rtti)
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -0,0 +1,19 @@
|
||||
# Vendored native dependencies (M1-01)
|
||||
|
||||
Checked in directly (no submodules) so clean clones build offline after
|
||||
checkout. **Do not hand-edit vendored trees** — upgrades are whole-tree
|
||||
replacements, recorded below.
|
||||
|
||||
| Dep | Version pinned | Upstream | Notes |
|
||||
|--------|---------------|-----------------------|-------|
|
||||
| flecs | `v4.1.6` (`fb55f3c2`) | https://github.com/SanderMertens/flecs | C99/C++ archetype ECS; built `flecs_static` only (`FLECS_SHARED=OFF`, `FLECS_TESTS=OFF`) |
|
||||
| enkiTS | `master@404a3bf8` | https://github.com/dougbinks/enkiTS | C++ task scheduler; static lib, C interface ON (M1-03 binds it to Flecs task hooks), examples OFF |
|
||||
|
||||
Re-vendor recipe:
|
||||
|
||||
```bash
|
||||
git clone --depth 1 --branch v4.1.6 https://github.com/SanderMertens/flecs.git deps/flecs
|
||||
git clone --depth 1 https://github.com/dougbinks/enkiTS.git deps/enkiTS
|
||||
rm -rf deps/flecs/.git deps/enkiTS/.git
|
||||
# then update the pin table above with the new tag/SHA
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: dougbinks
|
||||
patreon: enkisoftware
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: https://www.enkisoftware.com/
|
||||
@@ -0,0 +1,152 @@
|
||||
name: Build
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [ master, dev ]
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
statuses: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-linux-clang:
|
||||
name: Linux, Clang
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CFLAGS: -Werror
|
||||
CXXFLAGS: -Werror
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Configure static library
|
||||
run: cmake -S . -B build-static -D ENKITS_SANITIZE=ON
|
||||
- name: Build static library
|
||||
run: cmake --build build-static --parallel
|
||||
- name: Test static library
|
||||
run: ./build-static/TestAll
|
||||
|
||||
- name: Configure shared library
|
||||
run: cmake -S . -B build-shared -D ENKITS_BUILD_SHARED=ON
|
||||
- name: Build shared library
|
||||
run: cmake --build build-shared --parallel
|
||||
- name: Test shared library
|
||||
run: ./build-shared/TestAll
|
||||
|
||||
build-linux-gcc:
|
||||
name: Linux, gcc
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CC: gcc
|
||||
CXX: g++
|
||||
CFLAGS: -Werror
|
||||
CXXFLAGS: -Werror
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Configure static library
|
||||
run: cmake -S . -B build-static -D ENKITS_SANITIZE=ON
|
||||
- name: Build static library
|
||||
run: cmake --build build-static --parallel
|
||||
- name: Test static library
|
||||
run: ./build-static/TestAll
|
||||
|
||||
- name: Configure shared library
|
||||
run: cmake -S . -B build-shared -D ENKITS_BUILD_SHARED=ON
|
||||
- name: Build shared library
|
||||
run: cmake --build build-shared --parallel
|
||||
- name: Test shared library
|
||||
run: ./build-shared/TestAll
|
||||
|
||||
build-ubuntu-zig:
|
||||
name: Linux, zig
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
version: 0.16.0
|
||||
|
||||
- name: Build static library and priorities
|
||||
run: zig build -Dbuild_examples=true -Dshared=false -Dtask_priorities=4
|
||||
- name: Test static library
|
||||
run: ./zig-out/bin/TestAll
|
||||
|
||||
- name: Build shared library
|
||||
run: zig build -Dbuild_examples=true -Dshared=true
|
||||
- name: Test shared library
|
||||
run: ./zig-out/bin/TestAll
|
||||
|
||||
|
||||
build-macos-cocoa-clang:
|
||||
name: Cocoa (macOS, Clang)
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
CFLAGS: -Werror
|
||||
CXXFLAGS: -Werror
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Configure static library
|
||||
run: cmake -S . -B build-static -D ENKITS_SANITIZE=ON
|
||||
- name: Build static library
|
||||
run: cmake --build build-static --parallel
|
||||
- name: Test static library
|
||||
run: ./build-static/TestAll
|
||||
|
||||
- name: Configure shared library
|
||||
run: cmake -S . -B build-shared -D ENKITS_BUILD_SHARED=ON
|
||||
- name: Build shared library
|
||||
run: cmake --build build-shared --parallel
|
||||
- name: Test shared library
|
||||
run: ./build-shared/TestAll
|
||||
|
||||
build-windows-win32-vs2022:
|
||||
name: Win32 (Windows, VS2022)
|
||||
runs-on: windows-2022
|
||||
env:
|
||||
CFLAGS: /WX
|
||||
CXXFLAGS: /WX
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup MSVC dev command prompt
|
||||
uses: TheMrMilchmann/setup-msvc-dev@v3
|
||||
with:
|
||||
arch: x64
|
||||
|
||||
- name: Configure static library
|
||||
run: cmake -S . -B build-static -G "Visual Studio 17 2022" -D ENKITS_SANITIZE=ON
|
||||
- name: Build static library
|
||||
run: cmake --build build-static --parallel
|
||||
- name: Test static library
|
||||
run: .\build-static\Debug\TestAll.exe
|
||||
|
||||
- name: Configure shared library
|
||||
run: cmake -S . -B build-shared -G "Visual Studio 17 2022" -D ENKITS_BUILD_SHARED=ON
|
||||
- name: Build shared library
|
||||
run: cmake --build build-shared --parallel
|
||||
- name: Test shared library
|
||||
run: .\build-shared\Debug\TestAll.exe
|
||||
|
||||
build-windows-win32-zig:
|
||||
name: Win32 (Windows, zig)
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
version: 0.16.0
|
||||
|
||||
- name: Build static library and priorities
|
||||
run: zig build -Dbuild_examples=true -Dshared=false -Dtask_priorities=4
|
||||
- name: Test static library
|
||||
run: .\zig-out\bin\TestAll.exe
|
||||
|
||||
- name: Build shared library
|
||||
run: zig build -Dbuild_examples=true -Dshared=true
|
||||
- name: Test shared library
|
||||
run: .\zig-out\bin\TestAll.exe
|
||||
@@ -0,0 +1 @@
|
||||
build/
|
||||
@@ -0,0 +1,176 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project( enkiTS
|
||||
VERSION "1.11"
|
||||
DESCRIPTION "A C and C++ task scheduler for creating parallel programs"
|
||||
HOMEPAGE_URL "https://github.com/dougbinks/enkiTS"
|
||||
LANGUAGES C CXX)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
option( ENKITS_BUILD_C_INTERFACE "Build C interface" ON )
|
||||
option( ENKITS_BUILD_EXAMPLES "Build example applications" ON )
|
||||
option( ENKITS_BUILD_SHARED "Build shared library" OFF )
|
||||
option( ENKITS_INSTALL "Generate installation target" OFF )
|
||||
option( ENKITS_SANITIZE "Build with sanitizers" OFF)
|
||||
|
||||
set( ENKITS_TASK_PRIORITIES_NUM "3" CACHE STRING "Number of task priorities, 1-5, 0 for defined by defaults in source" )
|
||||
|
||||
set( ENKITS_HEADERS
|
||||
src/LockLessMultiReadPipe.h
|
||||
src/TaskScheduler.h
|
||||
)
|
||||
|
||||
set( ENKITS_SRC
|
||||
src/TaskScheduler.cpp
|
||||
)
|
||||
|
||||
if( ENKITS_BUILD_C_INTERFACE )
|
||||
list( APPEND ENKITS_HEADERS
|
||||
src/TaskScheduler_c.h
|
||||
)
|
||||
|
||||
list( APPEND ENKITS_SRC
|
||||
src/TaskScheduler_c.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
list( APPEND ENKITS_SRC ${ENKITS_HEADERS} )
|
||||
|
||||
if(ENKITS_SANITIZE)
|
||||
if(MSVC)
|
||||
add_compile_options(/fsanitize=address)
|
||||
add_link_options(/INCREMENTAL:NO)
|
||||
else()
|
||||
# add_compile_options(-fsanitize=thread -fno-omit-frame-pointer)
|
||||
add_compile_options(-fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined)
|
||||
add_link_options(-fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if( ENKITS_BUILD_SHARED )
|
||||
add_library( enkiTS SHARED ${ENKITS_SRC} )
|
||||
target_compile_definitions( enkiTS PRIVATE ENKITS_BUILD_DLL=1 )
|
||||
target_compile_definitions( enkiTS INTERFACE ENKITS_DLL=1 )
|
||||
set_target_properties( enkiTS PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||
if( UNIX )
|
||||
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")
|
||||
endif()
|
||||
endif ()
|
||||
else()
|
||||
add_library( enkiTS STATIC ${ENKITS_SRC} )
|
||||
endif()
|
||||
|
||||
target_include_directories( enkiTS PUBLIC
|
||||
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/enkiTS> )
|
||||
|
||||
if( ENKITS_TASK_PRIORITIES_NUM GREATER "0" )
|
||||
target_compile_definitions( enkiTS PUBLIC "ENKITS_TASK_PRIORITIES_NUM=${ENKITS_TASK_PRIORITIES_NUM}" )
|
||||
endif()
|
||||
|
||||
if( UNIX )
|
||||
set( CMAKE_THREAD_PREFER_PTHREAD TRUE )
|
||||
find_package( Threads REQUIRED )
|
||||
if( CMAKE_USE_PTHREADS_INIT )
|
||||
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread" )
|
||||
endif()
|
||||
target_link_libraries( enkiTS ${CMAKE_THREAD_LIBS_INIT} )
|
||||
endif()
|
||||
|
||||
if( ENKITS_INSTALL )
|
||||
install(
|
||||
TARGETS enkiTS
|
||||
EXPORT enkiTSConfig
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES ${ENKITS_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/enkiTS)
|
||||
install(
|
||||
EXPORT enkiTSConfig
|
||||
NAMESPACE enkiTS::
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/enkiTS)
|
||||
endif()
|
||||
|
||||
if( UNIX )
|
||||
SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11" )
|
||||
endif()
|
||||
if( APPLE )
|
||||
SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++" )
|
||||
endif()
|
||||
|
||||
if( ENKITS_BUILD_EXAMPLES )
|
||||
add_executable( ParallelSum example/ParallelSum.cpp example/Timer.h )
|
||||
target_link_libraries(ParallelSum enkiTS )
|
||||
|
||||
add_executable( PinnedTask example/PinnedTask.cpp )
|
||||
target_link_libraries(PinnedTask enkiTS )
|
||||
|
||||
add_executable( LambdaTask example/LambdaTask.cpp example/Timer.h )
|
||||
target_link_libraries(LambdaTask enkiTS )
|
||||
|
||||
if( ENKITS_TASK_PRIORITIES_NUM GREATER "0" )
|
||||
add_executable( Priorities example/Priorities.cpp )
|
||||
target_link_libraries(Priorities enkiTS )
|
||||
endif()
|
||||
|
||||
add_executable( TaskThroughput example/TaskThroughput.cpp example/Timer.h )
|
||||
target_link_libraries(TaskThroughput enkiTS )
|
||||
|
||||
add_executable( TaskOverhead example/TaskOverhead.cpp example/Timer.h )
|
||||
target_link_libraries(TaskOverhead enkiTS )
|
||||
|
||||
add_executable( TestWaitforTask example/TestWaitforTask.cpp )
|
||||
target_link_libraries(TestWaitforTask enkiTS )
|
||||
|
||||
add_executable( ExternalTaskThread example/ExternalTaskThread.cpp )
|
||||
target_link_libraries(ExternalTaskThread enkiTS )
|
||||
|
||||
add_executable( CustomAllocator example/CustomAllocator.cpp )
|
||||
target_link_libraries(CustomAllocator enkiTS )
|
||||
|
||||
add_executable( TestAll example/TestAll.cpp )
|
||||
target_link_libraries(TestAll enkiTS )
|
||||
|
||||
add_executable( Dependencies example/Dependencies.cpp )
|
||||
target_link_libraries(Dependencies enkiTS )
|
||||
|
||||
add_executable( CompletionAction example/CompletionAction.cpp )
|
||||
target_link_libraries(CompletionAction enkiTS )
|
||||
|
||||
add_executable( WaitForNewPinnedTasks example/WaitForNewPinnedTasks.cpp )
|
||||
target_link_libraries(WaitForNewPinnedTasks enkiTS )
|
||||
|
||||
if( ENKITS_BUILD_C_INTERFACE )
|
||||
add_executable( ParallelSum_c example/ParallelSum_c.c )
|
||||
target_link_libraries(ParallelSum_c enkiTS )
|
||||
|
||||
add_executable( PinnedTask_c example/PinnedTask_c.c )
|
||||
target_link_libraries(PinnedTask_c enkiTS )
|
||||
|
||||
if( ENKITS_TASK_PRIORITIES_NUM GREATER "0" )
|
||||
add_executable( Priorities_c example/Priorities_c.c )
|
||||
target_link_libraries(Priorities_c enkiTS )
|
||||
endif()
|
||||
|
||||
add_executable( ExternalTaskThread_c example/ExternalTaskThread_c.c )
|
||||
target_link_libraries(ExternalTaskThread_c enkiTS )
|
||||
|
||||
add_executable( CustomAllocator_c example/CustomAllocator_c.c )
|
||||
target_link_libraries(CustomAllocator_c enkiTS )
|
||||
|
||||
add_executable( Dependencies_c example/Dependencies_c.c )
|
||||
target_link_libraries(Dependencies_c enkiTS )
|
||||
|
||||
add_executable( CompletionAction_c example/CompletionAction_c.c )
|
||||
target_link_libraries(CompletionAction_c enkiTS )
|
||||
|
||||
add_executable( WaitForNewPinnedTasks_c example/WaitForNewPinnedTasks_c.c )
|
||||
target_link_libraries(WaitForNewPinnedTasks_c enkiTS )
|
||||
|
||||
endif()
|
||||
|
||||
endif()
|
||||
@@ -0,0 +1,17 @@
|
||||
Copyright (c) 2013 Doug Binks
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgement in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
@@ -0,0 +1,400 @@
|
||||
Support development of enkiTS through [Github Sponsors](https://github.com/sponsors/dougbinks) or [Patreon](https://www.patreon.com/enkisoftware)
|
||||
|
||||
[<img src="https://img.shields.io/static/v1?logo=github&label=Github&message=Sponsor&color=#ea4aaa" width="200"/>](https://github.com/sponsors/dougbinks) [<img src="https://c5.patreon.com/external/logo/[email protected]" alt="Become a Patron" width="150"/>](https://www.patreon.com/enkisoftware)
|
||||
|
||||

|
||||
# enkiTS
|
||||
| [Master branch](https://github.com/dougbinks/enkiTS/) | [Dev branch](https://github.com/dougbinks/enkiTS/tree/dev) |
|
||||
| --- | --- |
|
||||
| [](https://github.com/dougbinks/enkiTS/actions) | [](https://github.com/dougbinks/enkiTS/actions) |
|
||||
|
||||
## enki Task Scheduler
|
||||
|
||||
A permissively licensed C and C++ Task Scheduler for creating parallel programs. Requires C++11 support.
|
||||
|
||||
The primary goal of enkiTS is to help developers create programs which handle both data and task level parallelism to utilize the full performance of multicore CPUs, whilst being lightweight (only a small amount of code) and easy to use.
|
||||
|
||||
* [C++ API via src/TaskScheduler.h](src/TaskScheduler.h)
|
||||
* [C API via src/TaskScheduler_c.h](src/TaskScheduler_c.h)
|
||||
|
||||
enkiTS was developed for, and is used in [enkisoftware](http://www.enkisoftware.com/)'s Avoyd codebase.
|
||||
|
||||
## Platforms
|
||||
|
||||
- Windows, Linux, Mac OS, Android (should work on iOS)
|
||||
- x64 & x86, ARM
|
||||
|
||||
enkiTS is primarily developed on x64 and x86 Intel architectures on MS Windows, with well tested support for Linux and somewhat less frequently tested support on Mac OS and ARM Android.
|
||||
|
||||
## Examples
|
||||
|
||||
Several examples exist in the [example folder](https://github.com/dougbinks/enkiTS/tree/master/example).
|
||||
|
||||
For further examples, see https://github.com/dougbinks/enkiTSExamples
|
||||
|
||||
## Building
|
||||
|
||||
Building enkiTS is simple, just add the files in enkiTS/src to your build system (_c.* files can be ignored if you only need C++ interface), and add enkiTS/src to your include path. Unix / Linux builds will likely require the pthreads library.
|
||||
|
||||
For C++
|
||||
|
||||
- Use `#include "TaskScheduler.h"`
|
||||
- Add enkiTS/src to your include path
|
||||
- Compile / Add to project:
|
||||
- `TaskScheduler.cpp`
|
||||
- Unix / Linux builds will likely require the pthreads library.
|
||||
|
||||
For C
|
||||
|
||||
- Use `#include "TaskScheduler_c.h"`
|
||||
- Add enkiTS/src to your include path
|
||||
- Compile / Add to project:
|
||||
- `TaskScheduler.cpp`
|
||||
- `TaskScheduler_c.cpp`
|
||||
- Unix / Linux builds will likely require the pthreads library.
|
||||
|
||||
For cmake, on Windows / Mac OS X / Linux with cmake installed, open a prompt in the enkiTS directory and:
|
||||
|
||||
1. `mkdir build`
|
||||
1. `cd build`
|
||||
1. `cmake ..`
|
||||
1. either run `make all` or for Visual Studio open `enkiTS.sln`
|
||||
|
||||
## Project Features
|
||||
|
||||
1. *Lightweight* - enkiTS is designed to be lean so you can use it anywhere easily, and understand it.
|
||||
1. *Fast, then scalable* - enkiTS is designed for consumer devices first, so performance on a low number of threads is important, followed by scalability.
|
||||
1. *Braided parallelism* - enkiTS can issue tasks from another task as well as from the thread which created the Task System, and has a simple task interface for both data parallel and task parallelism.
|
||||
1. *Up-front Allocation friendly* - enkiTS is designed for zero allocations during scheduling.
|
||||
1. *Can pin tasks to a given thread* - enkiTS can schedule a task which will only be run on the specified thread.
|
||||
1. *Can set task priorities* - Up to 5 task priorities can be configured via define ENKITS_TASK_PRIORITIES_NUM (defaults to 3). Higher priority tasks are run before lower priority ones.
|
||||
1. *Can register external threads to use with enkiTS* - Can configure enkiTS with numExternalTaskThreads which can be registered to use with the enkiTS API.
|
||||
1. *Custom allocator API* - can configure enkiTS with custom allocators, see [example/CustomAllocator.cpp](example/CustomAllocator.cpp) and [example/CustomAllocator_c.c](example/CustomAllocator_c.c).
|
||||
1. *Dependencies* - can set dependendencies between tasks see [example/Dependencies.cpp](example/Dependencies.cpp) and [example/Dependencies_c.c](example/Dependencies_c.c).
|
||||
1. *Completion Actions* - can perform an action on task completion. This avoids the expensive action of adding the task to the scheduler, and can be used to safely delete a completed task. See [example/CompletionAction.cpp](example/CompletionAction.cpp) and [example/CompletionAction_c.c](example/CompletionAction_c.c)
|
||||
1. **NEW** *Can wait for pinned tasks* - Can wait for pinned tasks, useful for creating IO threads which do no other work. See [example/WaitForNewPinnedTasks.cpp](example/WaitForNewPinnedTasks.cpp) and [example/WaitForNewPinnedTasks_c.c](example/WaitForNewPinnedTasks_c.c).
|
||||
|
||||
## Installing
|
||||
|
||||
I recommend using enkiTS directly from source in each project rather than installing it for system wide use. However enkiTS' cmake script can also be used to install the library
|
||||
if the `ENKITS_INSTALL` cmake variable is set to `ON` (it defaults to `OFF`).
|
||||
|
||||
When installed the header files are installed in a subdirectory of the include path, `include/enkiTS` to ensure that they do not conflict with header files from other packages.
|
||||
When building applications either ensure this is part of the `INCLUDE_PATH` variable or ensure that enkiTS is in the header path in the source files, for example use `#include "enkiTS/TaskScheduler.h"` instead of `#include "TaskScheduler.h"`.
|
||||
|
||||
## Using enkiTS
|
||||
|
||||
### C++ usage
|
||||
- full example in [example/ParallelSum.cpp](example/ParallelSum.cpp)
|
||||
- C example in [example/ParallelSum_c.c](example/ParallelSum_c.c)
|
||||
```C
|
||||
#include "TaskScheduler.h"
|
||||
|
||||
enki::TaskScheduler g_TS;
|
||||
|
||||
// define a task set, can ignore range if we only do one thing
|
||||
struct ParallelTaskSet : enki::ITaskSet {
|
||||
void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override {
|
||||
// do something here, can issue tasks with g_TS
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
g_TS.Initialize();
|
||||
ParallelTaskSet task; // default constructor has a set size of 1
|
||||
g_TS.AddTaskSetToPipe( &task );
|
||||
|
||||
// wait for task set (running tasks if they exist)
|
||||
// since we've just added it and it has no range we'll likely run it.
|
||||
g_TS.WaitforTask( &task );
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### C++ 11 lambda usage
|
||||
- full example in [example/LambdaTask.cpp](example/LambdaTask.cpp)
|
||||
```C
|
||||
#include "TaskScheduler.h"
|
||||
|
||||
enki::TaskScheduler g_TS;
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
g_TS.Initialize();
|
||||
|
||||
enki::TaskSet task( 1, []( enki::TaskSetPartition range_, uint32_t threadnum_ ) {
|
||||
// do something here
|
||||
} );
|
||||
|
||||
g_TS.AddTaskSetToPipe( &task );
|
||||
g_TS.WaitforTask( &task );
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Task priorities usage in C++
|
||||
- full example in [example/Priorities.cpp](example/Priorities.cpp)
|
||||
- C example in [example/Priorities_c.c](example/Priorities_c.c)
|
||||
```C
|
||||
// See full example in Priorities.cpp
|
||||
#include "TaskScheduler.h"
|
||||
|
||||
enki::TaskScheduler g_TS;
|
||||
|
||||
struct ExampleTask : enki::ITaskSet
|
||||
{
|
||||
ExampleTask( ) { m_SetSize = size_; }
|
||||
|
||||
void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override {
|
||||
// See full example in Priorities.cpp
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// This example demonstrates how to run a long running task alongside tasks
|
||||
// which must complete as early as possible using priorities.
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
g_TS.Initialize();
|
||||
|
||||
ExampleTask lowPriorityTask( 10 );
|
||||
lowPriorityTask.m_Priority = enki::TASK_PRIORITY_LOW;
|
||||
|
||||
ExampleTask highPriorityTask( 1 );
|
||||
highPriorityTask.m_Priority = enki::TASK_PRIORITY_HIGH;
|
||||
|
||||
g_TS.AddTaskSetToPipe( &lowPriorityTask );
|
||||
for( int task = 0; task < 10; ++task )
|
||||
{
|
||||
// run high priority tasks
|
||||
g_TS.AddTaskSetToPipe( &highPriorityTask );
|
||||
|
||||
// wait for task but only run tasks of the same priority or higher on this thread
|
||||
g_TS.WaitforTask( &highPriorityTask, highPriorityTask.m_Priority );
|
||||
}
|
||||
// wait for low priority task, run any tasks on this thread whilst waiting
|
||||
g_TS.WaitforTask( &lowPriorityTask );
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Pinned Tasks usage in C++
|
||||
- full example in [example/PinnedTask.cpp](example/PinnedTask.cpp)
|
||||
- C example in [example/PinnedTask_c.c](example/PinnedTask_c.c)
|
||||
```C
|
||||
#include "TaskScheduler.h"
|
||||
|
||||
enki::TaskScheduler g_TS;
|
||||
|
||||
// define a task set, can ignore range if we only do one thing
|
||||
struct PinnedTask : enki::IPinnedTask {
|
||||
void Execute() override {
|
||||
// do something here, can issue tasks with g_TS
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
g_TS.Initialize();
|
||||
PinnedTask task; //default constructor sets thread for pinned task to 0 (main thread)
|
||||
g_TS.AddPinnedTask( &task );
|
||||
|
||||
// RunPinnedTasks must be called on main thread to run any pinned tasks for that thread.
|
||||
// Tasking threads automatically do this in their task loop.
|
||||
g_TS.RunPinnedTasks();
|
||||
|
||||
// wait for task set (running tasks if they exist)
|
||||
// since we've just added it and it has no range we'll likely run it.
|
||||
g_TS.WaitforTask( &task );
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Dependency usage in C++
|
||||
- full example in [example/Dependencies.cpp](example/Dependencies.cpp)
|
||||
- C example in [example/Dependencies_c.c](example/Dependencies_c.c)
|
||||
```C
|
||||
#include "TaskScheduler.h"
|
||||
|
||||
enki::TaskScheduler g_TS;
|
||||
|
||||
// define a task set, can ignore range if we only do one thing
|
||||
struct TaskA : enki::ITaskSet {
|
||||
void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override {
|
||||
// do something here, can issue tasks with g_TS
|
||||
}
|
||||
};
|
||||
|
||||
struct TaskB : enki::ITaskSet {
|
||||
enki::Dependency m_Dependency;
|
||||
void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override {
|
||||
// do something here, can issue tasks with g_TS
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
g_TS.Initialize();
|
||||
|
||||
// set dependencies once (can set more than one if needed).
|
||||
TaskA taskA;
|
||||
TaskB taskB;
|
||||
taskB.SetDependency( taskB.m_Dependency, &taskA );
|
||||
|
||||
g_TS.AddTaskSetToPipe( &taskA ); // add first task
|
||||
g_TS.WaitforTask( &taskB ); // wait for last
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### External task thread usage in C++
|
||||
- full example in [example/ExternalTaskThread.cpp](example/ExternalTaskThread.cpp)
|
||||
- C example in [example/ExternalTaskThread_c.c](example/ExternalTaskThread_c.c)
|
||||
```C
|
||||
#include "TaskScheduler.h"
|
||||
|
||||
enki::TaskScheduler g_TS;
|
||||
struct ParallelTaskSet : ITaskSet
|
||||
{
|
||||
void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override {
|
||||
// Do something
|
||||
}
|
||||
};
|
||||
|
||||
void threadFunction()
|
||||
{
|
||||
g_TS.RegisterExternalTaskThread();
|
||||
|
||||
// sleep for a while instead of doing something such as file IO
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( num_ * 100 ) );
|
||||
|
||||
ParallelTaskSet task;
|
||||
g_TS.AddTaskSetToPipe( &task );
|
||||
g_TS.WaitforTask( &task);
|
||||
|
||||
g_TS.DeRegisterExternalTaskThread();
|
||||
}
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
enki::TaskSchedulerConfig config;
|
||||
config.numExternalTaskThreads = 1; // we have one extra external thread
|
||||
|
||||
g_TS.Initialize( config );
|
||||
|
||||
std::thread exampleThread( threadFunction );
|
||||
|
||||
exampleThread.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### WaitForPinnedTasks thread usage in C++ (useful for IO threads)
|
||||
- full example in [example/WaitForNewPinnedTasks.cpp](example/WaitForNewPinnedTasks.cpp)
|
||||
- C example in [example/WaitForNewPinnedTasks_c.c](example/WaitForNewPinnedTasks_c.c)
|
||||
```C++
|
||||
#include "TaskScheduler.h"
|
||||
|
||||
enki::TaskScheduler g_TS;
|
||||
|
||||
struct RunPinnedTaskLoopTask : enki::IPinnedTask
|
||||
{
|
||||
void Execute() override
|
||||
{
|
||||
while( !g_TS.GetIsShutdownRequested() )
|
||||
{
|
||||
g_TS.WaitForNewPinnedTasks(); // this thread will 'sleep' until there are new pinned tasks
|
||||
g_TS.RunPinnedTasks();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct PretendDoFileIO : enki::IPinnedTask
|
||||
{
|
||||
void Execute() override
|
||||
{
|
||||
// Do file IO
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
enki::TaskSchedulerConfig config;
|
||||
|
||||
// In this example we create more threads than the hardware can run,
|
||||
// because the IO thread will spend most of it's time idle or blocked
|
||||
// and therefore not scheduled for CPU time by the OS
|
||||
config.numTaskThreadsToCreate += 1;
|
||||
|
||||
g_TS.Initialize( config );
|
||||
|
||||
// in this example we place our IO threads at the end
|
||||
RunPinnedTaskLoopTask runPinnedTaskLoopTasks;
|
||||
runPinnedTaskLoopTasks.threadNum = g_TS.GetNumTaskThreads() - 1;
|
||||
g_TS.AddPinnedTask( &runPinnedTaskLoopTasks );
|
||||
|
||||
// Send pretend file IO task to external thread FILE_IO
|
||||
PretendDoFileIO pretendDoFileIO;
|
||||
pretendDoFileIO.threadNum = runPinnedTaskLoopTasks.threadNum;
|
||||
g_TS.AddPinnedTask( &pretendDoFileIO );
|
||||
|
||||
// ensure runPinnedTaskLoopTasks complete by explicitly calling shutdown
|
||||
g_TS.WaitforAllAndShutdown();
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Bindings
|
||||
|
||||
- Odin [enkiTS Odin bindings](https://github.com/nadako/odin-enkiTS) by @nadako
|
||||
|
||||
## Deprecated
|
||||
|
||||
[The C++98 compatible branch](https://github.com/dougbinks/enkiTS/tree/C++98) has been deprecated as I'm not aware of anyone needing it.
|
||||
|
||||
The user thread versions are no longer being maintained as they are no longer in use. Similar functionality can be obtained with the externalTaskThreads
|
||||
* [User thread version on Branch UserThread](https://github.com/dougbinks/enkiTS/tree/UserThread) for running enkiTS on other tasking / threading systems, so it can be used as in other engines as well as standalone for example.
|
||||
* [C++ 11 version of user threads on Branch UserThread_C++11](https://github.com/dougbinks/enkiTS/tree/UserThread_C++11)
|
||||
|
||||
## Projects using enkiTS
|
||||
|
||||
### [Avoyd](https://www.avoyd.com)
|
||||
Avoyd is an abstract 6 degrees of freedom voxel game. enkiTS was developed for use in our [in-house engine powering Avoyd](https://www.enkisoftware.com/faq#engine).
|
||||
|
||||

|
||||
|
||||
### [Imogen](https://github.com/CedricGuillemet/Imogen)
|
||||
GPU/CPU Texture Generator
|
||||
|
||||

|
||||
|
||||
### [ToyPathRacer](https://github.com/aras-p/ToyPathTracer)
|
||||
Aras Pranckevičius' code for his series on [Daily Path Tracer experiments with various languages](https://aras-p.info/blog/2018/03/28/Daily-Pathtracer-Part-0-Intro/).
|
||||
|
||||
.
|
||||
|
||||
### [Mastering Graphics Programming with Vulkan](https://github.com/PacktPublishing/Mastering-Graphics-Programming-with-Vulkan)
|
||||
Marco Castorina and Gabriel Sassone's book on developing a modern rendering engine from first principles using the Vulkan API. enkiTS is used as the task library to distribute work across cores.
|
||||
|
||||

|
||||
|
||||
## License (zlib)
|
||||
|
||||
Copyright (c) 2013-2020 Doug Binks
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgement in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
@@ -0,0 +1,240 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub const Options = struct {
|
||||
pub const max_task_priorities = 5;
|
||||
|
||||
shared: bool,
|
||||
build_examples: bool,
|
||||
task_priorities: u32,
|
||||
|
||||
const defaults = Options{
|
||||
.shared = false,
|
||||
.build_examples = false,
|
||||
.task_priorities = 0,
|
||||
};
|
||||
|
||||
pub fn getOptions(b: *std.Build) !Options {
|
||||
const task_priorities = b.option(u32, "task_priorities", "Number of task priorities, 1-5, 0 for defined by defaults in source") orelse defaults.task_priorities;
|
||||
if (task_priorities > max_task_priorities) {
|
||||
std.log.err("task_priorities must be in range 0-{d}", .{max_task_priorities});
|
||||
return error.InvalidTaskPriorities;
|
||||
}
|
||||
|
||||
return .{
|
||||
.shared = b.option(bool, "shared", "Compile as shared library") orelse defaults.shared,
|
||||
.build_examples = b.option(bool, "build_examples", "Build examples") orelse defaults.build_examples,
|
||||
.task_priorities = task_priorities,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn applyTaskPriorities(options: Options, b: *std.Build, compile: *std.Build.Step.Compile) !void {
|
||||
if (options.task_priorities > 0) {
|
||||
const task_priorities_str = try std.fmt.allocPrint(b.allocator, "{d}", .{options.task_priorities});
|
||||
// leak task_priorities_str as we dont really care in build scripts and lifetime needs to be longer ...
|
||||
compile.root_module.addCMacro("ENKITS_TASK_PRIORITIES_NUM", task_priorities_str);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
const options = try Options.getOptions(b);
|
||||
const lib = try compileEnkiTS(b, target, optimize, options);
|
||||
|
||||
b.installArtifact(lib);
|
||||
|
||||
if (options.build_examples) {
|
||||
try buildExamples(b, target, optimize, lib, options);
|
||||
}
|
||||
}
|
||||
|
||||
fn compileEnkiTS(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, options: Options) !*std.Build.Step.Compile {
|
||||
const module = b.addModule("enki_ts", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libcpp = true,
|
||||
});
|
||||
|
||||
const linkage: std.builtin.LinkMode = if (options.shared) .dynamic else .static;
|
||||
const enki_ts = b.addLibrary(.{
|
||||
.root_module = module,
|
||||
.name = "enki_ts",
|
||||
.linkage = linkage,
|
||||
});
|
||||
|
||||
const cpp_source_files = &[_][]const u8{
|
||||
"src/LockLessMultiReadPipe.h",
|
||||
"src/TaskScheduler_c.h",
|
||||
"src/TaskScheduler_c.cpp",
|
||||
"src/TaskScheduler.cpp",
|
||||
"src/TaskScheduler.h",
|
||||
};
|
||||
|
||||
enki_ts.installHeader(b.path("src/TaskScheduler_c.h"), "TaskScheduler_c.h");
|
||||
enki_ts.installHeader(b.path("src/TaskScheduler.h"), "TaskScheduler.h");
|
||||
|
||||
var flags_arr = std.ArrayList([]const u8).empty;
|
||||
defer flags_arr.deinit(b.allocator);
|
||||
|
||||
const target_os = target.query.os_tag orelse builtin.target.os.tag;
|
||||
if (target_os == .macos) {
|
||||
try flags_arr.append(b.allocator, "-stdlib=libc++");
|
||||
} else {
|
||||
try flags_arr.append(b.allocator, "-std=c++11");
|
||||
}
|
||||
|
||||
if (target_os == .linux) {
|
||||
try flags_arr.append(b.allocator, "-pthread");
|
||||
}
|
||||
|
||||
if (options.shared) {
|
||||
try flags_arr.appendSlice(b.allocator, &[_][]const u8{
|
||||
"-fPIC",
|
||||
"-DBUILD_LIBTYPE_SHARED",
|
||||
});
|
||||
|
||||
enki_ts.root_module.addCMacro("ENKITS_BUILD_DLL", "1");
|
||||
enki_ts.root_module.addCMacro("ENKITS_DLL", "1");
|
||||
}
|
||||
|
||||
try options.applyTaskPriorities(b, enki_ts);
|
||||
|
||||
enki_ts.root_module.addCSourceFiles(.{
|
||||
.files = cpp_source_files,
|
||||
.flags = flags_arr.items,
|
||||
.language = .cpp,
|
||||
});
|
||||
|
||||
return enki_ts;
|
||||
}
|
||||
|
||||
fn buildExamples(
|
||||
b: *std.Build,
|
||||
target: std.Build.ResolvedTarget,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
enki_ts: *std.Build.Step.Compile,
|
||||
options: Options,
|
||||
) !void {
|
||||
inline for (&[_][]const u8{
|
||||
"CompletionAction",
|
||||
"CustomAllocator",
|
||||
"Dependencies",
|
||||
"ExternalTaskThread",
|
||||
"LambdaTask",
|
||||
"ParallelSum",
|
||||
"PinnedTask",
|
||||
"TaskOverhead",
|
||||
"TaskThroughput",
|
||||
"TestAll",
|
||||
"TestWaitforTask",
|
||||
"WaitForNewPinnedTasks",
|
||||
}) |cpp_example_name| {
|
||||
try buildCppExample(b, target, optimize, enki_ts, options, cpp_example_name);
|
||||
}
|
||||
|
||||
inline for (&[_][]const u8{
|
||||
"CompletionAction_c",
|
||||
"CustomAllocator_c",
|
||||
"Dependencies_c",
|
||||
"ExternalTaskThread_c",
|
||||
"ParallelSum_c",
|
||||
"PinnedTask_c",
|
||||
"WaitForNewPinnedTasks_c",
|
||||
}) |c_example_name| {
|
||||
try buildCExample(b, target, optimize, enki_ts, options, c_example_name);
|
||||
}
|
||||
|
||||
if (options.task_priorities > 0) {
|
||||
try buildCppExample(b, target, optimize, enki_ts, options, "Priorities");
|
||||
try buildCExample(b, target, optimize, enki_ts, options, "Priorities_c");
|
||||
}
|
||||
}
|
||||
|
||||
fn buildCppExample(
|
||||
b: *std.Build,
|
||||
target: std.Build.ResolvedTarget,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
enki_ts: *std.Build.Step.Compile,
|
||||
options: Options,
|
||||
comptime example_name: []const u8,
|
||||
) !void {
|
||||
const exe_mod = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = example_name,
|
||||
.root_module = exe_mod,
|
||||
});
|
||||
|
||||
const example_path = "example/" ++ example_name ++ ".cpp";
|
||||
const source_files = [_][]const u8{example_path};
|
||||
|
||||
exe.root_module.addCSourceFiles(.{
|
||||
.files = &source_files,
|
||||
.flags = &[_][]const u8{
|
||||
"-std=c++11",
|
||||
},
|
||||
.language = .cpp,
|
||||
});
|
||||
|
||||
try options.applyTaskPriorities(b, exe);
|
||||
|
||||
// link with lib
|
||||
exe.root_module.linkLibrary(enki_ts);
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
addExampleRunStep(b, exe, example_name);
|
||||
}
|
||||
|
||||
fn buildCExample(
|
||||
b: *std.Build,
|
||||
target: std.Build.ResolvedTarget,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
enki_ts: *std.Build.Step.Compile,
|
||||
options: Options,
|
||||
comptime example_name: []const u8,
|
||||
) !void {
|
||||
const exe_mod = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = example_name,
|
||||
.root_module = exe_mod,
|
||||
});
|
||||
|
||||
const example_path = "example/" ++ example_name ++ ".c";
|
||||
const source_files = [_][]const u8{example_path};
|
||||
|
||||
exe.root_module.addCSourceFiles(.{
|
||||
.files = &source_files,
|
||||
.flags = &[_][]const u8{},
|
||||
.language = .c,
|
||||
});
|
||||
|
||||
try options.applyTaskPriorities(b, exe);
|
||||
|
||||
// link with lib
|
||||
exe.root_module.linkLibrary(enki_ts);
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
addExampleRunStep(b, exe, example_name);
|
||||
}
|
||||
|
||||
fn addExampleRunStep(
|
||||
b: *std.Build,
|
||||
exe: *std.Build.Step.Compile,
|
||||
comptime example_name: []const u8,
|
||||
) void {
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
const run_step = b.step("run" ++ "-" ++ example_name, "Run the " ++ example_name ++ " example");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
.{
|
||||
.name = .enkiTS,
|
||||
.version = "1.11.0",
|
||||
.fingerprint = 0x486deed5a741584a, // Changing this has security and trust implications.
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.dependencies = .{},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
"README.md",
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright (c) 2020 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
using namespace enki;
|
||||
|
||||
TaskScheduler g_TS;
|
||||
|
||||
static std::atomic<int32_t> gs_CountAsRun = {0};
|
||||
static std::atomic<int32_t> gs_CountAsDeleted = {0};
|
||||
static std::atomic<int32_t> gs_CountBsRun = {0};
|
||||
static std::atomic<int32_t> gs_CountBsDeleted = {0};
|
||||
|
||||
struct CompletionActionDelete : ICompletable
|
||||
{
|
||||
Dependency m_Dependency;
|
||||
|
||||
// We override OnDependenciesComplete to provide an 'action' which occurs after
|
||||
// the dependency task is complete.
|
||||
void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ )
|
||||
{
|
||||
// Call base class OnDependenciesComplete BEFORE deleting depedent task or self
|
||||
ICompletable::OnDependenciesComplete( pTaskScheduler_, threadNum_ );
|
||||
|
||||
printf("CompletionActionDelete::OnDependenciesComplete called on thread %u\n", threadNum_ );
|
||||
|
||||
// In this example we delete the dependency, which is safe to do as the task
|
||||
// manager will not dereference it at this point.
|
||||
// However the dependency task should have no other dependents,
|
||||
// This class can have dependencies.
|
||||
delete m_Dependency.GetDependencyTask(); // also deletes this as member
|
||||
}
|
||||
};
|
||||
|
||||
struct SelfDeletingTaskB : ITaskSet
|
||||
{
|
||||
SelfDeletingTaskB()
|
||||
{
|
||||
m_TaskDeleter.SetDependency( m_TaskDeleter.m_Dependency, this );
|
||||
}
|
||||
|
||||
~SelfDeletingTaskB()
|
||||
{
|
||||
++gs_CountBsDeleted;
|
||||
printf("~SelfDeletingTaskB() called on thread %u\n\n", g_TS.GetThreadNum() );
|
||||
}
|
||||
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
if( 0 == range_.start )
|
||||
{
|
||||
// whilst would normally loop over range_ doing work here we want to only output info once per task
|
||||
++gs_CountBsRun;
|
||||
printf("SelfDeletingTaskB on thread %u with set size %u\n", threadnum_, m_SetSize);
|
||||
}
|
||||
}
|
||||
|
||||
CompletionActionDelete m_TaskDeleter;
|
||||
Dependency m_Dependency;
|
||||
};
|
||||
|
||||
struct CompletionActionModifyDependentTaskAndDelete : ICompletable
|
||||
{
|
||||
Dependency m_Dependency;
|
||||
|
||||
ITaskSet* m_pTaskToModify = nullptr;
|
||||
|
||||
// We override OnDependenciesComplete to provide an 'action' which occurs after
|
||||
// the dependency task is complete.
|
||||
void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ )
|
||||
{
|
||||
// Modify following task before calling OnDependenciesComplete
|
||||
m_pTaskToModify->m_SetSize = 10;
|
||||
|
||||
// Call base class OnDependenciesComplete AFTER modifying any depedent task
|
||||
ICompletable::OnDependenciesComplete( pTaskScheduler_, threadNum_ );
|
||||
|
||||
printf("CompletionActionModifyDependentTaskAndDelete::OnDependenciesComplete called on thread %u\n", threadNum_ );
|
||||
|
||||
// In this example we delete the dependency, which is safe to do as the task
|
||||
// manager will not dereference it at this point.
|
||||
// However the dependency task should have no other dependents,
|
||||
// This class can have dependencies.
|
||||
delete m_Dependency.GetDependencyTask(); // also deletes this as member
|
||||
}
|
||||
};
|
||||
|
||||
struct SelfDeletingTaskA : ITaskSet
|
||||
{
|
||||
SelfDeletingTaskA()
|
||||
{
|
||||
m_TaskModifyAndDelete.SetDependency( m_TaskModifyAndDelete.m_Dependency, this );
|
||||
SelfDeletingTaskB* pNextTask = new SelfDeletingTaskB();
|
||||
|
||||
// we set the dependency of pNextTask on the task deleter, not on this
|
||||
pNextTask->SetDependency( pNextTask->m_Dependency, &m_TaskModifyAndDelete );
|
||||
|
||||
// Set the completion actions task to modify to be the following task
|
||||
m_TaskModifyAndDelete.m_pTaskToModify = pNextTask;
|
||||
}
|
||||
|
||||
~SelfDeletingTaskA()
|
||||
{
|
||||
++gs_CountAsDeleted;
|
||||
printf("~SelfDeletingTaskA() called on thread %u\n\n", g_TS.GetThreadNum() );
|
||||
}
|
||||
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
(void)range_;
|
||||
++gs_CountAsRun;
|
||||
printf("SelfDeletingTaskA on thread %u with set size %u\n", threadnum_, m_SetSize);
|
||||
}
|
||||
|
||||
CompletionActionModifyDependentTaskAndDelete m_TaskModifyAndDelete;
|
||||
};
|
||||
|
||||
static const int RUNS = 10;
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
// This examples shows CompletionActions used to modify a following tasks parameters and delete tasks
|
||||
// Task Graph for this example (with names shortened to fit on screen):
|
||||
//
|
||||
// pTaskSetA
|
||||
// ->pCompletionActionA-Modify-ICompletable::OnDependenciesComplete-Delete
|
||||
// ->pTaskSetB
|
||||
// ->pCompletionActionB-ICompletable::OnDependenciesComplete-Delete
|
||||
//
|
||||
// Note that pTaskSetB must depend on pCompletionActionA NOT pTaskSetA or it could run at the same time as pCompletionActionA
|
||||
// so cannot be modified.
|
||||
|
||||
g_TS.Initialize();
|
||||
|
||||
for( int run = 0; run< RUNS; ++run )
|
||||
{
|
||||
g_TS.AddTaskSetToPipe( new SelfDeletingTaskA() );
|
||||
}
|
||||
g_TS.WaitforAllAndShutdown();
|
||||
|
||||
printf("%d As run, %d deleted\n%d Bs run, %d deleted.", gs_CountAsRun.load(), gs_CountAsDeleted.load(), gs_CountBsRun.load(), gs_CountBsDeleted.load() );
|
||||
|
||||
if( gs_CountAsRun != gs_CountAsDeleted ||
|
||||
gs_CountBsRun != gs_CountBsDeleted ||
|
||||
gs_CountAsRun != gs_CountBsRun )
|
||||
{
|
||||
printf("ERROR\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// Copyright (c) 2020 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler_c.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
enkiTaskScheduler* pETS;
|
||||
|
||||
struct CompletionArgs_ModifyTask
|
||||
{
|
||||
enkiTaskSet* pTaskB;
|
||||
uint32_t run;
|
||||
};
|
||||
|
||||
struct CompletionArgs_DeleteTask
|
||||
{
|
||||
enkiTaskSet* pTask;
|
||||
enkiDependency* pDependency; // in this example only 1 or 0 dependencies, but generally could be an array
|
||||
enkiCompletionAction* pCompletionAction;
|
||||
uint32_t run; // only required for example output, not needed for a general purpose delete task
|
||||
};
|
||||
|
||||
// In this example all our TaskSet functions share the same args struct, but we could use different one
|
||||
struct TaskSetArgs
|
||||
{
|
||||
enkiTaskSet* pTask;
|
||||
const char* name;
|
||||
uint32_t run;
|
||||
};
|
||||
|
||||
void CompletionFunctionPreComplete_ModifyDependentTask( void* pArgs_, uint32_t threadNum_ )
|
||||
{
|
||||
struct CompletionArgs_ModifyTask* pCompletionArgs_ModifyTask = pArgs_;
|
||||
struct enkiParamsTaskSet paramsTaskNext = enkiGetParamsTaskSet( pCompletionArgs_ModifyTask->pTaskB );
|
||||
|
||||
printf("CompletionFunctionPreComplete_ModifyDependentTask for run %u running on thread %u\n",
|
||||
pCompletionArgs_ModifyTask->run, threadNum_ );
|
||||
|
||||
// in this function we can modify the parameters of any task which depends on this CompletionFunction
|
||||
// pre complete functions should not be used to delete the current CompletionAction, for that use PostComplete functions
|
||||
paramsTaskNext.setSize = 10; // modify the set size of the next task - for example this could be based on output from previous task
|
||||
enkiSetParamsTaskSet( pCompletionArgs_ModifyTask->pTaskB, paramsTaskNext );
|
||||
|
||||
free( pCompletionArgs_ModifyTask );
|
||||
}
|
||||
|
||||
|
||||
void CompletionFunctionPostComplete_DeleteTask( void* pArgs_, uint32_t threadNum_ )
|
||||
{
|
||||
struct CompletionArgs_DeleteTask* pCompletionArgs_DeleteTask = pArgs_;
|
||||
|
||||
printf("CompletionFunctionPostComplete_DeleteTask for run %u running on thread %u\n",
|
||||
pCompletionArgs_DeleteTask->run, threadNum_ );
|
||||
|
||||
// can free memory in post complete
|
||||
|
||||
// note must delete a dependency before you delete the dependency task and the task to run on completion
|
||||
if( pCompletionArgs_DeleteTask->pDependency )
|
||||
{
|
||||
enkiDeleteDependency( pETS, pCompletionArgs_DeleteTask->pDependency );
|
||||
}
|
||||
|
||||
free( enkiGetParamsTaskSet( pCompletionArgs_DeleteTask->pTask ).pArgs );
|
||||
enkiDeleteTaskSet( pETS, pCompletionArgs_DeleteTask->pTask );
|
||||
|
||||
enkiDeleteCompletionAction( pETS, pCompletionArgs_DeleteTask->pCompletionAction );
|
||||
|
||||
// safe to free our own args in this example as no other function dereferences them
|
||||
free( pCompletionArgs_DeleteTask );
|
||||
}
|
||||
|
||||
void TaskSetFunc( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ )
|
||||
{
|
||||
(void)start_; (void)end_;
|
||||
struct TaskSetArgs* pTaskSetArgs = pArgs_;
|
||||
struct enkiParamsTaskSet paramsTaskNext = enkiGetParamsTaskSet( pTaskSetArgs->pTask );
|
||||
if( 0 == start_ )
|
||||
{
|
||||
// for clarity in this example we only output one printf per taskset func called, but would normally loop from start_ to end_ doing work
|
||||
printf("Task %s for run %u running on thread %u has set size %u\n", pTaskSetArgs->name, pTaskSetArgs->run, threadnum_, paramsTaskNext.setSize);
|
||||
}
|
||||
|
||||
// A TastSetFunction is not a safe place to free it's own pArgs_ as when the setSize > 1 there may be multiple
|
||||
// calls to this function with the same pArgs_
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
// This examples shows CompletionActions used to modify a following tasks parameters and free allocations
|
||||
// Task Graph for this example (with names shortened to fit on screen):
|
||||
//
|
||||
// pTaskSetA
|
||||
// ->pCompletionActionA-PreFunc-PostFunc
|
||||
// ->pTaskSetB
|
||||
// ->pCompletionActionB-(no PreFunc)-PostFunc
|
||||
//
|
||||
// Note that pTaskSetB must depend on pCompletionActionA NOT pTaskSetA or it could run at the same time as pCompletionActionA
|
||||
// so cannot be modified.
|
||||
|
||||
struct enkiTaskSet* pTaskSetA;
|
||||
struct enkiCompletionAction* pCompletionActionA;
|
||||
struct enkiTaskSet* pTaskSetB;
|
||||
struct enkiCompletionAction* pCompletionActionB;
|
||||
struct TaskSetArgs* pTaskSetArgsA;
|
||||
struct CompletionArgs_ModifyTask* pCompletionArgsA;
|
||||
struct enkiParamsCompletionAction paramsCompletionActionA;
|
||||
struct TaskSetArgs* pTaskSetArgsB;
|
||||
struct enkiDependency* pDependencyOfTaskSetBOnCompletionActionA;
|
||||
struct CompletionArgs_DeleteTask* pCompletionArgs_DeleteTaskA;
|
||||
struct CompletionArgs_DeleteTask* pCompletionArgs_DeleteTaskB;
|
||||
struct enkiParamsCompletionAction paramsCompletionActionB;
|
||||
int run;
|
||||
|
||||
pETS = enkiNewTaskScheduler();
|
||||
enkiInitTaskScheduler( pETS );
|
||||
|
||||
for( run=0; run<10; ++run )
|
||||
{
|
||||
// Create all this runs tasks and completion actions
|
||||
pTaskSetA = enkiCreateTaskSet( pETS, TaskSetFunc );
|
||||
pCompletionActionA = enkiCreateCompletionAction( pETS,
|
||||
CompletionFunctionPreComplete_ModifyDependentTask,
|
||||
CompletionFunctionPostComplete_DeleteTask );
|
||||
pTaskSetB = enkiCreateTaskSet( pETS, TaskSetFunc );
|
||||
pCompletionActionB = enkiCreateCompletionAction( pETS,
|
||||
NULL,
|
||||
CompletionFunctionPostComplete_DeleteTask );
|
||||
|
||||
// Set args for TaskSetA
|
||||
pTaskSetArgsA = malloc(sizeof(struct TaskSetArgs));
|
||||
pTaskSetArgsA->run = run;
|
||||
pTaskSetArgsA->pTask = pTaskSetA;
|
||||
pTaskSetArgsA->name = "A";
|
||||
enkiSetArgsTaskSet( pTaskSetA, pTaskSetArgsA );
|
||||
|
||||
// Set args for CompletionActionA, and make dependent on TaskSetA through pDependency
|
||||
pCompletionArgsA = malloc(sizeof(struct CompletionArgs_ModifyTask));
|
||||
pCompletionArgsA->pTaskB = pTaskSetB;
|
||||
pCompletionArgsA->run = run;
|
||||
pCompletionArgs_DeleteTaskA = malloc(sizeof(struct CompletionArgs_DeleteTask));
|
||||
pCompletionArgs_DeleteTaskA->pTask = pTaskSetA;
|
||||
pCompletionArgs_DeleteTaskA->pCompletionAction = pCompletionActionA;
|
||||
pCompletionArgs_DeleteTaskA->pDependency = NULL;
|
||||
pCompletionArgs_DeleteTaskA->run = run;
|
||||
|
||||
paramsCompletionActionA = enkiGetParamsCompletionAction( pCompletionActionA );
|
||||
paramsCompletionActionA.pArgsPreComplete = pCompletionArgsA;
|
||||
paramsCompletionActionA.pArgsPostComplete = pCompletionArgs_DeleteTaskA;
|
||||
paramsCompletionActionA.pDependency = enkiGetCompletableFromTaskSet( pTaskSetA );
|
||||
enkiSetParamsCompletionAction( pCompletionActionA, paramsCompletionActionA );
|
||||
|
||||
|
||||
// Set args for TaskSetB
|
||||
pTaskSetArgsB = malloc(sizeof(struct TaskSetArgs));
|
||||
pTaskSetArgsB->run = run;
|
||||
pTaskSetArgsB->pTask = pTaskSetB;
|
||||
pTaskSetArgsB->name = "B";
|
||||
enkiSetArgsTaskSet( pTaskSetB, pTaskSetArgsB );
|
||||
|
||||
// TaskSetB depends on pCompletionActionA
|
||||
pDependencyOfTaskSetBOnCompletionActionA = enkiCreateDependency( pETS );
|
||||
enkiSetDependency( pDependencyOfTaskSetBOnCompletionActionA,
|
||||
enkiGetCompletableFromCompletionAction( pCompletionActionA ),
|
||||
enkiGetCompletableFromTaskSet( pTaskSetB ) );
|
||||
|
||||
// Set args for CompletionActionB, and make dependent on TaskSetB through pDependency
|
||||
pCompletionArgs_DeleteTaskB = malloc(sizeof(struct CompletionArgs_DeleteTask));
|
||||
pCompletionArgs_DeleteTaskB->pTask = pTaskSetB;
|
||||
pCompletionArgs_DeleteTaskB->pDependency = pDependencyOfTaskSetBOnCompletionActionA;
|
||||
pCompletionArgs_DeleteTaskB->pCompletionAction = pCompletionActionB;
|
||||
pCompletionArgs_DeleteTaskB->run = run;
|
||||
|
||||
paramsCompletionActionB = enkiGetParamsCompletionAction( pCompletionActionB );
|
||||
paramsCompletionActionB.pArgsPreComplete = NULL; // pCompletionActionB does not have a PreComplete function
|
||||
paramsCompletionActionB.pArgsPostComplete = pCompletionArgs_DeleteTaskB;
|
||||
paramsCompletionActionB.pDependency = enkiGetCompletableFromTaskSet( pTaskSetB );
|
||||
enkiSetParamsCompletionAction( pCompletionActionB, paramsCompletionActionB );
|
||||
|
||||
|
||||
// To launch all, we only add the first TaskSet
|
||||
enkiAddTaskSet( pETS, pTaskSetA );
|
||||
}
|
||||
enkiWaitForAll( pETS );
|
||||
|
||||
enkiDeleteTaskScheduler( pETS );
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2019 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <thread>
|
||||
|
||||
using namespace enki;
|
||||
|
||||
TaskScheduler g_TS;
|
||||
|
||||
struct ParallelTaskSet : ITaskSet
|
||||
{
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
printf(" This could run on any thread, currently thread %d\n", threadnum_);
|
||||
}
|
||||
};
|
||||
|
||||
struct CustomData
|
||||
{
|
||||
const char* domainName;
|
||||
size_t totalAllocations;
|
||||
};
|
||||
|
||||
void* CustomAllocFunc( size_t align_, size_t size_, void* userData_, const char* file_, int line_ )
|
||||
{
|
||||
CustomData* data = (CustomData*)userData_;
|
||||
data->totalAllocations += size_;
|
||||
|
||||
// We don't need to use this macro as file_ and line_ will be valid and printable just not useful
|
||||
// But for this example it makes prettier output :)
|
||||
#ifdef ENKI_CUSTOM_ALLOC_FILE_AND_LINE
|
||||
printf("Allocating %g bytes with alignment %g in domain %s, total %g. File %s, line %d.\n",
|
||||
(double)size_, (double)align_, data->domainName, (double)data->totalAllocations, file_, line_ );
|
||||
#else
|
||||
printf("Allocating %g bytes with alignment %g in domain %s, total %g.\n",
|
||||
(double)size_, (double)align_, data->domainName, (double)data->totalAllocations );
|
||||
#endif
|
||||
|
||||
return DefaultAllocFunc( align_, size_, userData_, file_, line_ );
|
||||
};
|
||||
|
||||
void CustomFreeFunc( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ )
|
||||
{
|
||||
CustomData* data = (CustomData*)userData_;
|
||||
data->totalAllocations -= size_;
|
||||
|
||||
// we don't need to use this macro as file_ and line_ will be valid and printable just not useful
|
||||
// But for this example it makes prettier output :)
|
||||
#ifdef ENKI_CUSTOM_ALLOC_FILE_AND_LINE
|
||||
printf("Freeing %p in domain %s, total %g. File %s, line %d.\n",
|
||||
ptr_, data->domainName, (double)data->totalAllocations, file_, line_ );
|
||||
#else
|
||||
(void)file_; (void)line_;
|
||||
printf("Freeing %p in domain %s, total %g.\n",
|
||||
ptr_, data->domainName, (double)data->totalAllocations );
|
||||
#endif
|
||||
|
||||
DefaultFreeFunc( ptr_, size_, userData_, file_, line_ );
|
||||
};
|
||||
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
enki::TaskSchedulerConfig config;
|
||||
config.customAllocator.alloc = CustomAllocFunc;
|
||||
config.customAllocator.free = CustomFreeFunc;
|
||||
CustomData data{ "enkITS", 0 };
|
||||
config.customAllocator.userData = &data;
|
||||
|
||||
g_TS.Initialize( config );
|
||||
|
||||
ParallelTaskSet task;
|
||||
g_TS.AddTaskSetToPipe( &task );
|
||||
g_TS.WaitforTask( &task );
|
||||
g_TS.WaitforAllAndShutdown(); // ensure we shutdown before user data is destroyed.
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2019 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler_c.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
enkiTaskScheduler* pETS;
|
||||
enkiTaskSet* pParallelTask;
|
||||
|
||||
size_t totalAllocations = 0;
|
||||
|
||||
void ParallelFunc( uint32_t start_, uint32_t end, uint32_t threadnum_, void* pArgs_ )
|
||||
{
|
||||
// do something
|
||||
printf("ParallelFunc running on thread %d (could be any thread)\n", threadnum_ );
|
||||
}
|
||||
|
||||
void* CustomAllocFunc( size_t align_, size_t size_, void* userData_, const char* file_, int line_ )
|
||||
{
|
||||
totalAllocations += size_; // note this isn't thread safe, should use atomics in real application
|
||||
|
||||
// We don't need to use this macro as file_ and line_ will be valid and printable just not useful
|
||||
// But for this example it makes prettier output :)
|
||||
#ifdef ENKI_CUSTOM_ALLOC_FILE_AND_LINE
|
||||
printf("Allocating %g bytes in domain %s, total %g. File %s, line %d.\n",
|
||||
(double)size_, (const char*)userData_, (double)totalAllocations, file_, line_ );
|
||||
#else
|
||||
(void)file_; (void)line_;
|
||||
printf("Allocating %g bytes in domain %s, total %g.\n",
|
||||
(double)size_, (const char*)userData_, (double)totalAllocations );
|
||||
#endif
|
||||
|
||||
return enkiDefaultAllocFunc( align_, size_, userData_, file_, line_ );
|
||||
};
|
||||
|
||||
void CustomFreeFunc( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ )
|
||||
{
|
||||
totalAllocations -= size_; // note this isn't thread safe, should use atomics in real application
|
||||
|
||||
// We don't need to use this macro as file_ and line_ will be valid and printable just not useful
|
||||
// But for this example it makes prettier output :)
|
||||
#ifdef ENKI_CUSTOM_ALLOC_FILE_AND_LINE
|
||||
printf("Freeing %p in domain %s, total %g. File %s, line %d.\n",
|
||||
ptr_, (const char*)userData_, (double)totalAllocations, file_, line_ );
|
||||
#else
|
||||
(void)file_; (void)line_;
|
||||
printf("Freeing %p in domain %s, total %g.\n",
|
||||
ptr_, (const char*)userData_, (double)totalAllocations );
|
||||
#endif
|
||||
|
||||
enkiDefaultFreeFunc( ptr_, size_, userData_, file_, line_ );
|
||||
};
|
||||
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
struct enkiCustomAllocator customAllocator;
|
||||
|
||||
customAllocator.alloc = CustomAllocFunc;
|
||||
customAllocator.free = CustomFreeFunc;
|
||||
customAllocator.userData = (void*)"enkiTS";
|
||||
|
||||
pETS = enkiNewTaskSchedulerWithCustomAllocator( customAllocator );
|
||||
enkiInitTaskScheduler( pETS );
|
||||
|
||||
pParallelTask = enkiCreateTaskSet( pETS, ParallelFunc );
|
||||
|
||||
enkiAddTaskSet( pETS, pParallelTask );
|
||||
enkiWaitForTaskSet( pETS, pParallelTask );
|
||||
|
||||
enkiDeleteTaskSet( pETS, pParallelTask );
|
||||
|
||||
enkiDeleteTaskScheduler( pETS );
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright (c) 2020 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
#include "Timer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
using namespace enki;
|
||||
|
||||
TaskScheduler g_TS;
|
||||
|
||||
|
||||
#include <vector>
|
||||
|
||||
// We use a task to launch the first task in the task graph
|
||||
// as adding the tasks with many dependencies incurs an overhead,
|
||||
// which we might not want to occur on the main thread
|
||||
struct TaskLauncher : ITaskSet
|
||||
{
|
||||
ITaskSet* m_pTaskToLaunch = NULL;
|
||||
void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override
|
||||
{
|
||||
(void)range;
|
||||
g_TS.AddTaskSetToPipe( m_pTaskToLaunch );
|
||||
}
|
||||
};
|
||||
|
||||
struct TaskA : ITaskSet
|
||||
{
|
||||
void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override
|
||||
{
|
||||
(void)range;
|
||||
printf("A on thread %u\n", threadnum);
|
||||
}
|
||||
};
|
||||
|
||||
struct TaskB : ITaskSet
|
||||
{
|
||||
Dependency m_Dependency;
|
||||
|
||||
void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override
|
||||
{
|
||||
(void)range;
|
||||
printf("B on thread %u\n", threadnum);
|
||||
}
|
||||
};
|
||||
|
||||
struct TaskC : IPinnedTask
|
||||
{
|
||||
Dependency m_Dependencies[4];
|
||||
|
||||
void Execute() override
|
||||
{
|
||||
printf("C Pinned task on thread %u, should be %u\n", g_TS.GetThreadNum(), threadNum );
|
||||
}
|
||||
};
|
||||
|
||||
struct TaskD : ITaskSet
|
||||
{
|
||||
Dependency m_Dependency;
|
||||
|
||||
void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override
|
||||
{
|
||||
(void)range;
|
||||
printf("D on thread %u\n", threadnum);
|
||||
}
|
||||
};
|
||||
|
||||
// If you need to wait on multiple dependencies, but don't need to do anything
|
||||
// you can derive from ICompletable and add depedencies
|
||||
struct TasksFinished : ICompletable
|
||||
{
|
||||
std::vector<Dependency> m_Dependencies;
|
||||
Dependency m_DepencyOnLauncher; // could also store this in array above
|
||||
};
|
||||
|
||||
static const int RUNS = 20;
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
g_TS.Initialize();
|
||||
|
||||
// construct the graph once
|
||||
TaskA taskA;
|
||||
|
||||
TaskB taskBs[4];
|
||||
for( auto& task : taskBs )
|
||||
{
|
||||
task.SetDependency(task.m_Dependency,&taskA);
|
||||
}
|
||||
|
||||
TaskC taskC; // Task C is a pinned task, defaults to running on thread 0 (this thread)
|
||||
taskC.SetDependenciesArr( taskC.m_Dependencies, taskBs );
|
||||
|
||||
TaskD taskDs[10];
|
||||
for( auto& task : taskDs )
|
||||
{
|
||||
task.SetDependency(task.m_Dependency,&taskC);
|
||||
}
|
||||
|
||||
TasksFinished tasksFinished;
|
||||
tasksFinished.SetDependenciesVec( tasksFinished.m_Dependencies, taskDs );
|
||||
|
||||
TaskLauncher taskLauncher;
|
||||
taskLauncher.m_pTaskToLaunch = &taskA; //start with task A
|
||||
|
||||
// we need to add a dependency on the launcher to tasksFinished otherwise
|
||||
// tasksFinished might be labelled as complete before taskA launched
|
||||
tasksFinished.SetDependency( tasksFinished.m_DepencyOnLauncher, &taskLauncher );
|
||||
|
||||
// run graph many times
|
||||
for( int run = 0; run< RUNS; ++run )
|
||||
{
|
||||
printf("Run %d / %d.....\n", run+1, RUNS);
|
||||
|
||||
g_TS.AddTaskSetToPipe( &taskLauncher );
|
||||
|
||||
g_TS.WaitforTask( &tasksFinished );
|
||||
printf("Tasks Finished\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2020 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler_c.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
enkiTaskScheduler* pETS;
|
||||
|
||||
|
||||
void TaskSetFunc( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ )
|
||||
{
|
||||
(void)start_; (void)end_;
|
||||
char* str = (char*)pArgs_;
|
||||
printf("%s on thread %u\n", str, threadnum_);
|
||||
}
|
||||
|
||||
void PinnedTaskFunc( void* pArgs_ )
|
||||
{
|
||||
char* str = (char*)pArgs_;
|
||||
printf("%s Pinned task on thread 0, should be %u\n", str, enkiGetThreadNum( pETS ) );
|
||||
}
|
||||
|
||||
|
||||
#define NUM_TASK_B 4
|
||||
#define NUM_TASK_D 2
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
int run;
|
||||
enkiTaskSet* pTaskA;
|
||||
enkiTaskSet* pTaskB[NUM_TASK_B];
|
||||
enkiDependency* pTaskBDependencyToA[NUM_TASK_B];
|
||||
enkiPinnedTask* pPinnedTaskC;
|
||||
enkiDependency* pPinnedTaskCDependencyToBs[NUM_TASK_B];
|
||||
enkiTaskSet* pTaskD[NUM_TASK_D];
|
||||
enkiDependency* pTaskDDependencyToC[NUM_TASK_D];
|
||||
enkiCompletable* pCompletableFinished; // A completable can be used on it's own to check if tasks complete.
|
||||
enkiDependency* pDependencyToD[NUM_TASK_D];
|
||||
|
||||
pETS = enkiNewTaskScheduler();
|
||||
enkiInitTaskScheduler( pETS );
|
||||
|
||||
// create tasks and set dependencies once, reuse many times
|
||||
pTaskA = enkiCreateTaskSet( pETS, TaskSetFunc );
|
||||
enkiSetArgsTaskSet( pTaskA, "A" );
|
||||
for( int i=0; i<NUM_TASK_B; ++i )
|
||||
{
|
||||
pTaskB[i] = enkiCreateTaskSet( pETS, TaskSetFunc );
|
||||
enkiSetArgsTaskSet( pTaskB[i], "B" );
|
||||
pTaskBDependencyToA[i] = enkiCreateDependency( pETS );
|
||||
enkiSetDependency(
|
||||
pTaskBDependencyToA[i],
|
||||
enkiGetCompletableFromTaskSet( pTaskA ),
|
||||
enkiGetCompletableFromTaskSet( pTaskB[i] )
|
||||
);
|
||||
}
|
||||
pPinnedTaskC = enkiCreatePinnedTask( pETS, PinnedTaskFunc, 0 );
|
||||
enkiSetArgsPinnedTask( pPinnedTaskC, "C" );
|
||||
for( int i=0; i<NUM_TASK_B; ++i )
|
||||
{
|
||||
pPinnedTaskCDependencyToBs[i] = enkiCreateDependency( pETS );
|
||||
enkiSetDependency(
|
||||
pPinnedTaskCDependencyToBs[i],
|
||||
enkiGetCompletableFromTaskSet( pTaskB[i] ),
|
||||
enkiGetCompletableFromPinnedTask( pPinnedTaskC )
|
||||
);
|
||||
}
|
||||
for( int i=0; i<NUM_TASK_D; ++i )
|
||||
{
|
||||
pTaskD[i] = enkiCreateTaskSet( pETS, TaskSetFunc );
|
||||
enkiSetArgsTaskSet( pTaskD[i], "D" );
|
||||
pTaskDDependencyToC[i] = enkiCreateDependency( pETS );
|
||||
enkiSetDependency(
|
||||
pTaskDDependencyToC[i],
|
||||
enkiGetCompletableFromPinnedTask( pPinnedTaskC ),
|
||||
enkiGetCompletableFromTaskSet( pTaskD[i] )
|
||||
);
|
||||
}
|
||||
pCompletableFinished = enkiCreateCompletable( pETS );
|
||||
for( int i=0; i<NUM_TASK_D; ++i )
|
||||
{
|
||||
pDependencyToD[i] = enkiCreateDependency( pETS );
|
||||
enkiSetDependency(
|
||||
pDependencyToD[i],
|
||||
enkiGetCompletableFromTaskSet( pTaskD[i] ),
|
||||
pCompletableFinished
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// run task graph as many times as you like by adding first task,
|
||||
// and waiting for last (if needed).
|
||||
for( run=0; run<10; ++run )
|
||||
{
|
||||
printf("Starting run %d\n", run);
|
||||
enkiAddTaskSet( pETS, pTaskA );
|
||||
enkiWaitForCompletable( pETS, pCompletableFinished );
|
||||
printf("FINISHED run %d\n", run);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// new delete functions require task scheduler argument
|
||||
// as this reduces memory requirements
|
||||
for( int i=0; i<NUM_TASK_D; ++i )
|
||||
{
|
||||
enkiDeleteDependency( pETS, pDependencyToD[i] );
|
||||
}
|
||||
enkiDeleteCompletable( pETS, pCompletableFinished );
|
||||
for( int i=0; i<NUM_TASK_D; ++i )
|
||||
{
|
||||
enkiDeleteDependency( pETS, pTaskDDependencyToC[i] );
|
||||
enkiDeleteTaskSet( pETS, pTaskD[i] );
|
||||
}
|
||||
for( int i=0; i<NUM_TASK_B; ++i )
|
||||
{
|
||||
enkiDeleteDependency( pETS, pPinnedTaskCDependencyToBs[i] );
|
||||
}
|
||||
enkiDeletePinnedTask( pETS, pPinnedTaskC );
|
||||
for( int i=0; i<NUM_TASK_B; ++i )
|
||||
{
|
||||
enkiDeleteDependency( pETS, pTaskBDependencyToA[i] );
|
||||
enkiDeleteTaskSet( pETS, pTaskB[i] );
|
||||
}
|
||||
enkiDeleteTaskSet( pETS, pTaskA );
|
||||
|
||||
|
||||
enkiDeleteTaskScheduler( pETS );
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2019 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <thread>
|
||||
|
||||
using namespace enki;
|
||||
|
||||
TaskScheduler g_TS;
|
||||
static std::atomic<int32_t> g_Run;
|
||||
|
||||
|
||||
struct ParallelTaskSet : ITaskSet
|
||||
{
|
||||
ParallelTaskSet() { m_SetSize = 100; }
|
||||
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
printf(" Run %d: This could run on any thread, currently thread %d\n", g_Run.load(), threadnum_);
|
||||
|
||||
// sleep used as a 'pretend' workload
|
||||
std::chrono::milliseconds sleepTime( range_.end - range_.start );
|
||||
std::this_thread::sleep_for( sleepTime );
|
||||
}
|
||||
};
|
||||
|
||||
// Example thread function
|
||||
// May want to use threads for blocking IO, during which enkiTS task threads can do work
|
||||
void threadFunction( uint32_t num_ )
|
||||
{
|
||||
bool bRegistered = g_TS.RegisterExternalTaskThread();
|
||||
assert( bRegistered );
|
||||
if( bRegistered )
|
||||
{
|
||||
// sleep for a while instead of doing something such as file IO
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( num_ * 100 ) );
|
||||
|
||||
|
||||
ParallelTaskSet task;
|
||||
g_TS.AddTaskSetToPipe( &task );
|
||||
g_TS.WaitforTask( &task);
|
||||
g_TS.DeRegisterExternalTaskThread();
|
||||
}
|
||||
}
|
||||
|
||||
static const int REPEATS = 5;
|
||||
static const uint32_t NUMEXTERNALTHREADS = 5;
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
enki::TaskSchedulerConfig config;
|
||||
config.numExternalTaskThreads = NUMEXTERNALTHREADS;
|
||||
|
||||
std::thread threads[NUMEXTERNALTHREADS];
|
||||
g_TS.Initialize( config );
|
||||
|
||||
|
||||
for( g_Run = 0; g_Run< REPEATS; ++g_Run )
|
||||
{
|
||||
printf("Run %d\n", g_Run.load() );
|
||||
|
||||
for( uint32_t iThread = 0; iThread < NUMEXTERNALTHREADS; ++iThread )
|
||||
{
|
||||
threads[ iThread ] = std::thread( threadFunction, iThread );
|
||||
}
|
||||
|
||||
// check that out of order Deregister / Register works...
|
||||
threads[ 0 ].join();
|
||||
threads[ 0 ] = std::thread( threadFunction, 0 );
|
||||
|
||||
for( uint32_t iThread = 0; iThread < NUMEXTERNALTHREADS; ++iThread )
|
||||
{
|
||||
threads[ iThread ].join();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) 2019 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler_c.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
// XPLATF Thread handling functions for C
|
||||
#ifdef _WIN32
|
||||
|
||||
#define NOMINMAX
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
typedef HANDLE threadid_t;
|
||||
#define THREADFUNC_DECL DWORD WINAPI
|
||||
|
||||
// declare the thread start function as:
|
||||
// THREADFUNC_DECL MyThreadStart( void* pArg );
|
||||
int32_t ThreadCreate( threadid_t* returnid, DWORD ( WINAPI *StartFunc) (void* ), void* pArg )
|
||||
{
|
||||
DWORD threadid;
|
||||
*returnid = CreateThread( 0, 0, StartFunc, pArg, 0, &threadid );
|
||||
return *returnid != NULL;
|
||||
}
|
||||
|
||||
int32_t ThreadJoin( threadid_t threadid )
|
||||
{
|
||||
return WaitForSingleObject( threadid, INFINITE ) == 0;
|
||||
}
|
||||
|
||||
#else // posix
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef pthread_t threadid_t;
|
||||
#define THREADFUNC_DECL void*
|
||||
|
||||
// declare the thread start function as:
|
||||
// THREADFUNC_DECL MyThreadStart( void* pArg );
|
||||
int32_t ThreadCreate( threadid_t* returnid, void* ( *StartFunc) (void* ), void* pArg )
|
||||
{
|
||||
int32_t retval = pthread_create( returnid, NULL, StartFunc, pArg );
|
||||
|
||||
return retval == 0;
|
||||
}
|
||||
|
||||
int32_t ThreadJoin( threadid_t threadid )
|
||||
{
|
||||
return pthread_join( threadid, NULL ) == 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
enkiTaskScheduler* pETS;
|
||||
enkiTaskSet* pParallelTask;
|
||||
|
||||
|
||||
void ParallelFunc( uint32_t start_, uint32_t end, uint32_t threadnum_, void* pArgs_ )
|
||||
{
|
||||
// do something
|
||||
printf("ParallelFunc running on thread %d (could be any thread)\n", threadnum_ );
|
||||
}
|
||||
|
||||
THREADFUNC_DECL ThreadFunc( void* pArgs_ )
|
||||
{
|
||||
uint32_t threadNum;
|
||||
int retVal;
|
||||
|
||||
retVal = enkiRegisterExternalTaskThread( pETS );
|
||||
assert( retVal );
|
||||
|
||||
threadNum = enkiGetThreadNum( pETS );
|
||||
assert( threadNum == 1 );
|
||||
printf("ThreadFunc running on thread %d (should be thread 1)\n", threadNum );
|
||||
|
||||
pParallelTask = enkiCreateTaskSet( pETS, ParallelFunc );
|
||||
|
||||
enkiAddTaskSet( pETS, pParallelTask );
|
||||
enkiWaitForTaskSet( pETS, pParallelTask );
|
||||
|
||||
enkiDeleteTaskSet( pETS, pParallelTask );
|
||||
|
||||
enkiDeRegisterExternalTaskThread( pETS );
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
struct enkiTaskSchedulerConfig config;
|
||||
|
||||
pETS = enkiNewTaskScheduler();
|
||||
|
||||
// get default config and request one external thread
|
||||
config = enkiGetTaskSchedulerConfig( pETS );
|
||||
config.numExternalTaskThreads = 1;
|
||||
enkiInitTaskSchedulerWithConfig( pETS, config );
|
||||
|
||||
threadid_t threadID;
|
||||
ThreadCreate( &threadID, ThreadFunc, NULL );
|
||||
|
||||
ThreadJoin( threadID );
|
||||
|
||||
|
||||
enkiDeleteTaskScheduler( pETS );
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2013 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
#include "Timer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
|
||||
// lambda example
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
enki::TaskScheduler g_TS;
|
||||
g_TS.Initialize();
|
||||
|
||||
enki::TaskSet task( 1024, []( enki::TaskSetPartition range, uint32_t threadnum ) { printf("Thread %d, start %d, end %d\n", threadnum, range.start, range.end ); } );
|
||||
|
||||
g_TS.AddTaskSetToPipe( &task );
|
||||
g_TS.WaitforTask( &task );
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
// Copyright (c) 2013 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
#include "Timer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace enki;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
TaskScheduler g_TS;
|
||||
|
||||
struct ParallelSumTaskSet : ITaskSet
|
||||
{
|
||||
struct Count
|
||||
{
|
||||
// prevent false sharing.
|
||||
uint64_t count;
|
||||
char cacheline[64];
|
||||
};
|
||||
Count* m_pPartialSums;
|
||||
uint32_t m_NumPartialSums;
|
||||
|
||||
ParallelSumTaskSet( uint32_t size_ ) : m_pPartialSums(NULL), m_NumPartialSums(0) { m_SetSize = size_; m_MinRange = 10 * 1024; }
|
||||
virtual ~ParallelSumTaskSet()
|
||||
{
|
||||
delete[] m_pPartialSums;
|
||||
}
|
||||
|
||||
void Init( uint32_t numPartialSums_ )
|
||||
{
|
||||
delete[] m_pPartialSums;
|
||||
m_NumPartialSums =numPartialSums_ ;
|
||||
m_pPartialSums = new Count[ m_NumPartialSums ];
|
||||
memset( m_pPartialSums, 0, sizeof(Count)*m_NumPartialSums );
|
||||
}
|
||||
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
assert( m_pPartialSums && m_NumPartialSums );
|
||||
uint64_t sum = m_pPartialSums[threadnum_].count;
|
||||
for( uint64_t i = range_.start; i < range_.end; ++i )
|
||||
{
|
||||
sum += i + 1;
|
||||
}
|
||||
m_pPartialSums[threadnum_].count = sum;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct ParallelReductionSumTaskSet : ITaskSet
|
||||
{
|
||||
ParallelSumTaskSet* m_pParallelSum;
|
||||
Dependency m_Dependency;
|
||||
uint64_t m_FinalSum;
|
||||
|
||||
ParallelReductionSumTaskSet( ParallelSumTaskSet* pParallelSum_ ) : m_pParallelSum( pParallelSum_ ), m_Dependency( pParallelSum_, this ), m_FinalSum(0)
|
||||
{
|
||||
}
|
||||
|
||||
void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override
|
||||
{
|
||||
for( uint32_t i = 0; i < m_pParallelSum->m_NumPartialSums; ++i )
|
||||
{
|
||||
m_FinalSum += m_pParallelSum->m_pPartialSums[i].count;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static const int WARMUPS = 10;
|
||||
static const int RUNS = 20;
|
||||
static const int REPEATS = RUNS + WARMUPS;
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
uint32_t maxThreads = enki::GetNumHardwareThreads();
|
||||
double* avTimes = new double[ maxThreads ];
|
||||
|
||||
|
||||
// start by measuring serial
|
||||
double avSerial = 0.0f;
|
||||
uint32_t setSize = 100 * 1024 * 1024;
|
||||
uint64_t sumSerial;
|
||||
for( int run = 0; run< REPEATS; ++run )
|
||||
{
|
||||
Timer tSerial;
|
||||
tSerial.Start();
|
||||
|
||||
ParallelSumTaskSet serialTask( setSize );
|
||||
serialTask.Init( 1 );
|
||||
TaskSetPartition range = { 0, setSize };
|
||||
|
||||
serialTask.ExecuteRange( range, 0 );
|
||||
sumSerial = serialTask.m_pPartialSums[0].count;
|
||||
|
||||
tSerial.Stop();
|
||||
|
||||
if( run >= WARMUPS )
|
||||
{
|
||||
avSerial += tSerial.GetTimeMS() / RUNS;
|
||||
}
|
||||
printf("Serial Example complete in \t%fms,\t sum: %" PRIu64 "\n", tSerial.GetTimeMS(), sumSerial );
|
||||
}
|
||||
|
||||
// now measure parallel
|
||||
for( uint32_t numThreads = 1; numThreads <= maxThreads; ++numThreads )
|
||||
{
|
||||
g_TS.Initialize(numThreads);
|
||||
double avTime = 0.0;
|
||||
|
||||
for( int run = 0; run< REPEATS; ++run )
|
||||
{
|
||||
|
||||
printf("Run %d.....\n", run);
|
||||
Timer tParallel;
|
||||
tParallel.Start();
|
||||
|
||||
ParallelSumTaskSet parallelSumTask( setSize );
|
||||
parallelSumTask.Init( g_TS.GetNumTaskThreads() );
|
||||
ParallelReductionSumTaskSet parallelReductionSumTaskSet( ¶llelSumTask );
|
||||
|
||||
g_TS.AddTaskSetToPipe( ¶llelSumTask );
|
||||
g_TS.WaitforTask( ¶llelReductionSumTaskSet );
|
||||
|
||||
tParallel.Stop();
|
||||
|
||||
|
||||
printf("Parallel Example complete in \t%fms,\t sum: %" PRIu64 "\n", tParallel.GetTimeMS(), parallelReductionSumTaskSet.m_FinalSum );
|
||||
|
||||
if( run >= WARMUPS )
|
||||
{
|
||||
avTime += tParallel.GetTimeMS() / RUNS;
|
||||
}
|
||||
|
||||
if( sumSerial != parallelReductionSumTaskSet.m_FinalSum )
|
||||
{
|
||||
printf( "ERROR: sums do not match\n" );
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Speed Up Serial / Parallel: %f\n\n", avSerial / tParallel.GetTimeMS() );
|
||||
|
||||
}
|
||||
avTimes[numThreads-1] = avTime;
|
||||
printf("\nAverage Time for %d Hardware Threads Serial / Parallel: %f, Speed Up over serial: %f\n", numThreads, avTime, avSerial / avTime );
|
||||
}
|
||||
|
||||
printf("\nTime Serial (no task system): %f\n", avSerial );
|
||||
printf("\nHardware Threads, Av Time, Av Speed Up over serial, Av Speed up over 1 thread\n" );
|
||||
for( uint32_t numThreads = 1; numThreads <= maxThreads; ++numThreads )
|
||||
{
|
||||
printf("%d, %f, %f, %f\n", numThreads, avTimes[numThreads-1], avSerial / avTimes[numThreads-1], avTimes[0] / avTimes[numThreads-1] );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) 2013 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler_c.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
enkiTaskScheduler* pETS;
|
||||
|
||||
|
||||
typedef struct ParallelSumTaskSetArgs
|
||||
{
|
||||
uint64_t* pPartialSums;
|
||||
uint32_t numPartialSums;
|
||||
} ParallelSumTaskSetArgs;
|
||||
|
||||
void ParallelSumTaskSetFunc( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ )
|
||||
{
|
||||
ParallelSumTaskSetArgs args;
|
||||
uint64_t sum, i;
|
||||
|
||||
args = *(ParallelSumTaskSetArgs*)pArgs_;
|
||||
|
||||
sum = args.pPartialSums[threadnum_];
|
||||
for( i = start_; i < end_; ++i )
|
||||
{
|
||||
sum += i + 1;
|
||||
}
|
||||
args.pPartialSums[threadnum_] = sum;
|
||||
}
|
||||
|
||||
typedef struct ParallelReductionSumTaskSetArgs
|
||||
{
|
||||
ParallelSumTaskSetArgs sumArgs;
|
||||
uint32_t numParrallelSums;
|
||||
enkiTaskSet* pPSumTask;
|
||||
uint64_t sum;
|
||||
} ParallelReductionSumTaskSetArgs;
|
||||
|
||||
void ParallelReductionSumTaskSetFunc( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ )
|
||||
{
|
||||
ParallelReductionSumTaskSetArgs* pArgs;
|
||||
uint64_t sum, i;
|
||||
|
||||
pArgs = (ParallelReductionSumTaskSetArgs*)pArgs_;
|
||||
|
||||
sum = 0;
|
||||
for( i = 0; i < pArgs->sumArgs.numPartialSums; ++i )
|
||||
{
|
||||
sum += pArgs->sumArgs.pPartialSums[i];
|
||||
}
|
||||
|
||||
pArgs->sum = sum;
|
||||
}
|
||||
|
||||
static const uint32_t REPEATS = 10;
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
uint32_t run;
|
||||
uint64_t i, serialSum;
|
||||
ParallelReductionSumTaskSetArgs parRedSumTaskSetArgs;
|
||||
enkiTaskSet* pPSumTask;
|
||||
enkiTaskSet* pPSumReductionTask;
|
||||
enkiDependency* pDependencypPSumReductionOnpPSum;
|
||||
|
||||
pETS = enkiNewTaskScheduler();
|
||||
enkiInitTaskScheduler( pETS );
|
||||
|
||||
pPSumTask = enkiCreateTaskSet( pETS, ParallelSumTaskSetFunc );
|
||||
pPSumReductionTask = enkiCreateTaskSet( pETS, ParallelReductionSumTaskSetFunc );
|
||||
pDependencypPSumReductionOnpPSum = enkiCreateDependency( pETS );
|
||||
enkiSetDependency( pDependencypPSumReductionOnpPSum,
|
||||
enkiGetCompletableFromTaskSet( pPSumTask ),
|
||||
enkiGetCompletableFromTaskSet( pPSumReductionTask ) );
|
||||
|
||||
parRedSumTaskSetArgs.pPSumTask = pPSumTask;
|
||||
parRedSumTaskSetArgs.numParrallelSums = 10 * 1024 * 1024;
|
||||
parRedSumTaskSetArgs.sumArgs.numPartialSums = enkiGetNumTaskThreads( pETS );
|
||||
parRedSumTaskSetArgs.sumArgs.pPartialSums = (uint64_t*)malloc(
|
||||
sizeof(uint64_t) * parRedSumTaskSetArgs.sumArgs.numPartialSums );
|
||||
enkiSetArgsTaskSet( pPSumReductionTask, &parRedSumTaskSetArgs );
|
||||
|
||||
struct enkiParamsTaskSet parSumTaskParams = enkiGetParamsTaskSet( pPSumTask );
|
||||
parSumTaskParams.pArgs = &parRedSumTaskSetArgs.sumArgs;
|
||||
parSumTaskParams.setSize = parRedSumTaskSetArgs.numParrallelSums;
|
||||
enkiSetParamsTaskSet( pPSumTask, parSumTaskParams );
|
||||
|
||||
for( run = 0; run< REPEATS; ++run )
|
||||
{
|
||||
// reset partial sums
|
||||
memset( parRedSumTaskSetArgs.sumArgs.pPartialSums, 0,
|
||||
sizeof(uint64_t) * parRedSumTaskSetArgs.sumArgs.numPartialSums );
|
||||
|
||||
// add first task, parallel sum
|
||||
enkiAddTaskSet( pETS, pPSumTask );
|
||||
|
||||
// wait for reduction which will run due to dependencies
|
||||
enkiWaitForTaskSet( pETS, pPSumReductionTask );
|
||||
|
||||
printf("Parallel Example complete sum: \t %llu\n", (long long unsigned int)parRedSumTaskSetArgs.sum );
|
||||
|
||||
serialSum = 0;
|
||||
for( i = 0; i < parRedSumTaskSetArgs.numParrallelSums; ++i )
|
||||
{
|
||||
serialSum += i + 1;
|
||||
}
|
||||
|
||||
printf("Serial Example complete sum: \t %llu\n", (long long unsigned int)serialSum );
|
||||
|
||||
if( serialSum != parRedSumTaskSetArgs.sum )
|
||||
{
|
||||
printf("ERROR: Serial sum does not match parallel sum\n");
|
||||
}
|
||||
}
|
||||
|
||||
free( parRedSumTaskSetArgs.sumArgs.pPartialSums );
|
||||
|
||||
enkiDeleteDependency( pETS, pDependencypPSumReductionOnpPSum );
|
||||
enkiDeleteTaskSet( pETS, pPSumReductionTask );
|
||||
enkiDeleteTaskSet( pETS, pPSumTask );
|
||||
|
||||
enkiDeleteTaskScheduler( pETS );
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) 2013 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace enki;
|
||||
|
||||
TaskScheduler g_TS;
|
||||
|
||||
|
||||
struct PinnedTaskHelloWorld : IPinnedTask
|
||||
{
|
||||
PinnedTaskHelloWorld()
|
||||
: IPinnedTask(0) // set pinned thread to 0
|
||||
{}
|
||||
void Execute() override
|
||||
{
|
||||
printf("This will run on the main thread\n");
|
||||
}
|
||||
};
|
||||
|
||||
struct ParallelTaskSet : ITaskSet
|
||||
{
|
||||
PinnedTaskHelloWorld pinnedTask;
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
g_TS.AddPinnedTask( &pinnedTask );
|
||||
|
||||
printf("This could run on any thread, currently thread %d\n", threadnum_);
|
||||
|
||||
g_TS.WaitforTask( &pinnedTask );
|
||||
}
|
||||
};
|
||||
|
||||
static const int REPEATS = 5;
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
g_TS.Initialize();
|
||||
|
||||
for( int run = 0; run< REPEATS; ++run )
|
||||
{
|
||||
|
||||
ParallelTaskSet task;
|
||||
g_TS.AddTaskSetToPipe( &task );
|
||||
|
||||
printf("Task %d added\n", run );
|
||||
|
||||
// RunPinnedTasks must be called on main thread to run any pinned tasks for that thread.
|
||||
// Tasking threads automatically do this in their task loop.
|
||||
g_TS.RunPinnedTasks();
|
||||
|
||||
g_TS.WaitforTask( &task);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2013 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler_c.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
enkiTaskScheduler* pETS;
|
||||
enkiTaskSet* pParallelTask;
|
||||
enkiPinnedTask* pPinnedTask;
|
||||
|
||||
static const int REPEATS = 5;
|
||||
|
||||
void PinnedTaskFunc( void* pArgs_ )
|
||||
{
|
||||
printf("This will run on the main thread\n");
|
||||
}
|
||||
|
||||
void ParallelTaskSetFunc( uint32_t start_, uint32_t end, uint32_t threadnum_, void* pArgs_ )
|
||||
{
|
||||
enkiAddPinnedTask( pETS, pPinnedTask );
|
||||
printf("This could run on any thread, currently thread %d\n", threadnum_);
|
||||
enkiWaitForPinnedTask( pETS, pPinnedTask );
|
||||
}
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
int run;
|
||||
pETS = enkiNewTaskScheduler();
|
||||
enkiInitTaskScheduler( pETS );
|
||||
|
||||
pParallelTask = enkiCreateTaskSet( pETS, ParallelTaskSetFunc );
|
||||
pPinnedTask = enkiCreatePinnedTask( pETS, PinnedTaskFunc, 0 ); // pinned task is created for thread 0
|
||||
|
||||
|
||||
for( run = 0; run< REPEATS; ++run )
|
||||
{
|
||||
enkiAddTaskSet( pETS, pParallelTask );
|
||||
|
||||
enkiRunPinnedTasks( pETS );
|
||||
|
||||
enkiWaitForTaskSet( pETS, pParallelTask );
|
||||
}
|
||||
|
||||
enkiDeleteTaskSet( pETS, pParallelTask );
|
||||
enkiDeletePinnedTask( pETS, pPinnedTask );
|
||||
|
||||
enkiDeleteTaskScheduler( pETS );
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2019 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
#include "Timer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
enki::TaskScheduler g_TS;
|
||||
|
||||
struct ExampleTask : enki::ITaskSet
|
||||
{
|
||||
ExampleTask( uint32_t size_ ) { m_SetSize = size_; }
|
||||
|
||||
void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
if( m_Priority == enki::TASK_PRIORITY_LOW )
|
||||
{
|
||||
// fake slow task with timer
|
||||
Timer timer;
|
||||
timer.Start();
|
||||
double tWaittime = (double)( range_.end - range_.start ) * 100.;
|
||||
while( timer.GetTimeMS() < tWaittime )
|
||||
{
|
||||
}
|
||||
printf( "\tLOW PRIORITY TASK range complete: thread: %d, start: %d, end: %d\n",
|
||||
threadnum_, range_.start, range_.end );
|
||||
}
|
||||
else
|
||||
{
|
||||
printf( "HIGH PRIORITY TASK range complete: thread: %d, start: %d, end: %d\n",
|
||||
threadnum_, range_.start, range_.end );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// This example demonstrates how to run a long running task alongside tasks
|
||||
// which must complete as early as possible using priorities.
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
g_TS.Initialize();
|
||||
|
||||
ExampleTask lowPriorityTask( 10 );
|
||||
lowPriorityTask.m_Priority = enki::TASK_PRIORITY_LOW;
|
||||
|
||||
ExampleTask highPriorityTask( 1 );
|
||||
highPriorityTask.m_Priority = enki::TASK_PRIORITY_HIGH;
|
||||
|
||||
g_TS.AddTaskSetToPipe( &lowPriorityTask );
|
||||
for( int task = 0; task < 10; ++task )
|
||||
{
|
||||
// run high priority tasks
|
||||
g_TS.AddTaskSetToPipe( &highPriorityTask );
|
||||
|
||||
// wait for task but only run tasks of the same priority on this thread
|
||||
g_TS.WaitforTask( &highPriorityTask, highPriorityTask.m_Priority );
|
||||
}
|
||||
// wait for low priority task, run any tasks on this thread whilst waiting
|
||||
g_TS.WaitforTask( &lowPriorityTask );
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2013 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler_c.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
void LowPriorityTask( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ )
|
||||
{
|
||||
int wait = (int)( 0.1f * (float)( ( end_ - start_ ) * (float)CLOCKS_PER_SEC) );
|
||||
clock_t endTime = (clock_t)wait + clock();
|
||||
while( clock() < endTime )
|
||||
{
|
||||
}
|
||||
printf( "\tLOW PRIORITY TASK range complete: thread: %d, start: %d, end: %d\n",
|
||||
threadnum_, start_, end_ );
|
||||
}
|
||||
|
||||
void HighPriorityTask( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ )
|
||||
{
|
||||
printf( "HIGH PRIORITY TASK range complete: thread: %d, start: %d, end: %d\n",
|
||||
threadnum_, start_, end_ );
|
||||
}
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
int run;
|
||||
enkiTaskScheduler* pETS;
|
||||
enkiTaskSet* plowPriorityTask;
|
||||
enkiTaskSet* pHighPriorityTask;
|
||||
|
||||
pETS = enkiNewTaskScheduler();
|
||||
enkiInitTaskScheduler( pETS );
|
||||
|
||||
plowPriorityTask = enkiCreateTaskSet( pETS, LowPriorityTask );
|
||||
enkiSetPriorityTaskSet( plowPriorityTask, 1 ); // lower values higher priority
|
||||
enkiSetSetSizeTaskSet( plowPriorityTask, 10 );
|
||||
|
||||
pHighPriorityTask = enkiCreateTaskSet( pETS, HighPriorityTask );
|
||||
enkiSetPriorityTaskSet( pHighPriorityTask, 0 ); // lower values higher priority
|
||||
|
||||
enkiAddTaskSet( pETS, plowPriorityTask );
|
||||
for( run = 0; run < 10; ++run )
|
||||
{
|
||||
// run high priority tasks
|
||||
enkiAddTaskSet( pETS, pHighPriorityTask );
|
||||
|
||||
// wait for task but only run tasks of the same priority or higher on this thread
|
||||
enkiWaitForTaskSetPriority( pETS, pHighPriorityTask, 0 );
|
||||
}
|
||||
|
||||
// wait for low priority task, run any tasks on this thread whilst waiting
|
||||
enkiWaitForTaskSet( pETS, plowPriorityTask );
|
||||
|
||||
enkiDeleteTaskSet( pETS, plowPriorityTask );
|
||||
enkiDeleteTaskSet( pETS, pHighPriorityTask );
|
||||
|
||||
enkiDeleteTaskScheduler( pETS );
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright (c) 2013 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
#include "Timer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#include <inttypes.h>
|
||||
#undef __STDC_FORMAT_MACROS
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace enki;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
TaskScheduler g_TS;
|
||||
|
||||
struct ParallelSumTaskSet : ITaskSet
|
||||
{
|
||||
struct Count
|
||||
{
|
||||
// prevent false sharing.
|
||||
uint64_t count;
|
||||
char cacheline[64];
|
||||
};
|
||||
Count* m_pPartialSums;
|
||||
uint32_t m_NumPartialSums;
|
||||
|
||||
ParallelSumTaskSet( uint32_t size_ ) : m_pPartialSums(NULL), m_NumPartialSums(0) { m_SetSize = size_; m_MinRange = 1024; }
|
||||
virtual ~ParallelSumTaskSet()
|
||||
{
|
||||
delete[] m_pPartialSums;
|
||||
}
|
||||
|
||||
void Init( uint32_t numPartialSums_ )
|
||||
{
|
||||
delete[] m_pPartialSums;
|
||||
m_NumPartialSums =numPartialSums_ ;
|
||||
m_pPartialSums = new Count[ m_NumPartialSums ];
|
||||
memset( m_pPartialSums, 0, sizeof(Count)*m_NumPartialSums );
|
||||
}
|
||||
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
assert( m_pPartialSums && m_NumPartialSums );
|
||||
uint64_t sum = m_pPartialSums[threadnum_].count;
|
||||
for( uint64_t i = range_.start; i < range_.end; ++i )
|
||||
{
|
||||
sum += i + 1;
|
||||
}
|
||||
m_pPartialSums[threadnum_].count = sum;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct ParallelReductionSumTaskSet : ITaskSet
|
||||
{
|
||||
ParallelSumTaskSet* m_pParallelSum;
|
||||
Dependency m_Dependency;
|
||||
uint64_t m_FinalSum;
|
||||
|
||||
ParallelReductionSumTaskSet( ParallelSumTaskSet* pParallelSum_ ) : m_pParallelSum( pParallelSum_ ), m_Dependency( pParallelSum_, this ), m_FinalSum(0)
|
||||
{
|
||||
}
|
||||
|
||||
void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override
|
||||
{
|
||||
for( uint32_t i = 0; i < m_pParallelSum->m_NumPartialSums; ++i )
|
||||
{
|
||||
m_FinalSum += m_pParallelSum->m_pPartialSums[i].count;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static const int WARMUPS = 10;
|
||||
static const int RUNS = 100;
|
||||
static const int REPEATS = RUNS + WARMUPS;
|
||||
|
||||
static const int DATA_POINTS = 50;
|
||||
|
||||
uint32_t GetSetSizeForDataPoint( uint32_t dataPoint_ )
|
||||
{
|
||||
dataPoint_++;
|
||||
return dataPoint_ * dataPoint_ * 1024;
|
||||
}
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
double* avTimeTakenMS = new double[ DATA_POINTS ];
|
||||
double* avSerialTimeTakenMS = new double[ DATA_POINTS ];
|
||||
|
||||
|
||||
g_TS.Initialize();
|
||||
for( uint32_t dataPoint = 0; dataPoint < DATA_POINTS; ++dataPoint )
|
||||
{
|
||||
double avTimeMS = 0.0;
|
||||
uint32_t setSize = GetSetSizeForDataPoint( dataPoint );
|
||||
|
||||
double avSerialTimeMS = 0.0f;
|
||||
uint64_t sumSerial;
|
||||
for( int run = 0; run< REPEATS; ++run )
|
||||
{
|
||||
printf("Run %d.....\n", run);
|
||||
|
||||
// start by measuring serial
|
||||
ParallelSumTaskSet serialTask( setSize );
|
||||
serialTask.Init( 1 );
|
||||
TaskSetPartition range = { 0, setSize };
|
||||
|
||||
Timer tSerial;
|
||||
tSerial.Start();
|
||||
|
||||
serialTask.ExecuteRange( range, 0 );
|
||||
sumSerial = serialTask.m_pPartialSums[0].count;
|
||||
|
||||
tSerial.Stop();
|
||||
printf("Serial Example complete in \t%fms,\t sum: %" PRIu64 "\n", tSerial.GetTimeMS(), sumSerial );
|
||||
|
||||
|
||||
// Now measure parallel
|
||||
ParallelSumTaskSet parallelSumTask( setSize );
|
||||
parallelSumTask.Init( g_TS.GetNumTaskThreads() );
|
||||
ParallelReductionSumTaskSet parallelReductionSumTaskSet( ¶llelSumTask );
|
||||
|
||||
Timer tParallel;
|
||||
tParallel.Start();
|
||||
|
||||
g_TS.AddTaskSetToPipe( ¶llelSumTask );
|
||||
|
||||
g_TS.WaitforTask( ¶llelReductionSumTaskSet );
|
||||
|
||||
tParallel.Stop();
|
||||
printf("Parallel Example complete in \t%fms,\t sum: %" PRIu64 "\n", tParallel.GetTimeMS(), parallelReductionSumTaskSet.m_FinalSum );
|
||||
|
||||
if( sumSerial != parallelReductionSumTaskSet.m_FinalSum )
|
||||
{
|
||||
printf("ERROR, sums do not match\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if( run >= WARMUPS )
|
||||
{
|
||||
avSerialTimeMS += tSerial.GetTimeMS() / RUNS;
|
||||
avTimeMS += tParallel.GetTimeMS() / RUNS;
|
||||
}
|
||||
}
|
||||
avSerialTimeTakenMS[ dataPoint ] = avSerialTimeMS;
|
||||
avTimeTakenMS[ dataPoint ] = avTimeMS;
|
||||
printf("\nAverage time for set size %d: %fms parallel, %fms serial\n", setSize, (float)avTimeMS, (float)avSerialTimeMS );
|
||||
}
|
||||
|
||||
printf("\nSet Size,\tTime Parallel/ms,\tTime Serial/ms\n" );
|
||||
for( uint32_t dataPoint = 0; dataPoint < DATA_POINTS; ++dataPoint )
|
||||
{
|
||||
printf("%8d,\t%f,\t%f\n", GetSetSizeForDataPoint( dataPoint ), (float)avTimeTakenMS[ dataPoint ], (float)avSerialTimeTakenMS[ dataPoint ] );
|
||||
}
|
||||
|
||||
delete[] avSerialTimeTakenMS;
|
||||
delete[] avTimeTakenMS;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright (c) 2013 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
#include "Timer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <cstring>
|
||||
#include <string.h>
|
||||
|
||||
using namespace enki;
|
||||
|
||||
|
||||
const uint32_t numTasks = 1024*1024;
|
||||
|
||||
TaskScheduler g_TS;
|
||||
|
||||
|
||||
struct ConsumeTask : ITaskSet
|
||||
{
|
||||
static ConsumeTask tasks[numTasks];
|
||||
|
||||
struct Count
|
||||
{
|
||||
// prevent false sharing.
|
||||
uint32_t count;
|
||||
char cacheline[64];
|
||||
};
|
||||
static Count* pCount;
|
||||
static uint32_t numCount;
|
||||
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
++pCount[threadnum_].count;
|
||||
}
|
||||
|
||||
static void Init()
|
||||
{
|
||||
delete[] ConsumeTask::pCount;
|
||||
numCount = g_TS.GetNumTaskThreads();
|
||||
ConsumeTask::pCount = new Count[ numCount ];
|
||||
memset( pCount, 0, sizeof(Count) * numCount );
|
||||
}
|
||||
};
|
||||
|
||||
ConsumeTask ConsumeTask::tasks[numTasks];
|
||||
ConsumeTask::Count* ConsumeTask::pCount = NULL;
|
||||
uint32_t ConsumeTask::numCount = 0;
|
||||
|
||||
|
||||
|
||||
struct CreateTasks : ITaskSet
|
||||
{
|
||||
CreateTasks()
|
||||
{
|
||||
m_SetSize = numTasks;
|
||||
}
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
for( uint32_t i=range_.start; i <range_.end; ++i )
|
||||
{
|
||||
ConsumeTask& task = ConsumeTask::tasks[ i ];
|
||||
g_TS.AddTaskSetToPipe( &task );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
static const int WARMUPS = 5;
|
||||
static const int RUNS = 10;
|
||||
static const int REPEATS = RUNS + WARMUPS;
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
uint32_t maxThreads = enki::GetNumHardwareThreads();
|
||||
double* times = new double[ maxThreads ];
|
||||
double* stdev = new double[ maxThreads ];
|
||||
|
||||
|
||||
for( uint32_t numThreads = 1; numThreads <= maxThreads; ++numThreads )
|
||||
{
|
||||
g_TS.Initialize(numThreads);
|
||||
|
||||
double avTime = 0.0;
|
||||
double avTime2 = 0.0;
|
||||
uint32_t totalErrors = 0;
|
||||
for( int run = 0; run< REPEATS; ++run )
|
||||
{
|
||||
|
||||
printf("Run %d.....\n", run);
|
||||
Timer tParallel;
|
||||
CreateTasks createTask;
|
||||
ConsumeTask::Init();
|
||||
|
||||
tParallel.Start();
|
||||
|
||||
|
||||
g_TS.AddTaskSetToPipe( &createTask );
|
||||
|
||||
g_TS.WaitforAll();
|
||||
|
||||
tParallel.Stop();
|
||||
|
||||
|
||||
printf("Parallel Example complete in \t%fms, task rate: %f M tasks/s\n", tParallel.GetTimeMS(), numTasks / tParallel.GetTimeMS() / 1000.0f );
|
||||
|
||||
printf("Parallel Example error checking...");
|
||||
uint32_t numTasksDone = 0;
|
||||
for( uint32_t check = 0; check < ConsumeTask::numCount; ++check )
|
||||
{
|
||||
numTasksDone += ConsumeTask::pCount[check].count;
|
||||
}
|
||||
if( numTasksDone != numTasks )
|
||||
{
|
||||
printf("\n ERRORS FOUND - %d tasks not done!!!\n", numTasks - numTasksDone );
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" no errors found.\n" );
|
||||
}
|
||||
|
||||
if( run >= WARMUPS )
|
||||
{
|
||||
avTime2 += tParallel.GetTimeMS() * tParallel.GetTimeMS();
|
||||
avTime += tParallel.GetTimeMS() / RUNS;
|
||||
}
|
||||
}
|
||||
|
||||
printf("\nAverage Time for %d Hardware Threads: %fms, rate: %f M tasks/s. %d errors found.\n", numThreads, avTime, numTasks / avTime / 1000.0f, totalErrors );
|
||||
|
||||
times[numThreads-1] = avTime;
|
||||
stdev[numThreads-1] = sqrt(RUNS * avTime2 - (RUNS * avTime)*(RUNS * avTime)) / RUNS;
|
||||
}
|
||||
|
||||
printf("\nHardware Threads, Time, std, MTasks/s, Perf Multiplier\n" );
|
||||
for( uint32_t numThreads = 1; numThreads <= maxThreads; ++numThreads )
|
||||
{
|
||||
printf("%d, %f, %f, %f, %f\n", numThreads, times[numThreads-1], stdev[numThreads-1], numTasks / times[numThreads-1] / 1000.0f, times[0] / times[numThreads-1] );
|
||||
}
|
||||
|
||||
|
||||
delete[] times;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
// Copyright (c) 2020 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
#include "Timer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <string.h>
|
||||
|
||||
using namespace enki;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
TaskScheduler g_TS;
|
||||
uint32_t g_numTestsRun = 0;
|
||||
uint32_t g_numTestsSucceeded = 0;
|
||||
|
||||
void RunTestFunction( const char* pTestFuncName_, std::function<bool ()> TestFunc )
|
||||
{
|
||||
++g_numTestsRun;
|
||||
fprintf(stdout, "\nRunning: Test %2u: %s...\n", g_numTestsRun, pTestFuncName_ );
|
||||
bool bSuccess = TestFunc();
|
||||
if( bSuccess )
|
||||
{
|
||||
fprintf(stdout, "SUCCESS: Test %2u: %s.\n", g_numTestsRun, pTestFuncName_ );
|
||||
++g_numTestsSucceeded;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "FAILURE: Test %2u: %s.\n", g_numTestsRun, pTestFuncName_ );
|
||||
}
|
||||
}
|
||||
|
||||
struct ParallelSumTaskSet : ITaskSet
|
||||
{
|
||||
struct Count
|
||||
{
|
||||
// prevent false sharing.
|
||||
uint64_t count;
|
||||
char cacheline[64];
|
||||
};
|
||||
Count* m_pPartialSums;
|
||||
uint32_t m_NumPartialSums;
|
||||
|
||||
ParallelSumTaskSet( uint32_t size_ ) : m_pPartialSums(NULL), m_NumPartialSums(0) { m_SetSize = size_; }
|
||||
virtual ~ParallelSumTaskSet()
|
||||
{
|
||||
delete[] m_pPartialSums;
|
||||
}
|
||||
|
||||
void Init( uint32_t numPartialSums_ )
|
||||
{
|
||||
delete[] m_pPartialSums;
|
||||
m_NumPartialSums =numPartialSums_ ;
|
||||
m_pPartialSums = new Count[ m_NumPartialSums ];
|
||||
memset( m_pPartialSums, 0, sizeof(Count)*m_NumPartialSums );
|
||||
}
|
||||
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
assert( m_pPartialSums && m_NumPartialSums );
|
||||
uint64_t sum = m_pPartialSums[threadnum_].count;
|
||||
for( uint64_t i = range_.start; i < range_.end; ++i )
|
||||
{
|
||||
sum += i + 1;
|
||||
}
|
||||
m_pPartialSums[threadnum_].count = sum;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct ParallelReductionSumTaskSet : ITaskSet
|
||||
{
|
||||
ParallelSumTaskSet* m_pParallelSum;
|
||||
Dependency m_Dependency;
|
||||
uint64_t m_FinalSum;
|
||||
|
||||
ParallelReductionSumTaskSet( ParallelSumTaskSet* pParallelSum_ ) : m_pParallelSum( pParallelSum_ ), m_Dependency( pParallelSum_, this ), m_FinalSum(0)
|
||||
{
|
||||
}
|
||||
|
||||
void ExecuteRange( TaskSetPartition range, uint32_t threadnum ) override
|
||||
{
|
||||
for( uint32_t i = 0; i < m_pParallelSum->m_NumPartialSums; ++i )
|
||||
{
|
||||
m_FinalSum += m_pParallelSum->m_pPartialSums[i].count;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void threadFunction( uint32_t setSize_, bool* pbRegistered_, uint64_t* pSumParallel_ )
|
||||
{
|
||||
*pbRegistered_ = g_TS.RegisterExternalTaskThread();
|
||||
if( *pbRegistered_ )
|
||||
{
|
||||
ParallelSumTaskSet parallelSumTask( setSize_ );
|
||||
parallelSumTask.Init( g_TS.GetNumTaskThreads() );
|
||||
ParallelReductionSumTaskSet parallelReductionSumTaskSet( ¶llelSumTask );
|
||||
|
||||
g_TS.AddTaskSetToPipe( ¶llelSumTask );
|
||||
g_TS.WaitforTask( ¶llelReductionSumTaskSet );
|
||||
|
||||
g_TS.DeRegisterExternalTaskThread();
|
||||
*pSumParallel_ = parallelReductionSumTaskSet.m_FinalSum;
|
||||
}
|
||||
}
|
||||
|
||||
struct PinnedTask : IPinnedTask
|
||||
{
|
||||
PinnedTask()
|
||||
: IPinnedTask( enki::GetNumHardwareThreads() - 1 ) // set pinned thread to 0
|
||||
{}
|
||||
virtual void Execute()
|
||||
{
|
||||
threadRunOn = g_TS.GetThreadNum();
|
||||
}
|
||||
uint32_t threadRunOn = 0;
|
||||
};
|
||||
|
||||
|
||||
struct TestPriorities : ITaskSet
|
||||
{
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct CustomAllocData
|
||||
{
|
||||
const char* domainName;
|
||||
uint64_t totalAllocations;
|
||||
};
|
||||
|
||||
void* CustomAllocFunc( size_t align_, size_t size_, void* userData_, const char* file_, int line_ )
|
||||
{
|
||||
CustomAllocData* data = (CustomAllocData*)userData_;
|
||||
data->totalAllocations += size_;
|
||||
return DefaultAllocFunc( align_, size_, userData_, file_, line_ );
|
||||
};
|
||||
|
||||
void CustomFreeFunc( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ )
|
||||
{
|
||||
CustomAllocData* data = (CustomAllocData*)userData_;
|
||||
data->totalAllocations -= size_;
|
||||
DefaultFreeFunc( ptr_, size_, userData_, file_, line_ );
|
||||
};
|
||||
|
||||
std::atomic<int32_t> gs_DependencyCounter = {0};
|
||||
|
||||
struct TestDependenciesTaskSet : ITaskSet
|
||||
{
|
||||
int32_t m_Counter = 0;
|
||||
std::vector<Dependency> m_Dependencies;
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
m_Counter = gs_DependencyCounter.fetch_add(1);
|
||||
}
|
||||
};
|
||||
|
||||
struct TestDependenciesPinnedTask : IPinnedTask
|
||||
{
|
||||
int32_t m_Counter = 0;
|
||||
std::vector<Dependency> m_Dependencies;
|
||||
void Execute() override
|
||||
{
|
||||
m_Counter = gs_DependencyCounter.fetch_add(1);
|
||||
}
|
||||
};
|
||||
|
||||
struct TestDependenciesCompletable : ICompletable
|
||||
{
|
||||
std::vector<Dependency> m_Dependencies;
|
||||
};
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
fprintf( stdout,"\n---Running Tests----\n" );
|
||||
|
||||
enki::TaskSchedulerConfig baseConfig;
|
||||
fprintf( stdout,"System has %u hardware threads reported\n", baseConfig.numTaskThreadsToCreate + 1 );
|
||||
if( 0 == baseConfig.numTaskThreadsToCreate )
|
||||
{
|
||||
baseConfig.numTaskThreadsToCreate = 1;
|
||||
fprintf( stdout,"As only one hardware thread forcing enkiTS to use 2 threads\n");
|
||||
}
|
||||
|
||||
uint32_t setSize = 20 * 1024 * 1024;
|
||||
uint64_t sumSerial;
|
||||
|
||||
// evaluate serial for test comparison with parallel runs
|
||||
ParallelSumTaskSet serialTask( setSize );
|
||||
serialTask.Init( 1 );
|
||||
TaskSetPartition range = { 0, setSize };
|
||||
serialTask.ExecuteRange( range, 0 );
|
||||
sumSerial = serialTask.m_pPartialSums[0].count;
|
||||
|
||||
|
||||
|
||||
RunTestFunction(
|
||||
"Test Lots of TaskSets",
|
||||
[&]()->bool
|
||||
{
|
||||
g_TS.Initialize( baseConfig );
|
||||
|
||||
static constexpr uint32_t TASK_RANGE = 65*65;
|
||||
static constexpr uint32_t TASK_COUNT = 50;
|
||||
|
||||
|
||||
struct TaskSet : public enki::ITaskSet
|
||||
{
|
||||
TaskSet() : enki::ITaskSet(TASK_RANGE) {};
|
||||
virtual void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
if( range_.start >= TASK_RANGE && range_.end > TASK_RANGE )
|
||||
{
|
||||
countErrors.fetch_add(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::atomic<int32_t> countErrors{ 0 };
|
||||
};
|
||||
|
||||
TaskSet tasks[TASK_COUNT];
|
||||
|
||||
for( uint32_t i = 0; i < TASK_COUNT; ++i )
|
||||
{
|
||||
g_TS.AddTaskSetToPipe( &tasks[i] );
|
||||
}
|
||||
|
||||
g_TS.WaitforAll();
|
||||
|
||||
bool bSuccess = true;
|
||||
for( uint32_t i = 0; i < TASK_COUNT; ++i )
|
||||
{
|
||||
if( tasks[i].countErrors.load( std::memory_order_relaxed ) > 0 )
|
||||
{
|
||||
bSuccess = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
);
|
||||
RunTestFunction(
|
||||
"Parallel Reduction Sum",
|
||||
[&]()->bool
|
||||
{
|
||||
g_TS.Initialize( baseConfig );
|
||||
ParallelSumTaskSet parallelSumTask( setSize );
|
||||
parallelSumTask.Init( g_TS.GetNumTaskThreads() );
|
||||
ParallelReductionSumTaskSet parallelReductionSumTaskSet( ¶llelSumTask );
|
||||
|
||||
g_TS.AddTaskSetToPipe( ¶llelSumTask );
|
||||
g_TS.WaitforTask( ¶llelReductionSumTaskSet );
|
||||
|
||||
fprintf( stdout,"\tParallelReductionSum: %" PRIu64 ", sumSerial: %" PRIu64 "\n", parallelReductionSumTaskSet.m_FinalSum, sumSerial );
|
||||
return parallelReductionSumTaskSet.m_FinalSum == sumSerial;
|
||||
} );
|
||||
|
||||
RunTestFunction(
|
||||
"External Thread",
|
||||
[&]()->bool
|
||||
{
|
||||
enki::TaskSchedulerConfig config = baseConfig;
|
||||
config.numExternalTaskThreads = 1;
|
||||
bool bRegistered = false;
|
||||
uint64_t sumParallel = 0;
|
||||
g_TS.Initialize( config );
|
||||
|
||||
std::thread threads( threadFunction, setSize, &bRegistered, &sumParallel );
|
||||
threads.join();
|
||||
fprintf( stdout,"\tExternal thread sum: %" PRIu64 ", sumSerial: %" PRIu64 "\n", sumParallel, sumSerial );
|
||||
if( !bRegistered )
|
||||
{
|
||||
fprintf( stderr,"\tExternal thread did not register\n" );
|
||||
return false;
|
||||
}
|
||||
if( sumParallel != sumSerial )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} );
|
||||
|
||||
RunTestFunction(
|
||||
"Pinned Task",
|
||||
[&]()->bool
|
||||
{
|
||||
g_TS.Initialize( baseConfig );
|
||||
PinnedTask pinnedTask;
|
||||
g_TS.AddPinnedTask( &pinnedTask );
|
||||
g_TS.WaitforTask( &pinnedTask );
|
||||
fprintf( stdout,"\tPinned task ran on thread %u, requested thread %u\n", pinnedTask.threadRunOn, pinnedTask.threadNum );
|
||||
return pinnedTask.threadRunOn == pinnedTask.threadNum;
|
||||
} );
|
||||
|
||||
RunTestFunction(
|
||||
"Priorities",
|
||||
[&]()->bool
|
||||
{
|
||||
// check priorities run in order by forcing single threaded execution
|
||||
enki::TaskSchedulerConfig config = baseConfig;
|
||||
config.numTaskThreadsToCreate = 0;
|
||||
g_TS.Initialize( config );
|
||||
TestPriorities priorityTaskLow;
|
||||
priorityTaskLow.m_Priority = enki::TASK_PRIORITY_LOW;
|
||||
TestPriorities priorityTaskHigh;
|
||||
priorityTaskHigh.m_Priority = enki::TASK_PRIORITY_HIGH;
|
||||
g_TS.AddTaskSetToPipe( &priorityTaskLow );
|
||||
g_TS.AddTaskSetToPipe( &priorityTaskHigh );
|
||||
g_TS.WaitforTask( &priorityTaskHigh, priorityTaskHigh.m_Priority );
|
||||
|
||||
// WaitforTask should not have been run any task below high priority,
|
||||
// even though low priority task was added first
|
||||
if( priorityTaskLow.GetIsComplete() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
g_TS.WaitforTask( &priorityTaskLow );
|
||||
|
||||
return true;
|
||||
} );
|
||||
|
||||
RunTestFunction(
|
||||
"Custom Allocator",
|
||||
[&]()->bool
|
||||
{
|
||||
enki::TaskSchedulerConfig config = baseConfig;
|
||||
config.customAllocator.alloc = CustomAllocFunc;
|
||||
config.customAllocator.free = CustomFreeFunc;
|
||||
CustomAllocData customAllocdata{ "enkITS", 0 };
|
||||
config.customAllocator.userData = &customAllocdata;
|
||||
|
||||
g_TS.Initialize( config );
|
||||
uint64_t allocsAfterInit = customAllocdata.totalAllocations;
|
||||
fprintf( stdout,"\tenkiTS allocated bytes after init: %" PRIu64 "\n", customAllocdata.totalAllocations );
|
||||
|
||||
ParallelSumTaskSet parallelSumTask( setSize );
|
||||
parallelSumTask.Init( g_TS.GetNumTaskThreads() );
|
||||
ParallelReductionSumTaskSet parallelReductionSumTaskSet( ¶llelSumTask );
|
||||
g_TS.AddTaskSetToPipe( ¶llelSumTask );
|
||||
g_TS.WaitforTask( ¶llelReductionSumTaskSet );
|
||||
|
||||
fprintf( stdout,"\tenkiTS allocated bytes after running tasks: %" PRIu64 "\n", customAllocdata.totalAllocations );
|
||||
if( customAllocdata.totalAllocations != allocsAfterInit )
|
||||
{
|
||||
fprintf( stderr,"\tERROR: enkiTS allocated bytes during scheduling\n" );
|
||||
return false;
|
||||
}
|
||||
g_TS.WaitforAllAndShutdown();
|
||||
fprintf( stdout,"\tenkiTS allocated bytes after shutdown: %" PRIu64 "\n", customAllocdata.totalAllocations );
|
||||
return customAllocdata.totalAllocations == 0;
|
||||
} );
|
||||
|
||||
RunTestFunction(
|
||||
"Dependencies",
|
||||
[&]()->bool
|
||||
{
|
||||
g_TS.Initialize( baseConfig );
|
||||
|
||||
TestDependenciesTaskSet taskSetA;
|
||||
|
||||
TestDependenciesTaskSet taskSetBs[8];
|
||||
for( auto& task : taskSetBs )
|
||||
{
|
||||
task.SetDependenciesVec(task.m_Dependencies,{&taskSetA});
|
||||
}
|
||||
|
||||
TestDependenciesPinnedTask pinnedTaskC;
|
||||
pinnedTaskC.SetDependenciesVec(pinnedTaskC.m_Dependencies, taskSetBs);
|
||||
|
||||
TestDependenciesTaskSet taskSetDs[8];
|
||||
for( auto& task : taskSetDs )
|
||||
{
|
||||
task.SetDependenciesVec(task.m_Dependencies,{&pinnedTaskC});
|
||||
}
|
||||
TestDependenciesTaskSet taskSetEs[4];
|
||||
for( auto& task : taskSetEs )
|
||||
{
|
||||
task.SetDependenciesVec(task.m_Dependencies,taskSetDs);
|
||||
}
|
||||
|
||||
TestDependenciesCompletable finalTask;
|
||||
finalTask.SetDependenciesVec( finalTask.m_Dependencies,taskSetEs);
|
||||
|
||||
g_TS.AddTaskSetToPipe( &taskSetA );
|
||||
g_TS.WaitforTask( &finalTask );
|
||||
|
||||
// check counters and complete status
|
||||
if( !taskSetA.GetIsComplete() )
|
||||
{
|
||||
fprintf( stderr,"\tERROR: enkiTS dependencies issue taskSetA not complete\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t lastCount = taskSetA.m_Counter;
|
||||
int32_t countCheck = lastCount;
|
||||
for( auto& task : taskSetBs )
|
||||
{
|
||||
if( !task.GetIsComplete() )
|
||||
{
|
||||
fprintf( stderr,"\tERROR: enkiTS dependencies issue taskSetBs not complete\n");
|
||||
return false;
|
||||
}
|
||||
if( task.m_Counter < countCheck )
|
||||
{
|
||||
fprintf( stderr,"\tERROR: enkiTS dependencies issue %d < %d at line %d\n", task.m_Counter, lastCount, __LINE__ );
|
||||
return false;
|
||||
}
|
||||
lastCount = std::max( lastCount, task.m_Counter );
|
||||
}
|
||||
countCheck = lastCount;
|
||||
if( !pinnedTaskC.GetIsComplete() )
|
||||
{
|
||||
fprintf( stderr,"\tERROR: enkiTS dependencies issue pinnedTaskC not complete\n");
|
||||
return false;
|
||||
}
|
||||
if( pinnedTaskC.m_Counter < countCheck )
|
||||
{
|
||||
fprintf( stderr,"\tERROR: enkiTS dependencies issue %d < %d at line %d\n", pinnedTaskC.m_Counter, lastCount, __LINE__ );
|
||||
return false;
|
||||
lastCount = std::max( lastCount, pinnedTaskC.m_Counter );
|
||||
}
|
||||
countCheck = lastCount;
|
||||
for( auto& task : taskSetDs )
|
||||
{
|
||||
if( !task.GetIsComplete() )
|
||||
{
|
||||
fprintf( stderr,"\tERROR: enkiTS dependencies issue taskSetDs not complete\n");
|
||||
return false;
|
||||
}
|
||||
if( task.m_Counter < countCheck )
|
||||
{
|
||||
fprintf( stderr,"\tERROR: enkiTS dependencies issue %d < %d at line %d\n", task.m_Counter, lastCount, __LINE__ );
|
||||
return false;
|
||||
}
|
||||
lastCount = std::max( lastCount, task.m_Counter );
|
||||
}
|
||||
countCheck = lastCount;
|
||||
for( auto& task : taskSetEs )
|
||||
{
|
||||
if( !task.GetIsComplete() )
|
||||
{
|
||||
fprintf( stderr,"\tERROR: enkiTS dependencies issue taskSetEs not complete\n");
|
||||
return false;
|
||||
}
|
||||
if( task.m_Counter < countCheck )
|
||||
{
|
||||
fprintf( stderr,"\tERROR: enkiTS dependencies issue %d < %d at line %d\n", task.m_Counter, lastCount, __LINE__ );
|
||||
return false;
|
||||
}
|
||||
lastCount = std::max( lastCount, task.m_Counter );
|
||||
}
|
||||
g_TS.WaitforAllAndShutdown();
|
||||
return true;
|
||||
} );
|
||||
|
||||
RunTestFunction(
|
||||
"WaitForNewPinnedTasks",
|
||||
[&]()->bool
|
||||
{
|
||||
enki::TaskSchedulerConfig config = baseConfig;
|
||||
config.numTaskThreadsToCreate += 1;
|
||||
g_TS.Initialize( config );
|
||||
const uint32_t PINNED_ONLY_THREAD = g_TS.GetNumTaskThreads() - 1;
|
||||
|
||||
LambdaPinnedTask waitTask( PINNED_ONLY_THREAD, []()
|
||||
{
|
||||
while( g_TS.GetIsWaitforAllCalled() )
|
||||
{
|
||||
g_TS.WaitForNewPinnedTasks();
|
||||
g_TS.RunPinnedTasks();
|
||||
}
|
||||
} );
|
||||
|
||||
g_TS.AddPinnedTask( &waitTask );
|
||||
|
||||
PinnedTask pinnedTask;
|
||||
pinnedTask.threadNum = PINNED_ONLY_THREAD;
|
||||
g_TS.AddPinnedTask( &pinnedTask );
|
||||
g_TS.WaitforTask( &pinnedTask );
|
||||
fprintf( stdout,"\tPinned task ran on thread %u, requested thread %u\n", pinnedTask.threadRunOn, pinnedTask.threadNum );
|
||||
if( pinnedTask.threadRunOn != pinnedTask.threadNum )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
g_TS.WaitforAll(); // force all tasks to end, waitTask should exit because we use GetIsWaitforAllCalled()
|
||||
|
||||
g_TS.AddPinnedTask( &waitTask );
|
||||
g_TS.AddPinnedTask( &pinnedTask );
|
||||
g_TS.WaitforTask( &pinnedTask );
|
||||
fprintf( stdout,"\tPinned task ran on thread %u, requested thread %u\n", pinnedTask.threadRunOn, pinnedTask.threadNum );
|
||||
|
||||
g_TS.WaitforAllAndShutdown();
|
||||
|
||||
return pinnedTask.threadRunOn == pinnedTask.threadNum;
|
||||
} );
|
||||
|
||||
RunTestFunction(
|
||||
"ShutdownNow",
|
||||
[&]()->bool
|
||||
{
|
||||
g_TS.Initialize( baseConfig );
|
||||
ParallelSumTaskSet parallelSumTask( setSize );
|
||||
parallelSumTask.Init( g_TS.GetNumTaskThreads() );
|
||||
ParallelReductionSumTaskSet parallelReductionSumTaskSet( ¶llelSumTask );
|
||||
|
||||
g_TS.AddTaskSetToPipe( ¶llelSumTask );
|
||||
g_TS.WaitforTask( ¶llelReductionSumTaskSet );
|
||||
|
||||
fprintf( stdout,"\tCalling ShutdownNow()...");
|
||||
g_TS.ShutdownNow();
|
||||
fprintf( stdout," ...completed.\n" );
|
||||
return parallelReductionSumTaskSet.m_FinalSum == sumSerial;
|
||||
} );
|
||||
|
||||
fprintf( stdout, "\n%u Tests Run\n%u Tests Succeeded\n\n", g_numTestsRun, g_numTestsSucceeded );
|
||||
if( g_numTestsRun == g_numTestsSucceeded )
|
||||
{
|
||||
fprintf( stdout, "All tests SUCCEEDED\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf( stderr, "%u tests FAILED\n", g_numTestsRun - g_numTestsSucceeded );
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2019 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
#include "Timer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
enki::TaskScheduler g_TS;
|
||||
uint32_t g_Iteration;
|
||||
|
||||
std::atomic<int32_t> g_WaitForTaskCompletion(0);
|
||||
std::atomic<int32_t> g_WaitCount(0);
|
||||
|
||||
struct SlowTask : enki::ITaskSet
|
||||
{
|
||||
void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
// fake slow task with timer
|
||||
Timer timer;
|
||||
timer.Start();
|
||||
while( timer.GetTimeMS() < waitTime )
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
double waitTime;
|
||||
};
|
||||
|
||||
struct SlowPinnedTask : enki::IPinnedTask
|
||||
{
|
||||
void Execute() override
|
||||
{
|
||||
// fake slow task with timer
|
||||
Timer timer;
|
||||
timer.Start();
|
||||
while( timer.GetTimeMS() < waitTime )
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
double waitTime;
|
||||
};
|
||||
|
||||
struct WaitingTask : enki::ITaskSet
|
||||
{
|
||||
void ExecuteRange( enki::TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
int numWaitTasks = maxWaitasks - depth;
|
||||
for( int t = 0; t < numWaitTasks; ++t )
|
||||
{
|
||||
pWaitingTasks[t] = new WaitingTask;
|
||||
pWaitingTasks[t]->depth = depth + 1;
|
||||
g_TS.AddTaskSetToPipe( pWaitingTasks[t] );
|
||||
}
|
||||
for( SlowTask& task : tasks )
|
||||
{
|
||||
task.m_SetSize = 1000;
|
||||
task.waitTime = 0.00001 * double( rand() % 100 );
|
||||
g_TS.AddTaskSetToPipe( &task );
|
||||
}
|
||||
|
||||
for( SlowTask& task : tasks )
|
||||
{
|
||||
++g_WaitCount;
|
||||
g_TS.WaitforTask( &task );
|
||||
|
||||
// we add a random wait for pinned task here.
|
||||
uint32_t randThread = (uint32_t)rand() % g_TS.GetNumTaskThreads();
|
||||
pinnedTask.threadNum = randThread;
|
||||
pinnedTask.waitTime = 0.00001 * double( rand() % 10 );
|
||||
g_TS.AddPinnedTask( &pinnedTask );
|
||||
g_TS.WaitforTask( &pinnedTask );
|
||||
}
|
||||
|
||||
for( int t = 0; t < numWaitTasks; ++t )
|
||||
{
|
||||
++g_WaitCount;
|
||||
g_TS.WaitforTask( pWaitingTasks[t] );
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~WaitingTask()
|
||||
{
|
||||
for( WaitingTask* pWaitingTask : pWaitingTasks )
|
||||
{
|
||||
delete pWaitingTask;
|
||||
}
|
||||
}
|
||||
SlowTask tasks[4];
|
||||
SlowPinnedTask pinnedTask;
|
||||
int32_t depth = 0;
|
||||
static constexpr int maxWaitasks = 4;
|
||||
WaitingTask* pWaitingTasks[maxWaitasks] = {};
|
||||
};
|
||||
|
||||
|
||||
|
||||
// This example demonstrates how to run a long running task alongside tasks
|
||||
// which must complete as early as possible using priorities.
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
enki::TaskSchedulerConfig config;
|
||||
config.profilerCallbacks.waitForTaskCompleteStart = []( uint32_t threadnum_ ) { ++g_WaitCount; };
|
||||
config.profilerCallbacks.waitForTaskCompleteSuspendStart = []( uint32_t threadnum_ ) { ++g_WaitForTaskCompletion; };
|
||||
g_TS.Initialize( config );
|
||||
for( g_Iteration = 0; g_Iteration < 1000; ++g_Iteration )
|
||||
{
|
||||
printf( "\tIteration %d: Waits: %d blocking waits: %d\n",
|
||||
g_Iteration, g_WaitCount.load(), g_WaitForTaskCompletion.load() );
|
||||
WaitingTask taskRoot;
|
||||
g_TS.AddTaskSetToPipe( &taskRoot );
|
||||
g_TS.WaitforAll();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
// simple time class you shouldn't use outside example code.
|
||||
class Timer
|
||||
{
|
||||
double timeMS = 0.0;
|
||||
bool bRunning = false;
|
||||
public:
|
||||
Timer() = default;
|
||||
|
||||
// start does not reset time to 0, use reset
|
||||
void Start()
|
||||
{
|
||||
bRunning = true;
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
double GetTimeMS()
|
||||
{
|
||||
if( !bRunning )
|
||||
{
|
||||
return timeMS;
|
||||
}
|
||||
auto stop = std::chrono::high_resolution_clock::now();
|
||||
return 1000.0 * std::chrono::duration_cast<std::chrono::duration<double>>(stop - start).count();
|
||||
};
|
||||
|
||||
void Stop()
|
||||
{
|
||||
if( bRunning )
|
||||
{
|
||||
timeMS += GetTimeMS();
|
||||
bRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
timeMS = 0.0;
|
||||
bRunning = false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
|
||||
};
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) 2021 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace enki;
|
||||
|
||||
TaskScheduler g_TS;
|
||||
static std::atomic<int32_t> g_Run;
|
||||
|
||||
enum class IOThreadId
|
||||
{
|
||||
FILE_IO, // more than one file io thread may be useful if handling lots of small files
|
||||
NETWORK_IO_0,
|
||||
NETWORK_IO_1,
|
||||
NETWORK_IO_2,
|
||||
NUM,
|
||||
};
|
||||
|
||||
struct ParallelTaskSet : ITaskSet
|
||||
{
|
||||
ParallelTaskSet() { m_SetSize = 100; }
|
||||
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
bool bIsIOThread = threadnum_ >= g_TS.GetNumTaskThreads() - (uint32_t)IOThreadId::NUM;
|
||||
if( bIsIOThread )
|
||||
{
|
||||
assert( false ); //for this example this is an error - but external threads can run tasksets in general
|
||||
printf(" Run %d: ParallelTaskSet on thread %d which is an IO thread\n", g_Run.load(), threadnum_);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" Run %d: ParallelTaskSet on thread %d which is not an IO thread\n", g_Run.load(), threadnum_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct RunPinnedTaskLoopTask : IPinnedTask
|
||||
{
|
||||
void Execute() override
|
||||
{
|
||||
while( !g_TS.GetIsShutdownRequested() )
|
||||
{
|
||||
g_TS.WaitForNewPinnedTasks(); // this thread will 'sleep' until there are new pinned tasks
|
||||
g_TS.RunPinnedTasks();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct PretendDoFileIO : IPinnedTask
|
||||
{
|
||||
void Execute() override
|
||||
{
|
||||
printf(" Run %d: PretendDoFileIO on thread %d\n", g_Run.load(), threadNum );
|
||||
// sleep used as a 'pretend' blocking workload
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
|
||||
}
|
||||
};
|
||||
|
||||
struct PretendDoNetworkIO : IPinnedTask
|
||||
{
|
||||
void Execute() override
|
||||
{
|
||||
printf(" Run %d: PretendDoNetworkIO on thread %d\n", g_Run.load(), threadNum );
|
||||
// sleep used as a 'pretend' blocking workload
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 5 ) );
|
||||
}
|
||||
};
|
||||
|
||||
static const int REPEATS = 5;
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
enki::TaskSchedulerConfig config;
|
||||
|
||||
// In this example we create more threads than the hardware can run,
|
||||
// because the IO threads will spend most of their time idle or blocked
|
||||
// and therefore not scheduled for CPU time by the OS
|
||||
config.numTaskThreadsToCreate += (uint32_t)IOThreadId::NUM;
|
||||
|
||||
g_TS.Initialize( config );
|
||||
|
||||
// in this example we place our IO threads at the end
|
||||
uint32_t theadNumIOStart = g_TS.GetNumTaskThreads() - (uint32_t)IOThreadId::NUM;
|
||||
RunPinnedTaskLoopTask runPinnedTaskLoopTasks[ (uint32_t)IOThreadId::NUM ];
|
||||
|
||||
for( uint32_t ioThreadID = 0; ioThreadID < (uint32_t)IOThreadId::NUM; ++ioThreadID )
|
||||
{
|
||||
runPinnedTaskLoopTasks[ioThreadID].threadNum = ioThreadID + theadNumIOStart;
|
||||
g_TS.AddPinnedTask( &runPinnedTaskLoopTasks[ioThreadID] );
|
||||
}
|
||||
|
||||
|
||||
for( g_Run = 0; g_Run< REPEATS; ++g_Run )
|
||||
{
|
||||
printf("Run %d\n", g_Run.load() );
|
||||
|
||||
// set of a ParallelTaskSet
|
||||
// to demonstrate can perform work on enkiTS worker threads but WaitForNewPinnedTasks will
|
||||
// not perform work. This can be used to fully subscribe machine with enkiTS worker threads but
|
||||
// have extra IO bound threads to handle blocking tasks
|
||||
ParallelTaskSet parallelTaskSet;
|
||||
g_TS.AddTaskSetToPipe( ¶llelTaskSet );
|
||||
|
||||
// Send pretend file IO task to external thread FILE_IO
|
||||
PretendDoFileIO pretendDoFileIO;
|
||||
pretendDoFileIO.threadNum = (uint32_t)IOThreadId::FILE_IO + theadNumIOStart;
|
||||
g_TS.AddPinnedTask( &pretendDoFileIO );
|
||||
|
||||
// Send pretend network IO tasks to external thread NETWORK_IO_0 ... NUMEXTERNALTHREADS
|
||||
PretendDoNetworkIO pretendDoNetworkIO[ (uint32_t)IOThreadId::NUM - (uint32_t)IOThreadId::NETWORK_IO_0 ];
|
||||
for( uint32_t ioThreadID = (uint32_t)IOThreadId::NETWORK_IO_0; ioThreadID < (uint32_t)IOThreadId::NUM; ++ioThreadID )
|
||||
{
|
||||
pretendDoNetworkIO[ioThreadID-(uint32_t)IOThreadId::NETWORK_IO_0].threadNum = ioThreadID + theadNumIOStart;
|
||||
g_TS.AddPinnedTask( &pretendDoNetworkIO[ ioThreadID - (uint32_t)IOThreadId::NETWORK_IO_0 ] );
|
||||
}
|
||||
|
||||
g_TS.WaitforTask( ¶llelTaskSet );
|
||||
|
||||
// in this example we need to wait for IO tasks to complete before running next loop
|
||||
g_TS.WaitforTask( &pretendDoFileIO );
|
||||
for( uint32_t ioThreadID = (uint32_t)IOThreadId::NETWORK_IO_0; ioThreadID < (uint32_t)IOThreadId::NUM; ++ioThreadID )
|
||||
{
|
||||
g_TS.WaitforTask( &pretendDoNetworkIO[ ioThreadID - (uint32_t)IOThreadId::NETWORK_IO_0 ] );
|
||||
}
|
||||
}
|
||||
|
||||
// ensure runPinnedTaskLoopTasks complete by explicitly calling WaitforAllAndShutdown
|
||||
g_TS.WaitforAllAndShutdown();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2021 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler_c.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
enkiTaskScheduler* pETS;
|
||||
|
||||
|
||||
void PinnedTaskPretendIOFunc( void* pArgs_ )
|
||||
{
|
||||
int32_t dataVal = *(int32_t*)pArgs_;
|
||||
printf("Run %d: Example PinnedTaskPretendIOFunc - this could perform network or file IO\n", dataVal );
|
||||
}
|
||||
|
||||
void PinnedTaskRunPinnedTaskLoop( void* pArgs_ )
|
||||
{
|
||||
uint32_t threadNumDesired = *(uint32_t*)pArgs_;
|
||||
uint32_t threadNum = enkiGetThreadNum( pETS );
|
||||
assert( threadNum == threadNumDesired );
|
||||
printf("PinnedTaskRunPinnedTaskLoop running on thread %d (should be thread %d)\n", threadNum, threadNumDesired );
|
||||
|
||||
while( !enkiGetIsShutdownRequested( pETS ) )
|
||||
{
|
||||
enkiWaitForNewPinnedTasks( pETS );
|
||||
enkiRunPinnedTasks( pETS );
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
struct enkiTaskSchedulerConfig config;
|
||||
enkiPinnedTask* pPinnedTaskRunPinnedTaskLoop;
|
||||
enkiPinnedTask* pPinnedTaskPretendIO;
|
||||
|
||||
pETS = enkiNewTaskScheduler();
|
||||
|
||||
// get default config and request one external thread
|
||||
config = enkiGetTaskSchedulerConfig( pETS );
|
||||
|
||||
// In this example we create more threads than the hardware can run,
|
||||
// because the IO thread will spend most of it's time idle or blocked
|
||||
// and therefore not scheduled for CPU time by the OS
|
||||
config.numTaskThreadsToCreate += 1; // Create 1 extra thread for IO tasks (could create more if needed)
|
||||
enkiInitTaskSchedulerWithConfig( pETS, config );
|
||||
|
||||
uint32_t threadNumIOTasks = config.numTaskThreadsToCreate; // thread 0 is this thread, so last thread is num threads created.
|
||||
|
||||
// create task to run pinned task loop
|
||||
pPinnedTaskRunPinnedTaskLoop = enkiCreatePinnedTask( pETS, PinnedTaskRunPinnedTaskLoop, threadNumIOTasks );
|
||||
enkiAddPinnedTaskArgs( pETS, pPinnedTaskRunPinnedTaskLoop, &threadNumIOTasks );
|
||||
|
||||
// send pretend IO commands to external thread
|
||||
pPinnedTaskPretendIO = enkiCreatePinnedTask( pETS, PinnedTaskPretendIOFunc, threadNumIOTasks );
|
||||
for( int32_t i=0; i<5; ++i )
|
||||
{
|
||||
// we re-use one task here as we are waiting for each to complete
|
||||
enkiAddPinnedTaskArgs( pETS, pPinnedTaskPretendIO, &i );
|
||||
|
||||
// in most real world cases you would not wait for pinned IO task immediatly after
|
||||
// issueing it, but instead do work.
|
||||
// Rather than waiting can use dependencies or issue a pinned task to main thread (id 0) to send data
|
||||
enkiWaitForPinnedTask( pETS, pPinnedTaskPretendIO );
|
||||
}
|
||||
enkiDeletePinnedTask( pETS, pPinnedTaskPretendIO );
|
||||
|
||||
|
||||
// Shutdown enkiTS, which will cause pPinnedTaskRunPinnedTaskLoop to exit as enkiGetIsShutdownRequested will return true
|
||||
enkiWaitforAllAndShutdown( pETS );
|
||||
|
||||
// delete the tasks before the scheduler
|
||||
enkiDeletePinnedTask( pETS, pPinnedTaskRunPinnedTaskLoop );
|
||||
|
||||
enkiDeleteTaskScheduler( pETS );
|
||||
printf("WaitForNewPinnedTasks_c.c completed\n" );
|
||||
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
// Copyright (c) 2013 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <atomic>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef ENKI_ASSERT
|
||||
#include <assert.h>
|
||||
#define ENKI_ASSERT(x) assert(x)
|
||||
#endif
|
||||
|
||||
namespace enki
|
||||
{
|
||||
// LockLessMultiReadPipe - Single writer, multiple reader thread safe pipe using (semi) lockless programming
|
||||
// Readers can only read from the back of the pipe
|
||||
// The single writer can write to the front of the pipe, and read from both ends (a writer can be a reader)
|
||||
// for many of the principles used here, see http://msdn.microsoft.com/en-us/library/windows/desktop/ee418650(v=vs.85).aspx
|
||||
// Note: using log2 sizes so we do not need to clamp (multi-operation)
|
||||
// T is the contained type
|
||||
// Note this is not true lockless as the use of flags as a form of lock state.
|
||||
template<uint8_t cSizeLog2, typename T> class LockLessMultiReadPipe
|
||||
{
|
||||
public:
|
||||
LockLessMultiReadPipe();
|
||||
~LockLessMultiReadPipe() {}
|
||||
|
||||
// ReaderTryReadBack returns false if we were unable to read
|
||||
// This is thread safe for both multiple readers and the writer
|
||||
bool ReaderTryReadBack( T* pOut );
|
||||
|
||||
// WriterTryReadFront returns false if we were unable to read
|
||||
// This is thread safe for the single writer, but should not be called by readers
|
||||
bool WriterTryReadFront( T* pOut );
|
||||
|
||||
// WriterTryWriteFront returns false if we were unable to write
|
||||
// This is thread safe for the single writer, but should not be called by readers
|
||||
bool WriterTryWriteFront( const T& in );
|
||||
|
||||
// IsPipeEmpty() is a utility function, not intended for general use
|
||||
// Should only be used very prudently.
|
||||
bool IsPipeEmpty() const
|
||||
{
|
||||
return 0 == m_WriteIndex.load( std::memory_order_relaxed ) - m_ReadCount.load( std::memory_order_relaxed );
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
m_WriteIndex = 0;
|
||||
m_ReadIndex = 0;
|
||||
m_ReadCount = 0;
|
||||
memset( (void*)m_Flags, 0, sizeof( m_Flags ) );
|
||||
}
|
||||
|
||||
private:
|
||||
const static uint32_t ms_cSize = ( 1 << cSizeLog2 );
|
||||
const static uint32_t ms_cIndexMask = ms_cSize - 1;
|
||||
const static uint32_t FLAG_INVALID = 0xFFFFFFFF; // 32bit for CAS
|
||||
const static uint32_t FLAG_CAN_WRITE = 0x00000000; // 32bit for CAS
|
||||
const static uint32_t FLAG_CAN_READ = 0x11111111; // 32bit for CAS
|
||||
|
||||
T m_Buffer[ ms_cSize ];
|
||||
|
||||
// read and write indexes allow fast access to the pipe, but actual access
|
||||
// controlled by the access flags.
|
||||
std::atomic<uint32_t> m_WriteIndex;
|
||||
std::atomic<uint32_t> m_ReadCount;
|
||||
std::atomic<uint32_t> m_Flags[ ms_cSize ];
|
||||
std::atomic<uint32_t> m_ReadIndex;
|
||||
};
|
||||
|
||||
template<uint8_t cSizeLog2, typename T> inline
|
||||
LockLessMultiReadPipe<cSizeLog2,T>::LockLessMultiReadPipe()
|
||||
: m_WriteIndex(0)
|
||||
, m_ReadCount(0)
|
||||
, m_ReadIndex(0)
|
||||
{
|
||||
ENKI_ASSERT( cSizeLog2 < 32 );
|
||||
memset( (void*)m_Flags, 0, sizeof( m_Flags ) );
|
||||
}
|
||||
|
||||
template<uint8_t cSizeLog2, typename T> inline
|
||||
bool LockLessMultiReadPipe<cSizeLog2,T>::ReaderTryReadBack( T* pOut )
|
||||
{
|
||||
|
||||
uint32_t actualReadIndex;
|
||||
uint32_t readCount = m_ReadCount.load( std::memory_order_relaxed );
|
||||
|
||||
// We get hold of read index for consistency
|
||||
// and do first pass starting at read count
|
||||
uint32_t readIndexToUse = readCount;
|
||||
while(true)
|
||||
{
|
||||
|
||||
uint32_t writeIndex = m_WriteIndex.load( std::memory_order_relaxed );
|
||||
// power of two sizes ensures we can use a simple calc without modulus
|
||||
uint32_t numInPipe = writeIndex - readCount;
|
||||
if( 0 == numInPipe )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if( readIndexToUse >= writeIndex )
|
||||
{
|
||||
readIndexToUse = m_ReadIndex.load( std::memory_order_relaxed );
|
||||
}
|
||||
|
||||
// power of two sizes ensures we can perform AND for a modulus
|
||||
actualReadIndex = readIndexToUse & ms_cIndexMask;
|
||||
|
||||
// Multiple potential readers mean we should check if the data is valid,
|
||||
// using an atomic compare exchange
|
||||
uint32_t previous = FLAG_CAN_READ;
|
||||
bool bSuccess = m_Flags[ actualReadIndex ].compare_exchange_strong( previous, FLAG_INVALID, std::memory_order_acq_rel, std::memory_order_relaxed );
|
||||
if( bSuccess )
|
||||
{
|
||||
break;
|
||||
}
|
||||
++readIndexToUse;
|
||||
|
||||
// Update read count
|
||||
readCount = m_ReadCount.load( std::memory_order_relaxed );
|
||||
}
|
||||
|
||||
// we update the read index using an atomic add, as we've only read one piece of data.
|
||||
// this ensure consistency of the read index, and the above loop ensures readers
|
||||
// only read from unread data
|
||||
m_ReadCount.fetch_add(1, std::memory_order_relaxed );
|
||||
|
||||
// now read data, ensuring we do so after above reads & CAS
|
||||
*pOut = m_Buffer[ actualReadIndex ];
|
||||
|
||||
m_Flags[ actualReadIndex ].store( FLAG_CAN_WRITE, std::memory_order_release );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<uint8_t cSizeLog2, typename T> inline
|
||||
bool LockLessMultiReadPipe<cSizeLog2,T>::WriterTryReadFront( T* pOut )
|
||||
{
|
||||
uint32_t writeIndex = m_WriteIndex.load( std::memory_order_relaxed );
|
||||
uint32_t frontReadIndex = writeIndex;
|
||||
|
||||
// Multiple potential readers mean we should check if the data is valid,
|
||||
// using an atomic compare exchange - which acts as a form of lock (so not quite lockless really).
|
||||
uint32_t actualReadIndex = 0;
|
||||
while(true)
|
||||
{
|
||||
uint32_t readCount = m_ReadCount.load( std::memory_order_relaxed );
|
||||
// power of two sizes ensures we can use a simple calc without modulus
|
||||
uint32_t numInPipe = writeIndex - readCount;
|
||||
if( 0 == numInPipe )
|
||||
{
|
||||
m_ReadIndex.store( readCount, std::memory_order_release );
|
||||
return false;
|
||||
}
|
||||
--frontReadIndex;
|
||||
actualReadIndex = frontReadIndex & ms_cIndexMask;
|
||||
uint32_t previous = FLAG_CAN_READ;
|
||||
bool success = m_Flags[ actualReadIndex ].compare_exchange_strong( previous, FLAG_INVALID, std::memory_order_acq_rel, std::memory_order_relaxed );
|
||||
if( success )
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if( m_ReadIndex.load( std::memory_order_acquire ) >= frontReadIndex )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// now read data, ensuring we do so after above reads & CAS
|
||||
*pOut = m_Buffer[ actualReadIndex ];
|
||||
|
||||
m_Flags[ actualReadIndex ].store( FLAG_CAN_WRITE, std::memory_order_relaxed );
|
||||
|
||||
m_WriteIndex.store(writeIndex-1, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
template<uint8_t cSizeLog2, typename T> inline
|
||||
bool LockLessMultiReadPipe<cSizeLog2,T>::WriterTryWriteFront( const T& in )
|
||||
{
|
||||
// The writer 'owns' the write index, and readers can only reduce
|
||||
// the amount of data in the pipe.
|
||||
// We get hold of both values for consistency and to reduce false sharing
|
||||
// impacting more than one access
|
||||
uint32_t writeIndex = m_WriteIndex;
|
||||
|
||||
// power of two sizes ensures we can perform AND for a modulus
|
||||
uint32_t actualWriteIndex = writeIndex & ms_cIndexMask;
|
||||
|
||||
// a reader may still be reading this item, as there are multiple readers
|
||||
if( m_Flags[ actualWriteIndex ].load(std::memory_order_acquire) != FLAG_CAN_WRITE )
|
||||
{
|
||||
return false; // still being read, so have caught up with tail.
|
||||
}
|
||||
|
||||
// as we are the only writer we can update the data without atomics
|
||||
// whilst the write index has not been updated
|
||||
m_Buffer[ actualWriteIndex ] = in;
|
||||
m_Flags[ actualWriteIndex ].store( FLAG_CAN_READ, std::memory_order_release );
|
||||
|
||||
m_WriteIndex.fetch_add(1, std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Lockless multiwriter intrusive list
|
||||
// Type T must implement T* volatile pNext;
|
||||
template<typename T> class LocklessMultiWriteIntrusiveList
|
||||
{
|
||||
|
||||
std::atomic<T*> pHead;
|
||||
T tail;
|
||||
public:
|
||||
LocklessMultiWriteIntrusiveList() : pHead( &tail )
|
||||
{
|
||||
tail.pNext = NULL;
|
||||
}
|
||||
|
||||
bool IsListEmpty() const
|
||||
{
|
||||
return pHead == &tail;
|
||||
}
|
||||
|
||||
// Add - safe to perform from any thread
|
||||
void WriterWriteFront( T* pNode_ )
|
||||
{
|
||||
ENKI_ASSERT( pNode_ );
|
||||
pNode_->pNext = NULL;
|
||||
T* pPrev = pHead.exchange( pNode_ );
|
||||
pPrev->pNext = pNode_;
|
||||
}
|
||||
|
||||
// Remove - only thread safe for owner
|
||||
T* ReaderReadBack()
|
||||
{
|
||||
T* pTailPlus1 = tail.pNext;
|
||||
if( pTailPlus1 )
|
||||
{
|
||||
T* pTailPlus2 = pTailPlus1->pNext;
|
||||
if( pTailPlus2 )
|
||||
{
|
||||
//not head
|
||||
tail.pNext = pTailPlus2;
|
||||
}
|
||||
else
|
||||
{
|
||||
tail.pNext = NULL;
|
||||
T* pCompare = pTailPlus1; // we need preserve pTailPlus1 as compare will alter it on failure
|
||||
// pTailPlus1 is the head, attempt swap with tail
|
||||
if( !pHead.compare_exchange_strong( pCompare, &tail ) )
|
||||
{
|
||||
// pCompare receives the revised pHead on failure.
|
||||
// pTailPlus1 is no longer the head, so pTailPlus1->pNext should be non NULL
|
||||
while( (T*)NULL == pTailPlus1->pNext ) {;} // wait for pNext to be updated as head may have just changed.
|
||||
tail.pNext = pTailPlus1->pNext.load();
|
||||
pTailPlus1->pNext = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pTailPlus1;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,583 @@
|
||||
// Copyright (c) 2013 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <stdint.h>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
|
||||
// ENKITS_TASK_PRIORITIES_NUM can be set from 1 to 5.
|
||||
// 1 corresponds to effectively no priorities.
|
||||
#ifndef ENKITS_TASK_PRIORITIES_NUM
|
||||
#define ENKITS_TASK_PRIORITIES_NUM 3
|
||||
#endif
|
||||
|
||||
#ifndef ENKITS_API
|
||||
#if defined(_WIN32) && defined(ENKITS_BUILD_DLL)
|
||||
// Building enkiTS as a DLL
|
||||
#define ENKITS_API __declspec(dllexport)
|
||||
#elif defined(_WIN32) && defined(ENKITS_DLL)
|
||||
// Using enkiTS as a DLL
|
||||
#define ENKITS_API __declspec(dllimport)
|
||||
#elif defined(__GNUC__) && defined(ENKITS_BUILD_DLL)
|
||||
// Building enkiTS as a shared library
|
||||
#define ENKITS_API __attribute__((visibility("default")))
|
||||
#else
|
||||
#define ENKITS_API
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Define ENKI_CUSTOM_ALLOC_FILE_AND_LINE (at project level) to get file and line report in custom allocators,
|
||||
// this is default in Debug - to turn off define ENKI_CUSTOM_ALLOC_NO_FILE_AND_LINE
|
||||
#ifndef ENKI_CUSTOM_ALLOC_FILE_AND_LINE
|
||||
#if defined(_DEBUG ) && !defined(ENKI_CUSTOM_ALLOC_NO_FILE_AND_LINE)
|
||||
#define ENKI_CUSTOM_ALLOC_FILE_AND_LINE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef ENKI_ASSERT
|
||||
#include <assert.h>
|
||||
#define ENKI_ASSERT(x) assert(x)
|
||||
#endif
|
||||
|
||||
#if (!defined(_MSVC_LANG) && __cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)
|
||||
#define ENKI_DEPRECATED [[deprecated]]
|
||||
#else
|
||||
#define ENKI_DEPRECATED
|
||||
#endif
|
||||
|
||||
namespace enki
|
||||
{
|
||||
struct TaskSetPartition
|
||||
{
|
||||
uint32_t start;
|
||||
uint32_t end;
|
||||
};
|
||||
|
||||
class TaskScheduler;
|
||||
class TaskPipe;
|
||||
class PinnedTaskList;
|
||||
class Dependency;
|
||||
struct ThreadArgs;
|
||||
struct ThreadDataStore;
|
||||
struct SubTaskSet;
|
||||
struct semaphoreid_t;
|
||||
|
||||
static constexpr uint32_t NO_THREAD_NUM = 0xFFFFFFFF;
|
||||
|
||||
ENKITS_API uint32_t GetNumHardwareThreads();
|
||||
|
||||
enum TaskPriority
|
||||
{
|
||||
TASK_PRIORITY_HIGH = 0,
|
||||
#if ( ENKITS_TASK_PRIORITIES_NUM > 3 )
|
||||
TASK_PRIORITY_MED_HI,
|
||||
#endif
|
||||
#if ( ENKITS_TASK_PRIORITIES_NUM > 2 )
|
||||
TASK_PRIORITY_MED,
|
||||
#endif
|
||||
#if ( ENKITS_TASK_PRIORITIES_NUM > 4 )
|
||||
TASK_PRIORITY_MED_LO,
|
||||
#endif
|
||||
#if ( ENKITS_TASK_PRIORITIES_NUM > 1 )
|
||||
TASK_PRIORITY_LOW,
|
||||
#endif
|
||||
TASK_PRIORITY_NUM
|
||||
};
|
||||
|
||||
// ICompletable is a base class used to check for completion.
|
||||
// Can be used with dependencies to wait for their completion.
|
||||
// Derive from ITaskSet or IPinnedTask for running parallel tasks.
|
||||
class ICompletable
|
||||
{
|
||||
public:
|
||||
bool GetIsComplete() const {
|
||||
return 0 == m_RunningCount.load( std::memory_order_acquire );
|
||||
}
|
||||
|
||||
virtual ~ICompletable();
|
||||
|
||||
// Dependency helpers, see Dependencies.cpp
|
||||
void SetDependency( Dependency& dependency_, const ICompletable* pDependencyTask_ );
|
||||
template<typename D, typename T, int SIZE> void SetDependenciesArr( D& dependencyArray_ , const T(&taskArray_)[SIZE] );
|
||||
template<typename D, typename T> void SetDependenciesArr( D& dependencyArray_, std::initializer_list<T*> taskpList_ );
|
||||
template<typename D, typename T, int SIZE> void SetDependenciesArr( D(&dependencyArray_)[SIZE], const T(&taskArray_)[SIZE] );
|
||||
template<typename D, typename T, int SIZE> void SetDependenciesArr( D(&dependencyArray_)[SIZE], std::initializer_list<T*> taskpList_ );
|
||||
template<typename D, typename T, int SIZE> void SetDependenciesVec( D& dependencyVec_, const T(&taskArray_)[SIZE] );
|
||||
template<typename D, typename T> void SetDependenciesVec( D& dependencyVec_, std::initializer_list<T*> taskpList_ );
|
||||
|
||||
TaskPriority m_Priority = TASK_PRIORITY_HIGH;
|
||||
protected:
|
||||
// Deriving from an ICompletable and overriding OnDependenciesComplete is advanced use.
|
||||
// If you do override OnDependenciesComplete() call:
|
||||
// ICompletable::OnDependenciesComplete( pTaskScheduler_, threadNum_ );
|
||||
// in your implementation.
|
||||
virtual void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ );
|
||||
private:
|
||||
friend class TaskScheduler;
|
||||
friend class Dependency;
|
||||
std::atomic<int32_t> m_RunningCount = {0};
|
||||
std::atomic<int32_t> m_DependenciesCompletedCount = {0};
|
||||
int32_t m_DependenciesCount = 0;
|
||||
mutable std::atomic<int32_t> m_WaitingForTaskCount = {0};
|
||||
mutable Dependency* m_pDependents = NULL;
|
||||
};
|
||||
|
||||
// Subclass ITaskSet to create tasks.
|
||||
// TaskSets can be re-used, but check completion first.
|
||||
class ITaskSet : public ICompletable
|
||||
{
|
||||
public:
|
||||
ITaskSet() = default;
|
||||
ITaskSet( uint32_t setSize_ )
|
||||
: m_SetSize( setSize_ )
|
||||
{}
|
||||
|
||||
ITaskSet( uint32_t setSize_, uint32_t minRange_ )
|
||||
: m_SetSize( setSize_ )
|
||||
, m_MinRange( minRange_ )
|
||||
, m_RangeToRun(minRange_)
|
||||
{}
|
||||
|
||||
// Execute range should be overloaded to process tasks. It will be called with a
|
||||
// range_ where range.start >= 0; range.start < range.end; and range.end < m_SetSize;
|
||||
// The range values should be mapped so that linearly processing them in order is cache friendly
|
||||
// i.e. neighbouring values should be close together.
|
||||
// threadnum_ should not be used for changing processing of data, its intended purpose
|
||||
// is to allow per-thread data buckets for output.
|
||||
virtual void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) = 0;
|
||||
|
||||
// Set Size - usually the number of data items to be processed, see ExecuteRange. Defaults to 1
|
||||
uint32_t m_SetSize = 1;
|
||||
|
||||
// Min Range - Minimum size of TaskSetPartition range when splitting a task set into partitions.
|
||||
// Designed for reducing scheduling overhead by preventing set being
|
||||
// divided up too small. Ranges passed to ExecuteRange will *not* be a multiple of this,
|
||||
// only attempts to deliver range sizes larger than this most of the time.
|
||||
// This should be set to a value which results in computation effort of at least 10k
|
||||
// clock cycles to minimize task scheduler overhead.
|
||||
// NOTE: The last partition will be smaller than m_MinRange if m_SetSize is not a multiple
|
||||
// of m_MinRange.
|
||||
// Also known as grain size in literature.
|
||||
uint32_t m_MinRange = 1;
|
||||
|
||||
private:
|
||||
friend class TaskScheduler;
|
||||
void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ ) final;
|
||||
uint32_t m_RangeToRun = 1;
|
||||
};
|
||||
|
||||
// Subclass IPinnedTask to create tasks which can be run on a given thread only.
|
||||
class IPinnedTask : public ICompletable
|
||||
{
|
||||
public:
|
||||
IPinnedTask() = default;
|
||||
IPinnedTask( uint32_t threadNum_ ) : threadNum(threadNum_) {} // default is to run a task on main thread
|
||||
|
||||
// IPinnedTask needs to be non-abstract for intrusive list functionality.
|
||||
// Should never be called as is, should be overridden.
|
||||
virtual void Execute() { ENKI_ASSERT(false); }
|
||||
|
||||
uint32_t threadNum = 0; // thread to run this pinned task on
|
||||
std::atomic<IPinnedTask*> pNext = {NULL};
|
||||
private:
|
||||
void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ ) final;
|
||||
};
|
||||
|
||||
// TaskSet - a utility task set for creating tasks based on std::function.
|
||||
typedef std::function<void (TaskSetPartition range, uint32_t threadnum )> TaskSetFunction;
|
||||
class TaskSet : public ITaskSet
|
||||
{
|
||||
public:
|
||||
TaskSet() = default;
|
||||
TaskSet( TaskSetFunction func_ ) : m_Function( std::move(func_) ) {}
|
||||
TaskSet( uint32_t setSize_, TaskSetFunction func_ ) : ITaskSet( setSize_ ), m_Function( std::move(func_) ) {}
|
||||
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override { m_Function( range_, threadnum_ ); }
|
||||
TaskSetFunction m_Function;
|
||||
};
|
||||
|
||||
// LambdaPinnedTask - a utility pinned task for creating tasks based on std::func.
|
||||
typedef std::function<void ()> PinnedTaskFunction;
|
||||
class LambdaPinnedTask : public IPinnedTask
|
||||
{
|
||||
public:
|
||||
LambdaPinnedTask() = default;
|
||||
LambdaPinnedTask( PinnedTaskFunction func_ ) : m_Function( std::move(func_) ) {}
|
||||
LambdaPinnedTask( uint32_t threadNum_, PinnedTaskFunction func_ ) : IPinnedTask( threadNum_ ), m_Function( std::move(func_) ) {}
|
||||
|
||||
void Execute() override { m_Function(); }
|
||||
PinnedTaskFunction m_Function;
|
||||
};
|
||||
|
||||
class Dependency
|
||||
{
|
||||
public:
|
||||
Dependency() = default;
|
||||
Dependency( const Dependency& ) = delete;
|
||||
ENKITS_API Dependency( Dependency&& ) noexcept;
|
||||
ENKITS_API Dependency( const ICompletable* pDependencyTask_, ICompletable* pTaskToRunOnCompletion_ );
|
||||
ENKITS_API ~Dependency();
|
||||
|
||||
ENKITS_API void SetDependency( const ICompletable* pDependencyTask_, ICompletable* pTaskToRunOnCompletion_ );
|
||||
ENKITS_API void ClearDependency();
|
||||
ICompletable* GetTaskToRunOnCompletion() { return pTaskToRunOnCompletion; }
|
||||
const ICompletable* GetDependencyTask() { return pDependencyTask; }
|
||||
private:
|
||||
friend class TaskScheduler; friend class ICompletable;
|
||||
ICompletable* pTaskToRunOnCompletion = NULL;
|
||||
const ICompletable* pDependencyTask = NULL;
|
||||
Dependency* pNext = NULL;
|
||||
};
|
||||
|
||||
// TaskScheduler implements several callbacks intended for profilers
|
||||
typedef void (*ProfilerCallbackFunc)( uint32_t threadnum_ );
|
||||
struct ProfilerCallbacks
|
||||
{
|
||||
ProfilerCallbackFunc threadStart;
|
||||
ProfilerCallbackFunc threadStop;
|
||||
ProfilerCallbackFunc waitForNewTaskSuspendStart; // thread suspended waiting for new tasks
|
||||
ProfilerCallbackFunc waitForNewTaskSuspendStop; // thread unsuspended
|
||||
ProfilerCallbackFunc waitForTaskCompleteStart; // thread waiting for task completion
|
||||
ProfilerCallbackFunc waitForTaskCompleteStop; // thread stopped waiting
|
||||
ProfilerCallbackFunc waitForTaskCompleteSuspendStart; // thread suspended waiting task completion
|
||||
ProfilerCallbackFunc waitForTaskCompleteSuspendStop; // thread unsuspended
|
||||
};
|
||||
|
||||
// Custom allocator, set in TaskSchedulerConfig. Also see ENKI_CUSTOM_ALLOC_FILE_AND_LINE for file_ and line_
|
||||
typedef void* (*AllocFunc)( size_t align_, size_t size_, void* userData_, const char* file_, int line_ );
|
||||
typedef void (*FreeFunc)( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ );
|
||||
ENKITS_API void* DefaultAllocFunc( size_t align_, size_t size_, void* userData_, const char* file_, int line_ );
|
||||
ENKITS_API void DefaultFreeFunc( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ );
|
||||
struct CustomAllocator
|
||||
{
|
||||
AllocFunc alloc = DefaultAllocFunc;
|
||||
FreeFunc free = DefaultFreeFunc;
|
||||
void* userData = nullptr;
|
||||
};
|
||||
|
||||
// TaskSchedulerConfig - configuration struct for advanced Initialize
|
||||
struct TaskSchedulerConfig
|
||||
{
|
||||
// numTaskThreadsToCreate - Number of tasking threads the task scheduler will create. Must be > 0.
|
||||
// Defaults to GetNumHardwareThreads()-1 threads as thread which calls initialize is thread 0.
|
||||
uint32_t numTaskThreadsToCreate = GetNumHardwareThreads()-1;
|
||||
|
||||
// numExternalTaskThreads - Advanced use. Number of external threads which need to use TaskScheduler API.
|
||||
// See TaskScheduler::RegisterExternalTaskThread() for usage.
|
||||
// Defaults to 0. The thread used to initialize the TaskScheduler can also use the TaskScheduler API.
|
||||
// Thus there are (numTaskThreadsToCreate + numExternalTaskThreads + 1) able to use the API, with this
|
||||
// defaulting to the number of hardware threads available to the system.
|
||||
uint32_t numExternalTaskThreads = 0;
|
||||
|
||||
ProfilerCallbacks profilerCallbacks = {};
|
||||
|
||||
CustomAllocator customAllocator;
|
||||
};
|
||||
|
||||
class TaskScheduler
|
||||
{
|
||||
public:
|
||||
ENKITS_API TaskScheduler();
|
||||
ENKITS_API ~TaskScheduler();
|
||||
|
||||
// Call an Initialize function before adding tasks.
|
||||
|
||||
// Initialize() will create GetNumHardwareThreads()-1 tasking threads, which is
|
||||
// sufficient to fill the system when including the main thread.
|
||||
// Initialize can be called multiple times - it will wait for completion
|
||||
// before re-initializing.
|
||||
ENKITS_API void Initialize();
|
||||
|
||||
// Initialize( numThreadsTotal_ )
|
||||
// will create numThreadsTotal_-1 threads, as thread 0 is
|
||||
// the thread on which the initialize was called.
|
||||
// numThreadsTotal_ must be > 0
|
||||
ENKITS_API void Initialize( uint32_t numThreadsTotal_ );
|
||||
|
||||
// Initialize with advanced TaskSchedulerConfig settings. See TaskSchedulerConfig.
|
||||
ENKITS_API void Initialize( TaskSchedulerConfig config_ );
|
||||
|
||||
// Get config. Can be called before Initialize to get the defaults.
|
||||
ENKITS_API TaskSchedulerConfig GetConfig() const;
|
||||
|
||||
// while( !GetIsShutdownRequested() ) {} can be used in tasks which loop, to check if enkiTS has been requested to shutdown.
|
||||
// If GetIsShutdownRequested() returns true should then exit. Not required for finite tasks
|
||||
// Safe to use with WaitforAllAndShutdown() and ShutdownNow() where this will be set
|
||||
// Not safe to use with WaitforAll(), use GetIsWaitforAllCalled() instead.
|
||||
inline bool GetIsShutdownRequested() const { return m_bShutdownRequested.load( std::memory_order_acquire ); }
|
||||
|
||||
// while( !GetIsWaitforAllCalled() ) {} can be used in tasks which loop, to check if WaitforAll() has been called.
|
||||
// If GetIsWaitforAllCalled() returns false should then exit. Not required for finite tasks
|
||||
// This is intended to be used with code which calls WaitforAll().
|
||||
// This is also set when the task manager is shutting down, so no need to have an additional check for GetIsShutdownRequested()
|
||||
inline bool GetIsWaitforAllCalled() const { return m_bWaitforAllCalled.load( std::memory_order_acquire ); }
|
||||
|
||||
// Adds the TaskSet to pipe and returns if the pipe is not full.
|
||||
// If the pipe is full, pTaskSet is run.
|
||||
// should only be called from main thread, or within a task
|
||||
ENKITS_API void AddTaskSetToPipe( ITaskSet* pTaskSet_ );
|
||||
|
||||
// Thread 0 is main thread, otherwise use threadNum
|
||||
// Pinned tasks can be added from any thread
|
||||
ENKITS_API void AddPinnedTask( IPinnedTask* pTask_ );
|
||||
|
||||
// This function will run any IPinnedTask* for current thread, but not run other
|
||||
// Main thread should call this or use a wait to ensure its tasks are run.
|
||||
ENKITS_API void RunPinnedTasks();
|
||||
|
||||
// Runs the TaskSets in pipe until true == pTaskSet->GetIsComplete();
|
||||
// Should only be called from thread which created the task scheduler, or within a task.
|
||||
// If called with 0 it will try to run tasks, and return if none are available.
|
||||
// To run only a subset of tasks, set priorityOfLowestToRun_ to a high priority.
|
||||
// Default is lowest priority available.
|
||||
// Only wait for child tasks of the current task otherwise a deadlock could occur.
|
||||
// WaitforTask will exit if ShutdownNow() is called even if pCompletable_ is not complete.
|
||||
ENKITS_API void WaitforTask( const ICompletable* pCompletable_, enki::TaskPriority priorityOfLowestToRun_ = TaskPriority(TASK_PRIORITY_NUM - 1) );
|
||||
|
||||
// Waits for all task sets to complete - not guaranteed to work unless we know we
|
||||
// are in a situation where tasks aren't being continuously added.
|
||||
// If you are running tasks which loop, make sure to check GetIsWaitforAllCalled() and exit
|
||||
// WaitforAll will exit if ShutdownNow() is called even if there are still tasks to run or currently running
|
||||
ENKITS_API void WaitforAll();
|
||||
|
||||
// Waits for all task sets to complete and shutdown threads - not guaranteed to work unless we know we
|
||||
// are in a situation where tasks aren't being continuously added.
|
||||
// This function can be safely called even if TaskScheduler::Initialize() has not been called.
|
||||
ENKITS_API void WaitforAllAndShutdown();
|
||||
|
||||
// Shutdown threads without waiting for all tasks to complete.
|
||||
// Intended to be used to exit an application quickly.
|
||||
// This function can be safely called even if TaskScheduler::Initialize() has not been called.
|
||||
// This function will still wait for any running tasks to exit before the task threads exit.
|
||||
// ShutdownNow will cause tasks which have been added to the scheduler but not completed
|
||||
// to be in an undefined state in which should not be re-launched.
|
||||
ENKITS_API void ShutdownNow();
|
||||
|
||||
// Waits for the current thread to receive a PinnedTask.
|
||||
// Will not run any tasks - use with RunPinnedTasks().
|
||||
// Can be used with both ExternalTaskThreads or with an enkiTS tasking thread to create
|
||||
// a thread which only runs pinned tasks. If enkiTS threads are used can create
|
||||
// extra enkiTS task threads to handle non-blocking computation via normal tasks.
|
||||
ENKITS_API void WaitForNewPinnedTasks();
|
||||
|
||||
// Returns the number of threads created for running tasks + number of external threads
|
||||
// plus 1 to account for the thread used to initialize the task scheduler.
|
||||
// Equivalent to config values: numTaskThreadsToCreate + numExternalTaskThreads + 1.
|
||||
// It is guaranteed that GetThreadNum() < GetNumTaskThreads()
|
||||
ENKITS_API uint32_t GetNumTaskThreads() const;
|
||||
|
||||
// Returns the current task threadNum.
|
||||
// Will return 0 for thread which initialized the task scheduler,
|
||||
// and NO_THREAD_NUM for all other non-enkiTS threads which have not been registered ( see RegisterExternalTaskThread() ),
|
||||
// and < GetNumTaskThreads() for all registered and internal enkiTS threads.
|
||||
// It is guaranteed that GetThreadNum() < GetNumTaskThreads() unless it is NO_THREAD_NUM
|
||||
ENKITS_API uint32_t GetThreadNum() const;
|
||||
|
||||
// Call on a thread to register the thread to use the TaskScheduling API.
|
||||
// This is implicitly done for the thread which initializes the TaskScheduler
|
||||
// Intended for developers who have threads who need to call the TaskScheduler API
|
||||
// Returns true if successful, false if not.
|
||||
// Can only have numExternalTaskThreads registered at any one time, which must be set
|
||||
// at initialization time.
|
||||
ENKITS_API bool RegisterExternalTaskThread();
|
||||
|
||||
// As RegisterExternalTaskThread() but explicitly requests a given thread number.
|
||||
// threadNumToRegister_ must be >= GetNumFirstExternalTaskThread()
|
||||
// and < ( GetNumFirstExternalTaskThread() + numExternalTaskThreads ).
|
||||
ENKITS_API bool RegisterExternalTaskThread( uint32_t threadNumToRegister_ );
|
||||
|
||||
// Call on a thread on which RegisterExternalTaskThread has been called to deregister that thread.
|
||||
ENKITS_API void DeRegisterExternalTaskThread();
|
||||
|
||||
// Get the number of registered external task threads.
|
||||
ENKITS_API uint32_t GetNumRegisteredExternalTaskThreads();
|
||||
|
||||
// Get the thread number of the first external task thread. This thread
|
||||
// is not guaranteed to be registered, but threads are registered in order
|
||||
// from GetNumFirstExternalTaskThread() up to ( GetNumFirstExternalTaskThread() + numExternalTaskThreads )
|
||||
// Note that if numExternalTaskThreads == 0 a for loop using this will be valid:
|
||||
// for( uint32_t externalThreadNum = GetNumFirstExternalTaskThread();
|
||||
// externalThreadNum < ( GetNumFirstExternalTaskThread() + numExternalTaskThreads
|
||||
// ++externalThreadNum ) { // do something with externalThreadNum }
|
||||
inline static constexpr uint32_t GetNumFirstExternalTaskThread() { return 1; }
|
||||
|
||||
// ------------- Start DEPRECATED Functions -------------
|
||||
// DEPRECATED: use GetIsShutdownRequested() instead of GetIsRunning() in external code
|
||||
// while( GetIsRunning() ) {} can be used in tasks which loop, to check if enkiTS has been shutdown.
|
||||
// If GetIsRunning() returns false should then exit. Not required for finite tasks.
|
||||
ENKI_DEPRECATED inline bool GetIsRunning() const { return !GetIsShutdownRequested(); }
|
||||
|
||||
// DEPRECATED - WaitforTaskSet, deprecated interface use WaitforTask.
|
||||
ENKI_DEPRECATED inline void WaitforTaskSet( const ICompletable* pCompletable_ ) { WaitforTask( pCompletable_ ); }
|
||||
|
||||
// DEPRECATED - GetProfilerCallbacks. Use TaskSchedulerConfig instead.
|
||||
// Returns the ProfilerCallbacks structure so that it can be modified to
|
||||
// set the callbacks. Should be set prior to initialization.
|
||||
ENKI_DEPRECATED inline ProfilerCallbacks* GetProfilerCallbacks() { return &m_Config.profilerCallbacks; }
|
||||
// ------------- End DEPRECATED Functions -------------
|
||||
|
||||
private:
|
||||
friend class ICompletable; friend class ITaskSet; friend class IPinnedTask;
|
||||
static void TaskingThreadFunction( const ThreadArgs& args_ );
|
||||
bool HaveTasks( uint32_t threadNum_ );
|
||||
void WaitForNewTasks( uint32_t threadNum_ );
|
||||
void WaitForTaskCompletion( const ICompletable* pCompletable_, uint32_t threadNum_ );
|
||||
void RunPinnedTasks( uint32_t threadNum_, uint32_t priority_ );
|
||||
bool TryRunTask( uint32_t threadNum_, uint32_t& hintPipeToCheck_io_ );
|
||||
bool TryRunTask( uint32_t threadNum_, uint32_t priority_, uint32_t& hintPipeToCheck_io_ );
|
||||
void StartThreads();
|
||||
void StopThreads( bool bWait_ );
|
||||
void SplitAndAddTask( uint32_t threadNum_, SubTaskSet subTask_, uint32_t rangeToSplit_ );
|
||||
void WakeThreadsForNewTasks();
|
||||
void WakeThreadsForTaskCompletion();
|
||||
bool WakeSuspendedThreadsWithPinnedTasks( uint32_t threadNum_ );
|
||||
void InitDependencies( ICompletable* pCompletable_ );
|
||||
inline bool GetIsRunningInt() const { return m_bRunning.load( std::memory_order_acquire ); }
|
||||
|
||||
ENKITS_API void TaskComplete( ICompletable* pTask_, bool bWakeThreads_, uint32_t threadNum_ );
|
||||
ENKITS_API void AddTaskSetToPipeInt( ITaskSet* pTaskSet_, uint32_t threadNum_ );
|
||||
ENKITS_API void AddPinnedTaskInt( IPinnedTask* pTask_ );
|
||||
|
||||
template< typename T > T* NewArray( size_t num_, const char* file_, int line_ );
|
||||
template< typename T > void DeleteArray( T* p_, size_t num_, const char* file_, int line_ );
|
||||
template<class T, class... Args> T* New( const char* file_, int line_, Args&&... args_ );
|
||||
template< typename T > void Delete( T* p_, const char* file_, int line_ );
|
||||
template< typename T > T* Alloc( const char* file_, int line_ );
|
||||
template< typename T > void Free( T* p_, const char* file_, int line_ );
|
||||
semaphoreid_t* SemaphoreNew();
|
||||
void SemaphoreDelete( semaphoreid_t* pSemaphore_ );
|
||||
|
||||
TaskPipe* m_pPipesPerThread[ TASK_PRIORITY_NUM ];
|
||||
PinnedTaskList* m_pPinnedTaskListPerThread[ TASK_PRIORITY_NUM ];
|
||||
|
||||
uint32_t m_NumThreads;
|
||||
ThreadDataStore* m_pThreadDataStore;
|
||||
std::thread* m_pThreads;
|
||||
std::atomic<bool> m_bRunning;
|
||||
std::atomic<bool> m_bShutdownRequested;
|
||||
std::atomic<bool> m_bWaitforAllCalled;
|
||||
std::atomic<int32_t> m_NumInternalTaskThreadsRunning;
|
||||
std::atomic<int32_t> m_NumThreadsWaitingForNewTasks;
|
||||
std::atomic<int32_t> m_NumThreadsWaitingForTaskCompletion;
|
||||
uint32_t m_NumPartitions;
|
||||
semaphoreid_t* m_pNewTaskSemaphore;
|
||||
semaphoreid_t* m_pTaskCompleteSemaphore;
|
||||
uint32_t m_NumInitialPartitions;
|
||||
bool m_bHaveThreads;
|
||||
TaskSchedulerConfig m_Config;
|
||||
std::atomic<int32_t> m_NumExternalTaskThreadsRegistered;
|
||||
|
||||
TaskScheduler( const TaskScheduler& nocopy_ );
|
||||
TaskScheduler& operator=( const TaskScheduler& nocopy_ );
|
||||
|
||||
protected:
|
||||
void SetCustomAllocator( CustomAllocator customAllocator_ ); // for C interface
|
||||
};
|
||||
|
||||
inline void ICompletable::OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ )
|
||||
{
|
||||
m_RunningCount.fetch_sub( 1, std::memory_order_acq_rel );
|
||||
pTaskScheduler_->TaskComplete( this, true, threadNum_ );
|
||||
}
|
||||
|
||||
inline void ITaskSet::OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ )
|
||||
{
|
||||
pTaskScheduler_->AddTaskSetToPipeInt( this, threadNum_ );
|
||||
}
|
||||
|
||||
inline void IPinnedTask::OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ )
|
||||
{
|
||||
(void)threadNum_;
|
||||
pTaskScheduler_->AddPinnedTaskInt( this );
|
||||
}
|
||||
|
||||
inline ICompletable::~ICompletable()
|
||||
{
|
||||
ENKI_ASSERT( GetIsComplete() ); // this task is still waiting to run
|
||||
Dependency* pDependency = m_pDependents;
|
||||
while( pDependency )
|
||||
{
|
||||
Dependency* pNext = pDependency->pNext;
|
||||
pDependency->pDependencyTask = NULL;
|
||||
pDependency->pNext = NULL;
|
||||
pDependency = pNext;
|
||||
}
|
||||
}
|
||||
|
||||
inline void ICompletable::SetDependency( Dependency& dependency_, const ICompletable* pDependencyTask_ )
|
||||
{
|
||||
ENKI_ASSERT( pDependencyTask_ != this );
|
||||
dependency_.SetDependency( pDependencyTask_, this );
|
||||
}
|
||||
|
||||
template<typename D, typename T, int SIZE>
|
||||
void ICompletable::SetDependenciesArr( D& dependencyArray_ , const T(&taskArray_)[SIZE] ) {
|
||||
static_assert( std::tuple_size<D>::value >= SIZE, "Size of dependency array too small" );
|
||||
for( int i = 0; i < SIZE; ++i )
|
||||
{
|
||||
dependencyArray_[i].SetDependency( &taskArray_[i], this );
|
||||
}
|
||||
}
|
||||
template<typename D, typename T>
|
||||
void ICompletable::SetDependenciesArr( D& dependencyArray_, std::initializer_list<T*> taskpList_ ) {
|
||||
ENKI_ASSERT( std::tuple_size<D>::value >= taskpList_.size() );
|
||||
int i = 0;
|
||||
for( auto pTask : taskpList_ )
|
||||
{
|
||||
dependencyArray_[i++].SetDependency( pTask, this );
|
||||
}
|
||||
}
|
||||
template<typename D, typename T, int SIZE>
|
||||
void ICompletable::SetDependenciesArr( D(&dependencyArray_)[SIZE], const T(&taskArray_)[SIZE] ) {
|
||||
for( int i = 0; i < SIZE; ++i )
|
||||
{
|
||||
dependencyArray_[i].SetDependency( &taskArray_[i], this );
|
||||
}
|
||||
}
|
||||
template<typename D, typename T, int SIZE>
|
||||
void ICompletable::SetDependenciesArr( D(&dependencyArray_)[SIZE], std::initializer_list<T*> taskpList_ ) {
|
||||
ENKI_ASSERT( SIZE >= taskpList_.size() );
|
||||
int i = 0;
|
||||
for( auto pTask : taskpList_ )
|
||||
{
|
||||
dependencyArray_[i++].SetDependency( pTask, this );
|
||||
}
|
||||
}
|
||||
template<typename D, typename T, int SIZE>
|
||||
void ICompletable::SetDependenciesVec( D& dependencyVec_, const T(&taskArray_)[SIZE] ) {
|
||||
dependencyVec_.resize( SIZE );
|
||||
for( int i = 0; i < SIZE; ++i )
|
||||
{
|
||||
dependencyVec_[i].SetDependency( &taskArray_[i], this );
|
||||
}
|
||||
}
|
||||
|
||||
template<typename D, typename T>
|
||||
void ICompletable::SetDependenciesVec( D& dependencyVec_, std::initializer_list<T*> taskpList_ ) {
|
||||
dependencyVec_.resize( taskpList_.size() );
|
||||
int i = 0;
|
||||
for( auto pTask : taskpList_ )
|
||||
{
|
||||
dependencyVec_[i++].SetDependency( pTask, this );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
// Copyright (c) 2013 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#include "TaskScheduler_c.h"
|
||||
#include "TaskScheduler.h"
|
||||
|
||||
using namespace enki;
|
||||
|
||||
#if defined(ENKI_CUSTOM_ALLOC_FILE_AND_LINE)
|
||||
#define ENKI_FILE_AND_LINE __FILE__, __LINE__
|
||||
#else
|
||||
namespace
|
||||
{
|
||||
const char* gc_File = "";
|
||||
const uint32_t gc_Line = 0;
|
||||
}
|
||||
#define ENKI_FILE_AND_LINE gc_File, gc_Line
|
||||
#endif
|
||||
|
||||
void* enkiDefaultAllocFunc( size_t align_, size_t size_, void* userData_, const char* file_, int line_ )
|
||||
{
|
||||
return enki::DefaultAllocFunc( align_, size_, userData_, file_, line_ );
|
||||
}
|
||||
|
||||
void enkiDefaultFreeFunc( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ )
|
||||
{
|
||||
return enki::DefaultFreeFunc( ptr_, size_, userData_, file_, line_ );
|
||||
}
|
||||
|
||||
struct enkiTaskScheduler : TaskScheduler
|
||||
{
|
||||
void enkiSetCustomAllocator( CustomAllocator customAllocator_ )
|
||||
{
|
||||
SetCustomAllocator( customAllocator_ );
|
||||
}
|
||||
};
|
||||
|
||||
struct enkiCompletable : ICompletable {}; // empty struct which we will use for completables
|
||||
|
||||
struct enkiTaskSet : ITaskSet
|
||||
{
|
||||
enkiTaskSet( enkiTaskExecuteRange taskFun_ ) : taskFun(taskFun_), pArgs(NULL) {}
|
||||
|
||||
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
|
||||
{
|
||||
taskFun( range_.start, range_.end, threadnum_, pArgs );
|
||||
}
|
||||
|
||||
enkiTaskExecuteRange taskFun;
|
||||
void* pArgs = NULL;
|
||||
};
|
||||
|
||||
struct enkiPinnedTask : IPinnedTask
|
||||
{
|
||||
enkiPinnedTask( enkiPinnedTaskExecute taskFun_, uint32_t threadNum_ )
|
||||
: IPinnedTask( threadNum_ ), taskFun(taskFun_), pArgs(NULL) {}
|
||||
|
||||
void Execute() override
|
||||
{
|
||||
taskFun( pArgs );
|
||||
}
|
||||
|
||||
enkiPinnedTaskExecute taskFun;
|
||||
void* pArgs = NULL;
|
||||
};
|
||||
|
||||
struct enkiCompletionAction : ICompletable
|
||||
{
|
||||
void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ ) override
|
||||
{
|
||||
if( completionFunctionPreComplete )
|
||||
{
|
||||
completionFunctionPreComplete( pArgsPreComplete, threadNum_ );
|
||||
}
|
||||
|
||||
// make temporaries for post completion as this task could get deleted after OnDependenciesComplete
|
||||
enkiCompletionFunction tempCompletionFunctionPostComplete = completionFunctionPostComplete;
|
||||
void* ptempArgsPostComplete = pArgsPostComplete;
|
||||
|
||||
ICompletable::OnDependenciesComplete( pTaskScheduler_, threadNum_ );
|
||||
|
||||
if( tempCompletionFunctionPostComplete )
|
||||
{
|
||||
tempCompletionFunctionPostComplete( ptempArgsPostComplete, threadNum_ );
|
||||
}
|
||||
}
|
||||
|
||||
enkiCompletionFunction completionFunctionPreComplete;
|
||||
enkiCompletionFunction completionFunctionPostComplete;
|
||||
Dependency dependency;
|
||||
void* pArgsPreComplete = NULL;
|
||||
void* pArgsPostComplete = NULL;
|
||||
};
|
||||
|
||||
struct enkiDependency : Dependency {}; // empty struct which we will use for dependencies
|
||||
|
||||
enkiTaskScheduler* enkiNewTaskScheduler()
|
||||
{
|
||||
CustomAllocator defaultAllocator;
|
||||
enkiTaskScheduler* pETS = (enkiTaskScheduler*)defaultAllocator.alloc(
|
||||
alignof(enkiTaskScheduler), sizeof(enkiTaskScheduler), defaultAllocator.userData, ENKI_FILE_AND_LINE );
|
||||
new(pETS) enkiTaskScheduler;
|
||||
return pETS;
|
||||
}
|
||||
|
||||
enkiTaskScheduler* enkiNewTaskSchedulerWithCustomAllocator( struct enkiCustomAllocator customAllocator_ )
|
||||
{
|
||||
enkiTaskScheduler* pETS = (enkiTaskScheduler*)customAllocator_.alloc(
|
||||
alignof(enkiTaskScheduler), sizeof(enkiTaskScheduler), customAllocator_.userData, ENKI_FILE_AND_LINE );
|
||||
|
||||
CustomAllocator customAllocatorCpp;
|
||||
customAllocatorCpp.alloc = customAllocator_.alloc;
|
||||
customAllocatorCpp.free = customAllocator_.free;
|
||||
customAllocatorCpp.userData = customAllocator_.userData;
|
||||
|
||||
new(pETS) enkiTaskScheduler;
|
||||
pETS->enkiSetCustomAllocator( customAllocatorCpp );
|
||||
return pETS;
|
||||
}
|
||||
|
||||
struct enkiTaskSchedulerConfig enkiGetTaskSchedulerConfig( enkiTaskScheduler* pETS_ )
|
||||
{
|
||||
TaskSchedulerConfig config = pETS_->GetConfig();
|
||||
enkiTaskSchedulerConfig configC;
|
||||
configC.numExternalTaskThreads = config.numExternalTaskThreads;
|
||||
configC.numTaskThreadsToCreate = config.numTaskThreadsToCreate;
|
||||
configC.profilerCallbacks.threadStart = config.profilerCallbacks.threadStart;
|
||||
configC.profilerCallbacks.threadStop = config.profilerCallbacks.threadStop;
|
||||
configC.profilerCallbacks.waitForNewTaskSuspendStart = config.profilerCallbacks.waitForNewTaskSuspendStart;
|
||||
configC.profilerCallbacks.waitForNewTaskSuspendStop = config.profilerCallbacks.waitForNewTaskSuspendStop;
|
||||
configC.profilerCallbacks.waitForTaskCompleteStart = config.profilerCallbacks.waitForTaskCompleteStart;
|
||||
configC.profilerCallbacks.waitForTaskCompleteStop = config.profilerCallbacks.waitForTaskCompleteStop;
|
||||
configC.profilerCallbacks.waitForTaskCompleteSuspendStart = config.profilerCallbacks.waitForTaskCompleteSuspendStart;
|
||||
configC.profilerCallbacks.waitForTaskCompleteSuspendStop = config.profilerCallbacks.waitForTaskCompleteSuspendStop;
|
||||
configC.customAllocator.alloc = config.customAllocator.alloc;
|
||||
configC.customAllocator.free = config.customAllocator.free;
|
||||
configC.customAllocator.userData = config.customAllocator.userData;
|
||||
return configC;
|
||||
}
|
||||
|
||||
int enkiGetIsRunning( enkiTaskScheduler* pETS_ )
|
||||
{
|
||||
return (int)(!pETS_->GetIsShutdownRequested());
|
||||
}
|
||||
|
||||
int enkiGetIsShutdownRequested(enkiTaskScheduler* pETS_)
|
||||
{
|
||||
return (int)pETS_->GetIsShutdownRequested();
|
||||
}
|
||||
|
||||
int enkiGetIsWaitforAllCalled( enkiTaskScheduler* pETS_ )
|
||||
{
|
||||
return (int)pETS_->GetIsWaitforAllCalled();
|
||||
}
|
||||
|
||||
void enkiInitTaskScheduler( enkiTaskScheduler* pETS_ )
|
||||
{
|
||||
pETS_->Initialize();
|
||||
}
|
||||
|
||||
void enkiInitTaskSchedulerNumThreads( enkiTaskScheduler* pETS_, uint32_t numThreads_ )
|
||||
{
|
||||
pETS_->Initialize( numThreads_ );
|
||||
}
|
||||
|
||||
void enkiInitTaskSchedulerWithConfig( enkiTaskScheduler* pETS_, struct enkiTaskSchedulerConfig config_ )
|
||||
{
|
||||
TaskSchedulerConfig config;
|
||||
config.numExternalTaskThreads = config_.numExternalTaskThreads;
|
||||
config.numTaskThreadsToCreate = config_.numTaskThreadsToCreate;
|
||||
config.profilerCallbacks.threadStart = config_.profilerCallbacks.threadStart;
|
||||
config.profilerCallbacks.threadStop = config_.profilerCallbacks.threadStop;
|
||||
config.profilerCallbacks.waitForNewTaskSuspendStart = config_.profilerCallbacks.waitForNewTaskSuspendStart;
|
||||
config.profilerCallbacks.waitForNewTaskSuspendStop = config_.profilerCallbacks.waitForNewTaskSuspendStop;
|
||||
config.profilerCallbacks.waitForTaskCompleteStart = config_.profilerCallbacks.waitForTaskCompleteStart;
|
||||
config.profilerCallbacks.waitForTaskCompleteStop = config_.profilerCallbacks.waitForTaskCompleteStop;
|
||||
config.profilerCallbacks.waitForTaskCompleteSuspendStart = config_.profilerCallbacks.waitForTaskCompleteSuspendStart;
|
||||
config.profilerCallbacks.waitForTaskCompleteSuspendStop = config_.profilerCallbacks.waitForTaskCompleteSuspendStop;
|
||||
config.customAllocator.alloc = config_.customAllocator.alloc;
|
||||
config.customAllocator.free = config_.customAllocator.free;
|
||||
config.customAllocator.userData = config_.customAllocator.userData;
|
||||
pETS_->Initialize( config );
|
||||
}
|
||||
|
||||
void enkiWaitforAllAndShutdown( enkiTaskScheduler* pETS_ )
|
||||
{
|
||||
pETS_->WaitforAllAndShutdown();
|
||||
}
|
||||
|
||||
void enkiDeleteTaskScheduler( enkiTaskScheduler* pETS_ )
|
||||
{
|
||||
CustomAllocator customAllocator = pETS_->GetConfig().customAllocator;
|
||||
pETS_->~enkiTaskScheduler();
|
||||
customAllocator.free( pETS_, sizeof(enkiTaskScheduler), customAllocator.userData, ENKI_FILE_AND_LINE );
|
||||
}
|
||||
|
||||
ENKITS_API uint32_t enkiGetNumFirstExternalTaskThread()
|
||||
{
|
||||
return enkiTaskScheduler::GetNumFirstExternalTaskThread();
|
||||
}
|
||||
|
||||
enkiTaskSet* enkiCreateTaskSet( enkiTaskScheduler* pETS_, enkiTaskExecuteRange taskFunc_ )
|
||||
{
|
||||
CustomAllocator customAllocator = pETS_->GetConfig().customAllocator;
|
||||
enkiTaskSet* pTask = (enkiTaskSet*)customAllocator.alloc(
|
||||
alignof(enkiTaskSet), sizeof(enkiTaskSet), customAllocator.userData, ENKI_FILE_AND_LINE );
|
||||
new(pTask) enkiTaskSet( taskFunc_ );
|
||||
|
||||
return pTask;
|
||||
}
|
||||
|
||||
void enkiDeleteTaskSet( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ )
|
||||
{
|
||||
CustomAllocator customAllocator = pETS_->GetConfig().customAllocator;
|
||||
|
||||
pTaskSet_->~enkiTaskSet();
|
||||
customAllocator.free( pTaskSet_, sizeof(enkiTaskSet), customAllocator.userData, ENKI_FILE_AND_LINE );
|
||||
}
|
||||
|
||||
enkiParamsTaskSet enkiGetParamsTaskSet( enkiTaskSet* pTaskSet_ )
|
||||
{
|
||||
enkiParamsTaskSet paramsTaskSet;
|
||||
paramsTaskSet.pArgs = pTaskSet_->pArgs;
|
||||
paramsTaskSet.setSize = pTaskSet_->m_SetSize;
|
||||
paramsTaskSet.minRange = pTaskSet_->m_MinRange;
|
||||
paramsTaskSet.priority = pTaskSet_->m_Priority;
|
||||
return paramsTaskSet;
|
||||
}
|
||||
|
||||
void enkiSetParamsTaskSet( enkiTaskSet* pTaskSet_, enkiParamsTaskSet params_ )
|
||||
{
|
||||
ENKI_ASSERT( params_.priority < ENKITS_TASK_PRIORITIES_NUM );
|
||||
pTaskSet_->pArgs = params_.pArgs;
|
||||
pTaskSet_->m_SetSize = params_.setSize;
|
||||
pTaskSet_->m_MinRange = params_.minRange;
|
||||
pTaskSet_->m_Priority = TaskPriority( params_.priority );
|
||||
}
|
||||
|
||||
void enkiSetPriorityTaskSet( enkiTaskSet* pTaskSet_, int priority_ )
|
||||
{
|
||||
ENKI_ASSERT( priority_ < ENKITS_TASK_PRIORITIES_NUM );
|
||||
pTaskSet_->m_Priority = TaskPriority( priority_ );
|
||||
}
|
||||
|
||||
void enkiSetArgsTaskSet( enkiTaskSet* pTaskSet_, void* pArgs_ )
|
||||
{
|
||||
pTaskSet_->pArgs = pArgs_;
|
||||
}
|
||||
|
||||
void enkiSetSetSizeTaskSet( enkiTaskSet* pTaskSet_, uint32_t setSize_ )
|
||||
{
|
||||
pTaskSet_->m_SetSize = setSize_;
|
||||
}
|
||||
|
||||
void enkiSetMinRangeTaskSet( enkiTaskSet* pTaskSet_, uint32_t minRange_ )
|
||||
{
|
||||
pTaskSet_->m_MinRange = minRange_;
|
||||
}
|
||||
|
||||
void enkiAddTaskSet( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ )
|
||||
{
|
||||
ENKI_ASSERT( pTaskSet_ );
|
||||
ENKI_ASSERT( pTaskSet_->taskFun );
|
||||
|
||||
pETS_->AddTaskSetToPipe( pTaskSet_ );
|
||||
}
|
||||
|
||||
void enkiAddTaskSetArgs( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_, void* pArgs_, uint32_t setSize_ )
|
||||
{
|
||||
ENKI_ASSERT( pTaskSet_ );
|
||||
ENKI_ASSERT( pTaskSet_->taskFun );
|
||||
|
||||
pTaskSet_->m_SetSize = setSize_;
|
||||
pTaskSet_->pArgs = pArgs_;
|
||||
pETS_->AddTaskSetToPipe( pTaskSet_ );
|
||||
}
|
||||
|
||||
void enkiAddTaskSetMinRange(enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_, void* pArgs_, uint32_t setSize_, uint32_t minRange_)
|
||||
{
|
||||
ENKI_ASSERT( pTaskSet_ );
|
||||
ENKI_ASSERT( pTaskSet_->taskFun );
|
||||
|
||||
pTaskSet_->m_SetSize = setSize_;
|
||||
pTaskSet_->m_MinRange = minRange_;
|
||||
pTaskSet_->pArgs = pArgs_;
|
||||
pETS_->AddTaskSetToPipe( pTaskSet_ );
|
||||
}
|
||||
|
||||
int enkiIsTaskSetComplete( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ )
|
||||
{
|
||||
(void)pETS_;
|
||||
ENKI_ASSERT( pTaskSet_ );
|
||||
return ( pTaskSet_->GetIsComplete() ) ? 1 : 0;
|
||||
}
|
||||
|
||||
enkiPinnedTask* enkiCreatePinnedTask(enkiTaskScheduler* pETS_, enkiPinnedTaskExecute taskFunc_, uint32_t threadNum_)
|
||||
{
|
||||
CustomAllocator customAllocator = pETS_->GetConfig().customAllocator;
|
||||
enkiPinnedTask* pTask = (enkiPinnedTask*)customAllocator.alloc(
|
||||
alignof(enkiPinnedTask), sizeof(enkiPinnedTask), customAllocator.userData, ENKI_FILE_AND_LINE );
|
||||
new(pTask) enkiPinnedTask( taskFunc_, threadNum_ );
|
||||
return pTask;
|
||||
}
|
||||
|
||||
void enkiDeletePinnedTask( enkiTaskScheduler* pETS_, enkiPinnedTask* pPinnedTask_ )
|
||||
{
|
||||
CustomAllocator customAllocator = pETS_->GetConfig().customAllocator;
|
||||
|
||||
pPinnedTask_->~enkiPinnedTask();
|
||||
customAllocator.free( pPinnedTask_, sizeof(enkiPinnedTask), customAllocator.userData, ENKI_FILE_AND_LINE );
|
||||
}
|
||||
|
||||
enkiParamsPinnedTask enkiGetParamsPinnedTask( enkiPinnedTask* pTask_ )
|
||||
{
|
||||
enkiParamsPinnedTask paramsPinnedTask;
|
||||
paramsPinnedTask.pArgs = pTask_->pArgs;
|
||||
paramsPinnedTask.priority = pTask_->m_Priority;
|
||||
return paramsPinnedTask;
|
||||
}
|
||||
|
||||
void enkiSetParamsPinnedTask( enkiPinnedTask* pTask_, enkiParamsPinnedTask params_ )
|
||||
{
|
||||
ENKI_ASSERT( params_.priority < ENKITS_TASK_PRIORITIES_NUM );
|
||||
pTask_->pArgs = params_.pArgs;
|
||||
pTask_->m_Priority = TaskPriority( params_.priority );
|
||||
}
|
||||
|
||||
void enkiSetPriorityPinnedTask( enkiPinnedTask* pTask_, int priority_ )
|
||||
{
|
||||
ENKI_ASSERT( priority_ < ENKITS_TASK_PRIORITIES_NUM );
|
||||
pTask_->m_Priority = TaskPriority( priority_ );
|
||||
}
|
||||
|
||||
void enkiSetArgsPinnedTask( enkiPinnedTask* pTask_, void* pArgs_ )
|
||||
{
|
||||
pTask_->pArgs = pArgs_;
|
||||
}
|
||||
|
||||
void enkiAddPinnedTask(enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_)
|
||||
{
|
||||
ENKI_ASSERT( pTask_ );
|
||||
pETS_->AddPinnedTask( pTask_ );
|
||||
}
|
||||
|
||||
void enkiAddPinnedTaskArgs(enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_, void* pArgs_)
|
||||
{
|
||||
ENKI_ASSERT( pTask_ );
|
||||
pTask_->pArgs = pArgs_;
|
||||
pETS_->AddPinnedTask( pTask_ );
|
||||
}
|
||||
|
||||
void enkiRunPinnedTasks(enkiTaskScheduler* pETS_)
|
||||
{
|
||||
pETS_->RunPinnedTasks();
|
||||
}
|
||||
|
||||
int enkiIsPinnedTaskComplete(enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_)
|
||||
{
|
||||
ENKI_ASSERT( pTask_ );
|
||||
return ( pTask_->GetIsComplete() ) ? 1 : 0;
|
||||
}
|
||||
|
||||
void enkiWaitForTaskSet( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ )
|
||||
{
|
||||
pETS_->WaitforTask( pTaskSet_ );
|
||||
}
|
||||
|
||||
void enkiWaitForTaskSetPriority( enkiTaskScheduler * pETS_, enkiTaskSet * pTaskSet_, int maxPriority_ )
|
||||
{
|
||||
pETS_->WaitforTask( pTaskSet_, TaskPriority( maxPriority_ ) );
|
||||
}
|
||||
|
||||
void enkiWaitForPinnedTask( enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_ )
|
||||
{
|
||||
pETS_->WaitforTask( pTask_ );
|
||||
}
|
||||
|
||||
void enkiWaitForPinnedTaskPriority( enkiTaskScheduler * pETS_, enkiPinnedTask * pTask_, int maxPriority_ )
|
||||
{
|
||||
pETS_->WaitforTask( pTask_, TaskPriority( maxPriority_ ) );
|
||||
}
|
||||
|
||||
void enkiWaitForNewPinnedTasks( enkiTaskScheduler* pETS_ )
|
||||
{
|
||||
pETS_->WaitForNewPinnedTasks();
|
||||
}
|
||||
|
||||
|
||||
void enkiWaitForAll( enkiTaskScheduler* pETS_ )
|
||||
{
|
||||
pETS_->WaitforAll();
|
||||
}
|
||||
|
||||
uint32_t enkiGetNumTaskThreads( enkiTaskScheduler* pETS_ )
|
||||
{
|
||||
return pETS_->GetNumTaskThreads();
|
||||
}
|
||||
|
||||
uint32_t enkiGetThreadNum( enkiTaskScheduler* pETS_ )
|
||||
{
|
||||
return pETS_->GetThreadNum();
|
||||
}
|
||||
|
||||
int enkiRegisterExternalTaskThread( enkiTaskScheduler* pETS_)
|
||||
{
|
||||
return (int)pETS_->RegisterExternalTaskThread();
|
||||
}
|
||||
|
||||
int enkiRegisterExternalTaskThreadNum( enkiTaskScheduler* pETS_, uint32_t threadNumToRegister_ )
|
||||
{
|
||||
return (int)pETS_->RegisterExternalTaskThread( threadNumToRegister_ ) ;
|
||||
}
|
||||
|
||||
void enkiDeRegisterExternalTaskThread( enkiTaskScheduler* pETS_)
|
||||
{
|
||||
return pETS_->DeRegisterExternalTaskThread();
|
||||
}
|
||||
|
||||
uint32_t enkiGetNumRegisteredExternalTaskThreads( enkiTaskScheduler* pETS_)
|
||||
{
|
||||
return pETS_->GetNumRegisteredExternalTaskThreads();
|
||||
}
|
||||
|
||||
enkiCompletable* enkiGetCompletableFromTaskSet( enkiTaskSet* pTaskSet_ )
|
||||
{
|
||||
return reinterpret_cast<enkiCompletable*>( static_cast<ICompletable*>( pTaskSet_ ) );
|
||||
}
|
||||
|
||||
enkiCompletable* enkiGetCompletableFromPinnedTask( enkiPinnedTask* pPinnedTask_ )
|
||||
{
|
||||
return reinterpret_cast<enkiCompletable*>( static_cast<ICompletable*>( pPinnedTask_ ) );
|
||||
}
|
||||
|
||||
enkiCompletable* enkiGetCompletableFromCompletionAction( enkiCompletionAction* pCompletionAction_ )
|
||||
{
|
||||
return reinterpret_cast<enkiCompletable*>( static_cast<ICompletable*>( pCompletionAction_ ) );
|
||||
}
|
||||
|
||||
enkiCompletable* enkiCreateCompletable( enkiTaskScheduler* pETS_ )
|
||||
{
|
||||
CustomAllocator customAllocator = pETS_->GetConfig().customAllocator;
|
||||
enkiCompletable* pTask = (enkiCompletable*)customAllocator.alloc(
|
||||
alignof(enkiCompletable), sizeof(enkiCompletable), customAllocator.userData, ENKI_FILE_AND_LINE );
|
||||
new(pTask) enkiCompletable();
|
||||
return pTask;
|
||||
}
|
||||
|
||||
void enkiDeleteCompletable( enkiTaskScheduler* pETS_, enkiCompletable* pCompletable_ )
|
||||
{
|
||||
CustomAllocator customAllocator = pETS_->GetConfig().customAllocator;
|
||||
|
||||
pCompletable_->~enkiCompletable();
|
||||
customAllocator.free( pCompletable_, sizeof(enkiCompletable), customAllocator.userData, ENKI_FILE_AND_LINE );
|
||||
}
|
||||
|
||||
void enkiWaitForCompletable( enkiTaskScheduler* pETS_, enkiCompletable* pTask_ )
|
||||
{
|
||||
return pETS_->WaitforTask( reinterpret_cast<ICompletable*>( pTask_ ) );
|
||||
}
|
||||
|
||||
void enkiWaitForCompletablePriority( enkiTaskScheduler* pETS_, enkiCompletable* pTask_, int maxPriority_ )
|
||||
{
|
||||
return pETS_->WaitforTask( reinterpret_cast<ICompletable*>( pTask_ ), TaskPriority( maxPriority_ ) );
|
||||
}
|
||||
|
||||
enkiDependency* enkiCreateDependency( enkiTaskScheduler* pETS_ )
|
||||
{
|
||||
CustomAllocator customAllocator = pETS_->GetConfig().customAllocator;
|
||||
enkiDependency* pDep = (enkiDependency*)customAllocator.alloc(
|
||||
alignof(enkiDependency), sizeof(enkiDependency), customAllocator.userData, ENKI_FILE_AND_LINE );
|
||||
new(pDep) enkiDependency();
|
||||
return pDep;
|
||||
}
|
||||
|
||||
void enkiDeleteDependency( enkiTaskScheduler* pETS_, enkiDependency* pDependency_ )
|
||||
{
|
||||
CustomAllocator customAllocator = pETS_->GetConfig().customAllocator;
|
||||
|
||||
pDependency_->~enkiDependency();
|
||||
customAllocator.free( pDependency_, sizeof(enkiDependency), customAllocator.userData, ENKI_FILE_AND_LINE );
|
||||
}
|
||||
|
||||
void enkiSetDependency( enkiDependency* pDependency_, enkiCompletable* pDependencyTask_, enkiCompletable* pTaskToRunOnCompletion_ )
|
||||
{
|
||||
pTaskToRunOnCompletion_->SetDependency( *pDependency_, pDependencyTask_ );
|
||||
}
|
||||
|
||||
enkiCompletionAction* enkiCreateCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionFunction completionFunctionPreComplete_, enkiCompletionFunction completionFunctionPostComplete_ )
|
||||
{
|
||||
CustomAllocator customAllocator = pETS_->GetConfig().customAllocator;
|
||||
enkiCompletionAction* pCA = (enkiCompletionAction*)customAllocator.alloc(
|
||||
alignof(enkiCompletionAction), sizeof(enkiCompletionAction), customAllocator.userData, ENKI_FILE_AND_LINE );
|
||||
new(pCA) enkiCompletionAction();
|
||||
pCA->completionFunctionPreComplete = completionFunctionPreComplete_;
|
||||
pCA->completionFunctionPostComplete = completionFunctionPostComplete_;
|
||||
return pCA;
|
||||
}
|
||||
|
||||
void enkiDeleteCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionAction* pCompletionAction_ )
|
||||
{
|
||||
CustomAllocator customAllocator = pETS_->GetConfig().customAllocator;
|
||||
|
||||
pCompletionAction_->~enkiCompletionAction();
|
||||
customAllocator.free( pCompletionAction_, sizeof(enkiCompletionAction), customAllocator.userData, ENKI_FILE_AND_LINE );
|
||||
}
|
||||
|
||||
enkiParamsCompletionAction enkiGetParamsCompletionAction( enkiCompletionAction* pCompletionAction_ )
|
||||
{
|
||||
enkiParamsCompletionAction params;
|
||||
params.pArgsPreComplete = pCompletionAction_->pArgsPreComplete;
|
||||
params.pArgsPostComplete = pCompletionAction_->pArgsPostComplete;
|
||||
params.pDependency = reinterpret_cast<const enkiCompletable*>(
|
||||
pCompletionAction_->dependency.GetDependencyTask() );
|
||||
return params;
|
||||
}
|
||||
|
||||
void enkiSetParamsCompletionAction( enkiCompletionAction* pCompletionAction_, enkiParamsCompletionAction params_ )
|
||||
{
|
||||
pCompletionAction_->pArgsPreComplete = params_.pArgsPreComplete;
|
||||
pCompletionAction_->pArgsPostComplete = params_.pArgsPostComplete;
|
||||
pCompletionAction_->SetDependency( pCompletionAction_->dependency, params_.pDependency );
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
// Copyright (c) 2013 Doug Binks
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(_WIN32) && defined(ENKITS_BUILD_DLL)
|
||||
// Building enkiTS as a DLL
|
||||
#define ENKITS_API __declspec(dllexport)
|
||||
#elif defined(_WIN32) && defined(ENKITS_DLL)
|
||||
// Using enkiTS as a DLL
|
||||
#define ENKITS_API __declspec(dllimport)
|
||||
#elif defined(__GNUC__) && defined(ENKITS_BUILD_DLL)
|
||||
// Building enkiTS as a shared library
|
||||
#define ENKITS_API __attribute__((visibility("default")))
|
||||
#else
|
||||
#define ENKITS_API
|
||||
#endif
|
||||
|
||||
// Define ENKI_CUSTOM_ALLOC_FILE_AND_LINE (at project level) to get file and line report in custom allocators,
|
||||
// this is default in Debug - to turn off define ENKI_CUSTOM_ALLOC_NO_FILE_AND_LINE
|
||||
#ifndef ENKI_CUSTOM_ALLOC_FILE_AND_LINE
|
||||
#if defined(_DEBUG ) && !defined(ENKI_CUSTOM_ALLOC_NO_FILE_AND_LINE)
|
||||
#define ENKI_CUSTOM_ALLOC_FILE_AND_LINE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct enkiTaskScheduler enkiTaskScheduler;
|
||||
typedef struct enkiTaskSet enkiTaskSet;
|
||||
typedef struct enkiPinnedTask enkiPinnedTask;
|
||||
typedef struct enkiCompletable enkiCompletable;
|
||||
typedef struct enkiDependency enkiDependency;
|
||||
typedef struct enkiCompletionAction enkiCompletionAction;
|
||||
|
||||
static const uint32_t ENKI_NO_THREAD_NUM = 0xFFFFFFFF;
|
||||
|
||||
typedef void (* enkiTaskExecuteRange)( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ );
|
||||
typedef void (* enkiPinnedTaskExecute)( void* pArgs_ );
|
||||
typedef void (* enkiCompletionFunction)( void* pArgs_, uint32_t threadNum_ );
|
||||
|
||||
// TaskScheduler implements several callbacks intended for profilers
|
||||
typedef void (*enkiProfilerCallbackFunc)( uint32_t threadnum_ );
|
||||
struct enkiProfilerCallbacks
|
||||
{
|
||||
enkiProfilerCallbackFunc threadStart;
|
||||
enkiProfilerCallbackFunc threadStop;
|
||||
enkiProfilerCallbackFunc waitForNewTaskSuspendStart; // thread suspended waiting for new tasks
|
||||
enkiProfilerCallbackFunc waitForNewTaskSuspendStop; // thread unsuspended
|
||||
enkiProfilerCallbackFunc waitForTaskCompleteStart; // thread waiting for task completion
|
||||
enkiProfilerCallbackFunc waitForTaskCompleteStop; // thread stopped waiting
|
||||
enkiProfilerCallbackFunc waitForTaskCompleteSuspendStart; // thread suspended waiting task completion
|
||||
enkiProfilerCallbackFunc waitForTaskCompleteSuspendStop; // thread unsuspended
|
||||
};
|
||||
|
||||
// Custom allocator, set in enkiTaskSchedulerConfig. Also see ENKI_CUSTOM_ALLOC_FILE_AND_LINE for file_ and line_
|
||||
typedef void* (*enkiAllocFunc)( size_t align_, size_t size_, void* userData_, const char* file_, int line_ );
|
||||
typedef void (*enkiFreeFunc)( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ );
|
||||
ENKITS_API void* enkiDefaultAllocFunc( size_t align_, size_t size_, void* userData_, const char* file_, int line_ );
|
||||
ENKITS_API void enkiDefaultFreeFunc( void* ptr_, size_t size_, void* userData_, const char* file_, int line_ );
|
||||
struct enkiCustomAllocator
|
||||
{
|
||||
enkiAllocFunc alloc;
|
||||
enkiFreeFunc free;
|
||||
void* userData;
|
||||
};
|
||||
|
||||
struct enkiParamsTaskSet
|
||||
{
|
||||
void* pArgs;
|
||||
uint32_t setSize;
|
||||
uint32_t minRange;
|
||||
int priority;
|
||||
};
|
||||
|
||||
struct enkiParamsPinnedTask
|
||||
{
|
||||
void* pArgs;
|
||||
int priority;
|
||||
};
|
||||
|
||||
struct enkiParamsCompletionAction
|
||||
{
|
||||
void* pArgsPreComplete;
|
||||
void* pArgsPostComplete;
|
||||
const enkiCompletable* pDependency; // task which when complete triggers completion function
|
||||
};
|
||||
|
||||
// enkiTaskSchedulerConfig - configuration struct for advanced Initialize
|
||||
// Always use enkiGetTaskSchedulerConfig() to get defaults prior to altering and
|
||||
// initializing with enkiInitTaskSchedulerWithConfig().
|
||||
struct enkiTaskSchedulerConfig
|
||||
{
|
||||
// numTaskThreadsToCreate - Number of tasking threads the task scheduler will create. Must be > 0.
|
||||
// Defaults to GetNumHardwareThreads()-1 threads as thread which calls initialize is thread 0.
|
||||
uint32_t numTaskThreadsToCreate;
|
||||
|
||||
// numExternalTaskThreads - Advanced use. Number of external threads which need to use TaskScheduler API.
|
||||
// See TaskScheduler::RegisterExternalTaskThread() for usage.
|
||||
// Defaults to 0. The thread used to initialize the TaskScheduler can also use the TaskScheduler API.
|
||||
// Thus there are (numTaskThreadsToCreate + numExternalTaskThreads + 1) able to use the API, with this
|
||||
// defaulting to the number of hardware threads available to the system.
|
||||
uint32_t numExternalTaskThreads;
|
||||
|
||||
struct enkiProfilerCallbacks profilerCallbacks;
|
||||
|
||||
struct enkiCustomAllocator customAllocator;
|
||||
};
|
||||
|
||||
|
||||
/* ---------------------------- Task Scheduler ---------------------------- */
|
||||
// Create a new task scheduler
|
||||
ENKITS_API enkiTaskScheduler* enkiNewTaskScheduler( void );
|
||||
|
||||
// Create a new task scheduler using a custom allocator
|
||||
// This will use the custom allocator to allocate the task scheduler struct
|
||||
// and additionally will set the custom allocator in enkiTaskSchedulerConfig of the task scheduler
|
||||
ENKITS_API enkiTaskScheduler* enkiNewTaskSchedulerWithCustomAllocator( struct enkiCustomAllocator customAllocator_ );
|
||||
|
||||
// Get config. Can be called before enkiInitTaskSchedulerWithConfig to get the defaults
|
||||
ENKITS_API struct enkiTaskSchedulerConfig enkiGetTaskSchedulerConfig( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// DEPRECATED: use GetIsShutdownRequested() instead of GetIsRunning() in external code
|
||||
// while( enkiGetIsRunning(pETS) ) {} can be used in tasks which loop, to check if enkiTS has been shutdown.
|
||||
// If enkiGetIsRunning() returns false should then exit. Not required for finite tasks
|
||||
ENKITS_API int enkiGetIsRunning( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// while( !enkiGetIsShutdownRequested() ) {} can be used in tasks which loop, to check if enkiTS has been requested to shutdown.
|
||||
// If enkiGetIsShutdownRequested() returns true should then exit. Not required for finite tasks
|
||||
// Safe to use with enkiWaitforAllAndShutdown() where this will be set
|
||||
// Not safe to use with enkiWaitforAll(), use enkiGetIsWaitforAllCalled() instead.
|
||||
ENKITS_API int enkiGetIsShutdownRequested( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// while( !enkiGetIsWaitforAllCalled() ) {} can be used in tasks which loop, to check if enkiWaitforAll() has been called.
|
||||
// If enkiGetIsWaitforAllCalled() returns false should then exit. Not required for finite tasks
|
||||
// This is intended to be used with code which calls enkiWaitforAll().
|
||||
// This is also set when the task manager is shutting down, so no need to have an additional check for enkiGetIsShutdownRequested()
|
||||
ENKITS_API int enkiGetIsWaitforAllCalled( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// Initialize task scheduler - will create GetNumHardwareThreads()-1 threads, which is
|
||||
// sufficient to fill the system when including the main thread.
|
||||
// Initialize can be called multiple times - it will wait for completion
|
||||
// before re-initializing.
|
||||
ENKITS_API void enkiInitTaskScheduler( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// Initialize a task scheduler with numThreads_ (must be > 0)
|
||||
// will create numThreads_-1 threads, as thread 0 is
|
||||
// the thread on which the initialize was called.
|
||||
ENKITS_API void enkiInitTaskSchedulerNumThreads( enkiTaskScheduler* pETS_, uint32_t numThreads_ );
|
||||
|
||||
// Initialize a task scheduler with config, see enkiTaskSchedulerConfig for details
|
||||
ENKITS_API void enkiInitTaskSchedulerWithConfig( enkiTaskScheduler* pETS_, struct enkiTaskSchedulerConfig config_ );
|
||||
|
||||
// Waits for all task sets to complete and shutdown threads - not guaranteed to work unless we know we
|
||||
// are in a situation where tasks aren't being continuously added.
|
||||
// pETS_ can then be reused.
|
||||
// This function can be safely called even if enkiInit* has not been called.
|
||||
ENKITS_API void enkiWaitforAllAndShutdown( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// Delete a task scheduler.
|
||||
ENKITS_API void enkiDeleteTaskScheduler( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// Waits for all task sets to complete - not guaranteed to work unless we know we
|
||||
// are in a situation where tasks aren't being continuously added.
|
||||
ENKITS_API void enkiWaitForAll( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// Returns the number of threads created for running tasks + number of external threads
|
||||
// plus 1 to account for the thread used to initialize the task scheduler.
|
||||
// Equivalent to config values: numTaskThreadsToCreate + numExternalTaskThreads + 1.
|
||||
// It is guaranteed that enkiGetThreadNum() < enkiGetNumTaskThreads()
|
||||
ENKITS_API uint32_t enkiGetNumTaskThreads( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// Returns the current task threadNum.
|
||||
// Will return 0 for thread which initialized the task scheduler,
|
||||
// and ENKI_NO_THREAD_NUM for all other non-enkiTS threads which have not been registered ( see enkiRegisterExternalTaskThread() ),
|
||||
// and < enkiGetNumTaskThreads() for all registered and internal enkiTS threads.
|
||||
// It is guaranteed that enkiGetThreadNum() < enkiGetNumTaskThreads() unless it is ENKI_NO_THREAD_NUM
|
||||
ENKITS_API uint32_t enkiGetThreadNum( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// Call on a thread to register the thread to use the TaskScheduling API.
|
||||
// This is implicitly done for the thread which initializes the TaskScheduler
|
||||
// Intended for developers who have threads who need to call the TaskScheduler API
|
||||
// Returns true if successful, false if not.
|
||||
// Can only have numExternalTaskThreads registered at any one time, which must be set
|
||||
// at initialization time.
|
||||
ENKITS_API int enkiRegisterExternalTaskThread( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// As enkiRegisterExternalTaskThread() but explicitly requests a given thread number.
|
||||
// threadNumToRegister_ must be >= GetNumFirstExternalTaskThread()
|
||||
// and < ( GetNumFirstExternalTaskThread() + numExternalTaskThreads )
|
||||
ENKITS_API int enkiRegisterExternalTaskThreadNum( enkiTaskScheduler* pETS_, uint32_t threadNumToRegister_ );
|
||||
|
||||
// Call on a thread on which RegisterExternalTaskThread has been called to deregister that thread.
|
||||
ENKITS_API void enkiDeRegisterExternalTaskThread( enkiTaskScheduler* pETS_);
|
||||
|
||||
// Get the number of registered external task threads.
|
||||
ENKITS_API uint32_t enkiGetNumRegisteredExternalTaskThreads( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// Get the thread number of the first external task thread. This thread
|
||||
// is not guaranteed to be registered, but threads are registered in order
|
||||
// from GetNumFirstExternalTaskThread() up to ( GetNumFirstExternalTaskThread() + numExternalTaskThreads )
|
||||
// Note that if numExternalTaskThreads == 0 a for loop using this will be valid:
|
||||
// for( uint32_t externalThreadNum = GetNumFirstExternalTaskThread();
|
||||
// externalThreadNum < ( GetNumFirstExternalTaskThread() + numExternalTaskThreads
|
||||
// ++externalThreadNum ) { // do something with externalThreadNum }
|
||||
ENKITS_API uint32_t enkiGetNumFirstExternalTaskThread( void );
|
||||
|
||||
|
||||
/* ---------------------------- TaskSets ---------------------------- */
|
||||
// Create a task set.
|
||||
ENKITS_API enkiTaskSet* enkiCreateTaskSet( enkiTaskScheduler* pETS_, enkiTaskExecuteRange taskFunc_ );
|
||||
|
||||
// Delete a task set.
|
||||
ENKITS_API void enkiDeleteTaskSet( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ );
|
||||
|
||||
// Get task parameters via enkiParamsTaskSet
|
||||
ENKITS_API struct enkiParamsTaskSet enkiGetParamsTaskSet( enkiTaskSet* pTaskSet_ );
|
||||
|
||||
// Set task parameters via enkiParamsTaskSet
|
||||
ENKITS_API void enkiSetParamsTaskSet( enkiTaskSet* pTaskSet_, struct enkiParamsTaskSet params_ );
|
||||
|
||||
// Set task priority ( 0 to ENKITS_TASK_PRIORITIES_NUM-1, where 0 is highest)
|
||||
ENKITS_API void enkiSetPriorityTaskSet( enkiTaskSet* pTaskSet_, int priority_ );
|
||||
|
||||
// Set TaskSet args
|
||||
ENKITS_API void enkiSetArgsTaskSet( enkiTaskSet* pTaskSet_, void* pArgs_ );
|
||||
|
||||
// Set TaskSet set setSize
|
||||
ENKITS_API void enkiSetSetSizeTaskSet( enkiTaskSet* pTaskSet_, uint32_t setSize_ );
|
||||
|
||||
// Set TaskSet set min range
|
||||
ENKITS_API void enkiSetMinRangeTaskSet( enkiTaskSet* pTaskSet_, uint32_t minRange_ );
|
||||
|
||||
// Schedule the task, use parameters set with enkiSet*TaskSet
|
||||
ENKITS_API void enkiAddTaskSet( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ );
|
||||
|
||||
// Schedule the task
|
||||
// overwrites args previously set with enkiSetArgsTaskSet
|
||||
// overwrites setSize previously set with enkiSetSetSizeTaskSet
|
||||
ENKITS_API void enkiAddTaskSetArgs( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_,
|
||||
void* pArgs_, uint32_t setSize_ );
|
||||
|
||||
// Schedule the task with a minimum range.
|
||||
// This should be set to a value which results in computation effort of at least 10k
|
||||
// clock cycles to minimize task scheduler overhead.
|
||||
// NOTE: The last partition will be smaller than m_MinRange if m_SetSize is not a multiple
|
||||
// of m_MinRange.
|
||||
// Also known as grain size in literature.
|
||||
ENKITS_API void enkiAddTaskSetMinRange( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_,
|
||||
void* pArgs_, uint32_t setSize_, uint32_t minRange_ );
|
||||
// Check if TaskSet is complete. Doesn't wait. Returns 1 if complete, 0 if not.
|
||||
ENKITS_API int enkiIsTaskSetComplete( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ );
|
||||
|
||||
// Wait for a given task.
|
||||
// should only be called from thread which created the task scheduler, or within a task
|
||||
// if called with 0 it will try to run tasks, and return if none available.
|
||||
// Only wait for child tasks of the current task otherwise a deadlock could occur.
|
||||
ENKITS_API void enkiWaitForTaskSet( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_ );
|
||||
|
||||
// enkiWaitForTaskSetPriority as enkiWaitForTaskSet but only runs other tasks with priority <= maxPriority_
|
||||
// Only wait for child tasks of the current task otherwise a deadlock could occur.
|
||||
ENKITS_API void enkiWaitForTaskSetPriority( enkiTaskScheduler* pETS_, enkiTaskSet* pTaskSet_, int maxPriority_ );
|
||||
|
||||
|
||||
/* ---------------------------- PinnedTasks ---------------------------- */
|
||||
// Create a pinned task.
|
||||
ENKITS_API enkiPinnedTask* enkiCreatePinnedTask( enkiTaskScheduler* pETS_, enkiPinnedTaskExecute taskFunc_, uint32_t threadNum_ );
|
||||
|
||||
// Delete a pinned task.
|
||||
ENKITS_API void enkiDeletePinnedTask( enkiTaskScheduler* pETS_, enkiPinnedTask* pPinnedTask_ );
|
||||
|
||||
// Get task parameters via enkiParamsTaskSet
|
||||
ENKITS_API struct enkiParamsPinnedTask enkiGetParamsPinnedTask( enkiPinnedTask* pTask_ );
|
||||
|
||||
// Set task parameters via enkiParamsTaskSet
|
||||
ENKITS_API void enkiSetParamsPinnedTask( enkiPinnedTask* pTask_, struct enkiParamsPinnedTask params_ );
|
||||
|
||||
// Set PinnedTask ( 0 to ENKITS_TASK_PRIORITIES_NUM-1, where 0 is highest)
|
||||
ENKITS_API void enkiSetPriorityPinnedTask( enkiPinnedTask* pTask_, int priority_ );
|
||||
|
||||
// Set PinnedTask args
|
||||
ENKITS_API void enkiSetArgsPinnedTask( enkiPinnedTask* pTask_, void* pArgs_ );
|
||||
|
||||
// Schedule a pinned task
|
||||
// Pinned tasks can be added from any thread
|
||||
ENKITS_API void enkiAddPinnedTask( enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_ );
|
||||
|
||||
// Schedule a pinned task
|
||||
// Pinned tasks can be added from any thread
|
||||
// overwrites args previously set with enkiSetArgsPinnedTask
|
||||
ENKITS_API void enkiAddPinnedTaskArgs( enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_,
|
||||
void* pArgs_ );
|
||||
|
||||
// This function will run any enkiPinnedTask* for current thread, but not run other
|
||||
// Main thread should call this or use a wait to ensure its tasks are run.
|
||||
ENKITS_API void enkiRunPinnedTasks( enkiTaskScheduler * pETS_ );
|
||||
|
||||
// Check if enkiPinnedTask is complete. Doesn't wait. Returns 1 if complete, 0 if not.
|
||||
ENKITS_API int enkiIsPinnedTaskComplete( enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_ );
|
||||
|
||||
// Wait for a given pinned task.
|
||||
// should only be called from thread which created the task scheduler, or within a task
|
||||
// if called with 0 it will try to run tasks, and return if none available.
|
||||
// Only wait for child tasks of the current task otherwise a deadlock could occur.
|
||||
ENKITS_API void enkiWaitForPinnedTask( enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_ );
|
||||
|
||||
// enkiWaitForPinnedTaskPriority as enkiWaitForPinnedTask but only runs other tasks with priority <= maxPriority_
|
||||
// Only wait for child tasks of the current task otherwise a deadlock could occur.
|
||||
ENKITS_API void enkiWaitForPinnedTaskPriority( enkiTaskScheduler* pETS_, enkiPinnedTask* pTask_, int maxPriority_ );
|
||||
|
||||
// Waits for the current thread to receive a PinnedTask
|
||||
// Will not run any tasks - use with RunPinnedTasks()
|
||||
// Can be used with both ExternalTaskThreads or with an enkiTS tasking thread to create
|
||||
// a thread which only runs pinned tasks. If enkiTS threads are used can create
|
||||
// extra enkiTS task threads to handle non blocking computation via normal tasks.
|
||||
ENKITS_API void enkiWaitForNewPinnedTasks( enkiTaskScheduler* pETS_ );
|
||||
|
||||
|
||||
/* ---------------------------- Completables ---------------------------- */
|
||||
// Get a pointer to an enkiCompletable from an enkiTaskSet.
|
||||
// Do not call enkiDeleteCompletable on the returned pointer.
|
||||
ENKITS_API enkiCompletable* enkiGetCompletableFromTaskSet( enkiTaskSet* pTaskSet_ );
|
||||
|
||||
// Get a pointer to an enkiCompletable from an enkiPinnedTask.
|
||||
// Do not call enkiDeleteCompletable on the returned pointer.
|
||||
ENKITS_API enkiCompletable* enkiGetCompletableFromPinnedTask( enkiPinnedTask* pPinnedTask_ );
|
||||
|
||||
// Get a pointer to an enkiCompletable from an enkiPinnedTask.
|
||||
// Do not call enkiDeleteCompletable on the returned pointer.
|
||||
ENKITS_API enkiCompletable* enkiGetCompletableFromCompletionAction( enkiCompletionAction* pCompletionAction_ );
|
||||
|
||||
// Create an enkiCompletable
|
||||
// Can be used with dependencies to wait for their completion.
|
||||
// Delete with enkiDeleteCompletable
|
||||
ENKITS_API enkiCompletable* enkiCreateCompletable( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// Delete an enkiCompletable created with enkiCreateCompletable
|
||||
ENKITS_API void enkiDeleteCompletable( enkiTaskScheduler* pETS_, enkiCompletable* pCompletable_ );
|
||||
|
||||
// Wait for a given completable.
|
||||
// should only be called from thread which created the task scheduler, or within a task
|
||||
// if called with 0 it will try to run tasks, and return if none available.
|
||||
// Only wait for child tasks of the current task otherwise a deadlock could occur.
|
||||
ENKITS_API void enkiWaitForCompletable( enkiTaskScheduler* pETS_, enkiCompletable* pTask_ );
|
||||
|
||||
// enkiWaitForCompletablePriority as enkiWaitForCompletable but only runs other tasks with priority <= maxPriority_
|
||||
// Only wait for child tasks of the current task otherwise a deadlock could occur.
|
||||
ENKITS_API void enkiWaitForCompletablePriority( enkiTaskScheduler* pETS_, enkiCompletable* pTask_, int maxPriority_ );
|
||||
|
||||
|
||||
/* ---------------------------- Dependencies ---------------------------- */
|
||||
// Create an enkiDependency, used to set dependencies between tasks
|
||||
// Call enkiDeleteDependency to delete.
|
||||
ENKITS_API enkiDependency* enkiCreateDependency( enkiTaskScheduler* pETS_ );
|
||||
|
||||
// Delete an enkiDependency created with enkiCreateDependency.
|
||||
ENKITS_API void enkiDeleteDependency( enkiTaskScheduler* pETS_, enkiDependency* pDependency_ );
|
||||
|
||||
// Set a dependency between pDependencyTask_ and pTaskToRunOnCompletion_
|
||||
// such that when all dependencies of pTaskToRunOnCompletion_ are completed it will run.
|
||||
ENKITS_API void enkiSetDependency(
|
||||
enkiDependency* pDependency_,
|
||||
enkiCompletable* pDependencyTask_,
|
||||
enkiCompletable* pTaskToRunOnCompletion_ );
|
||||
|
||||
/* -------------------------- Completion Actions --------------------------- */
|
||||
// Create a CompletionAction.
|
||||
// completionFunctionPreComplete_ - function called BEFORE the complete action task is 'complete', which means this is prior to dependent tasks being run.
|
||||
// this function can thus alter any task arguments of the dependencies.
|
||||
// completionFunctionPostComplete_ - function called AFTER the complete action task is 'complete'. Dependent tasks may have already been started.
|
||||
// This function can delete the completion action if needed as it will no longer be accessed by other functions.
|
||||
// It is safe to set either of these to NULL if you do not require that function
|
||||
ENKITS_API enkiCompletionAction* enkiCreateCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionFunction completionFunctionPreComplete_, enkiCompletionFunction completionFunctionPostComplete_ );
|
||||
|
||||
// Delete a CompletionAction.
|
||||
ENKITS_API void enkiDeleteCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionAction* pCompletionAction_ );
|
||||
|
||||
// Get task parameters via enkiParamsTaskSet
|
||||
ENKITS_API struct enkiParamsCompletionAction enkiGetParamsCompletionAction( enkiCompletionAction* pCompletionAction_ );
|
||||
|
||||
// Set task parameters via enkiParamsTaskSet
|
||||
ENKITS_API void enkiSetParamsCompletionAction( enkiCompletionAction* pCompletionAction_, struct enkiParamsCompletionAction params_ );
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
cc_library(
|
||||
name = "flecs",
|
||||
visibility = ["//visibility:public"],
|
||||
defines = ["flecs_EXPORTS"],
|
||||
|
||||
srcs = glob(["src/**/*.c", "src/**/*.h", "src/**/*.inl"]),
|
||||
hdrs = glob(["include/**/*.h", "include/**/*.hpp", "include/**/*.inl"]),
|
||||
includes = ["include"],
|
||||
|
||||
copts = select({
|
||||
"@bazel_tools//src/conditions:windows": ["/std:c++17"],
|
||||
"//conditions:default": ["-std=c++17"],
|
||||
}),
|
||||
)
|
||||
@@ -0,0 +1,104 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
||||
project(flecs LANGUAGES C)
|
||||
|
||||
option(FLECS_STATIC "Build static flecs lib" ON)
|
||||
option(FLECS_SHARED "Build shared flecs lib" ON)
|
||||
option(FLECS_PIC "Compile static flecs lib with position independent code (PIC)" ON)
|
||||
option(FLECS_TESTS "Build flecs tests" OFF)
|
||||
|
||||
include(cmake/target_default_compile_warnings.cmake)
|
||||
include(cmake/target_default_compile_options.cmake)
|
||||
|
||||
# Automatically generate the same folder structure in Visual Studio as we have on disk
|
||||
macro(GroupSources curdir)
|
||||
file(GLOB children RELATIVE ${PROJECT_SOURCE_DIR}/${curdir} ${PROJECT_SOURCE_DIR}/${curdir}/*)
|
||||
foreach(child ${children})
|
||||
if(IS_DIRECTORY ${PROJECT_SOURCE_DIR}/${curdir}/${child})
|
||||
GroupSources(${curdir}/${child})
|
||||
else()
|
||||
string(REPLACE "/" "\\" groupname ${curdir})
|
||||
source_group(${groupname} FILES ${PROJECT_SOURCE_DIR}/${curdir}/${child})
|
||||
endif()
|
||||
endforeach()
|
||||
endmacro()
|
||||
|
||||
file(GLOB children RELATIVE ${PROJECT_SOURCE_DIR}/. ${PROJECT_SOURCE_DIR}/./*)
|
||||
foreach(child ${children})
|
||||
if(IS_DIRECTORY ${PROJECT_SOURCE_DIR}/${curdir}/${child})
|
||||
GroupSources(${child})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
file(GLOB_RECURSE INC include/*.h include/*.hpp)
|
||||
file(GLOB_RECURSE SRC src/*.c)
|
||||
|
||||
set(FLECS_TARGETS "")
|
||||
|
||||
macro(add_flecs_target TARGET CONFIG)
|
||||
add_library(${TARGET} ${CONFIG} ${INC} ${SRC})
|
||||
add_library(flecs::${TARGET} ALIAS ${TARGET})
|
||||
|
||||
target_default_compile_options_c(${TARGET})
|
||||
target_default_compile_warnings_c(${TARGET})
|
||||
if(WIN32)
|
||||
target_link_libraries(${TARGET} wsock32 ws2_32 dbghelp)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Android")
|
||||
target_include_directories(${TARGET} PRIVATE
|
||||
"${CMAKE_ANDROID_NDK}/toolchains/llvm/prebuilt/windows-x86_64/include/")
|
||||
message(STATUS "building Android project...")
|
||||
endif()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
target_link_libraries(${TARGET} pthread)
|
||||
endif()
|
||||
|
||||
if(FLECS_PIC)
|
||||
set_property(TARGET ${TARGET} PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
target_include_directories(${TARGET} PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
|
||||
list(APPEND FLECS_TARGETS ${TARGET})
|
||||
endmacro()
|
||||
|
||||
if(FLECS_SHARED)
|
||||
add_flecs_target(flecs SHARED)
|
||||
endif()
|
||||
|
||||
if(FLECS_STATIC)
|
||||
add_flecs_target(flecs_static STATIC)
|
||||
target_compile_definitions(flecs_static PUBLIC flecs_STATIC)
|
||||
endif()
|
||||
|
||||
if(FLECS_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
message(STATUS "Targets: ${FLECS_TARGETS}")
|
||||
|
||||
# define the install steps
|
||||
include(GNUInstallDirs)
|
||||
install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/"
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
FILES_MATCHING
|
||||
PATTERN "*.h"
|
||||
PATTERN "*.hpp"
|
||||
PATTERN "*.inl")
|
||||
|
||||
install(TARGETS ${FLECS_TARGETS}
|
||||
EXPORT flecs-export
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
install(EXPORT flecs-export
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/flecs
|
||||
NAMESPACE flecs::
|
||||
FILE flecs-config.cmake)
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"version": 5,
|
||||
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 10,
|
||||
"patch": 0
|
||||
},
|
||||
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "Android",
|
||||
"displayName": "Android",
|
||||
"description": "Build android",
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "${sourceDir}/build/${presetName}",
|
||||
"cacheVariables": {
|
||||
"_comment": "download the ndk and change the cache variables as your circumstances ",
|
||||
|
||||
"CMAKE_TOOLCHAIN_FILE": "C:/Program Files/AndroidDev/android-ndk-r27c/build/cmake/android.toolchain.cmake",
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"ANDROID_PLATFORM": "31",
|
||||
"ANDROID_ABI": "arm64-v8a",
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
|
||||
|
||||
"FLECS_STATIC": "OFF",
|
||||
"FLECS_SHARED": "ON",
|
||||
"FLECS_PIC": "ON",
|
||||
"FLECS_TESTS": "OFF"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "Android",
|
||||
"configurePreset": "Android"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Sander Mertens
|
||||
|
||||
Portions Copyright (c) Meta Platforms, Inc. and affiliates
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,53 @@
|
||||
"A fast entity component system (ECS) for C & C++"
|
||||
module(name = "flecs")
|
||||
|
||||
git_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
|
||||
|
||||
git_repository(
|
||||
name = "bake",
|
||||
remote = "https://github.com/SanderMertens/bake.git",
|
||||
commit = "10c02123d6e3e200578128d73c3fa3bb996dff4f",
|
||||
build_file_content = """
|
||||
cc_library(
|
||||
name = "driver-test",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":util", ":bake"],
|
||||
defines = ["__BAKE__", "bake_test_EXPORTS"],
|
||||
|
||||
srcs = glob(["drivers/test/src/**/*.c"]),
|
||||
hdrs = glob(["drivers/test/include/**/*.h"]),
|
||||
includes = ["drivers/test/include"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "bake",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":util"],
|
||||
|
||||
srcs = glob(["src/*.c", "src/*.h"]),
|
||||
hdrs = glob(["include/*.h", "include/bake/*.h"]),
|
||||
includes = ["include"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "util",
|
||||
visibility = ["//visibility:public"],
|
||||
defines = ["__BAKE__", "_XOPEN_SOURCE=600", "bake_util_EXPORTS", "UT_IMPL"],
|
||||
|
||||
linkopts = select({
|
||||
"@bazel_tools//src/conditions:windows": ["Dbghelp.lib", "Shell32.lib", "Shlwapi.lib"],
|
||||
"//conditions:default": ["-lrt -lpthread -ldl"],
|
||||
}),
|
||||
|
||||
srcs = glob(["util/src/*.c"]) + select({
|
||||
"@bazel_tools//src/conditions:windows": glob(["util/src/win/*.c"]),
|
||||
"//conditions:default": glob(["util/src/posix/*.c"]),
|
||||
}),
|
||||
hdrs = glob(["util/include/*.h", "util/include/bake-util/*.h"]) + select({
|
||||
"@bazel_tools//src/conditions:windows": glob(["util/include/bake-util/win/*.h"]),
|
||||
"//conditions:default": glob(["util/include/bake-util/posix/*.h"]),
|
||||
}),
|
||||
includes = ["util/include"],
|
||||
)
|
||||
""",
|
||||
)
|
||||
@@ -0,0 +1,184 @@
|
||||

|
||||
|
||||
[](https://github.com/SanderMertens/flecs/releases)
|
||||
[](https://github.com/SanderMertens/flecs/blob/master/LICENSE)
|
||||
[](https://www.flecs.dev/flecs/md_docs_2Docs.html)
|
||||
[](https://github.com/SanderMertens/flecs/actions?query=workflow%3ACI)
|
||||
[](https://discord.gg/BEzP5Rgrrp)
|
||||
|
||||
Flecs is a fast and lightweight Entity Component System that lets you build games and simulations with millions of entities ([join the Discord!](https://discord.gg/BEzP5Rgrrp)). Here are some of the framework's highlights:
|
||||
|
||||
- Fast and [portable](#language-bindings) zero dependency [C99 API](https://www.flecs.dev/flecs/group__c.html)
|
||||
- Modern type-safe [C++17 API](https://www.flecs.dev/flecs/group__cpp.html) that doesn't use STL containers
|
||||
- First open source ECS with full support for [Entity Relationships](https://www.flecs.dev/flecs/md_docs_2Relationships.html)!
|
||||
- Fast native support for [hierarchies](https://www.flecs.dev/flecs/md_docs_2HierarchiesManual.html) and [prefabs](https://www.flecs.dev/flecs/md_docs_2PrefabsManual.html)
|
||||
- Code base that builds in less than 5 seconds
|
||||
- Runs [in the browser](https://flecs.dev/city) without modifications with emscripten
|
||||
- Cache friendly [archetype/SoA storage](https://ajmmertens.medium.com/building-an-ecs-2-archetypes-and-vectorization-fe21690805f9) that can process millions of entities every frame
|
||||
- Automatic component registration that works out of the box across shared libraries/DLLs
|
||||
- Write free functions with [queries](https://github.com/SanderMertens/flecs/tree/master/examples/cpp/queries/basics) or run code automatically in [systems](https://github.com/SanderMertens/flecs/tree/master/examples/cpp/systems/pipeline)
|
||||
- Run games on multiple CPU cores with a fast lockless scheduler
|
||||
- Verified on all major compilers and platforms with [CI](https://github.com/SanderMertens/flecs/actions) running more than 13000 tests
|
||||
- Integrated [reflection framework](https://www.flecs.dev/flecs/group__c__addons__meta.html) with [JSON serializer](https://github.com/SanderMertens/flecs/tree/master/examples/cpp/reflection/basics_json) and support for [runtime components](https://github.com/SanderMertens/flecs/tree/master/examples/cpp/reflection/runtime_component)
|
||||
- [Unit annotations](https://github.com/SanderMertens/flecs/tree/master/examples/cpp/reflection/units) for components
|
||||
- Powerful [query language](https://github.com/SanderMertens/flecs/tree/master/examples/cpp/queries) with support for [joins](https://github.com/SanderMertens/flecs/tree/master/examples/cpp/queries/setting_variables) and [inheritance](https://github.com/SanderMertens/flecs/tree/master/examples/cpp/queries/component_inheritance)
|
||||
- [Statistics addon](https://www.flecs.dev/flecs/group__c__addons__stats.html) for profiling ECS performance
|
||||
- A web-based UI for monitoring & controlling your apps:
|
||||
|
||||
[](https://flecs.dev/explorer)
|
||||
|
||||
To support the project, give it a star 🌟 !
|
||||
|
||||
## What is an Entity Component System?
|
||||
ECS is a way of organizing code and data that lets you build games that are larger, more complex and are easier to extend. Something is called an ECS when it:
|
||||
- Has _entities_ that uniquely identify objects in a game
|
||||
- Has _components_ which are datatypes that can be added to entities
|
||||
- Has _systems_ which are functions that run for all entities matching a component _query_
|
||||
|
||||
For more information, check the [ECS FAQ](https://github.com/SanderMertens/ecs-faq)!
|
||||
|
||||
## Show me the code!
|
||||
C99 example:
|
||||
```c
|
||||
typedef struct {
|
||||
float x, y;
|
||||
} Position, Velocity;
|
||||
|
||||
void Move(ecs_iter_t *it) {
|
||||
Position *p = ecs_field(it, Position, 0);
|
||||
Velocity *v = ecs_field(it, Velocity, 1);
|
||||
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
p[i].x += v[i].x;
|
||||
p[i].y += v[i].y;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
ecs_world_t *ecs = ecs_init();
|
||||
|
||||
ECS_COMPONENT(ecs, Position);
|
||||
ECS_COMPONENT(ecs, Velocity);
|
||||
|
||||
ECS_SYSTEM(ecs, Move, EcsOnUpdate, Position, Velocity);
|
||||
|
||||
ecs_entity_t e = ecs_insert(ecs,
|
||||
ecs_value(Position, {10, 20}),
|
||||
ecs_value(Velocity, {1, 2}));
|
||||
|
||||
while (ecs_progress(ecs, 0)) { }
|
||||
}
|
||||
```
|
||||
|
||||
Same example in C++:
|
||||
|
||||
```cpp
|
||||
struct Position {
|
||||
float x, y;
|
||||
};
|
||||
|
||||
struct Velocity {
|
||||
float x, y;
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
flecs::world ecs;
|
||||
|
||||
ecs.system<Position, const Velocity>()
|
||||
.each([](Position& p, const Velocity& v) {
|
||||
p.x += v.x;
|
||||
p.y += v.y;
|
||||
});
|
||||
|
||||
auto e = ecs.entity()
|
||||
.insert([](Position& p, Velocity& v) {
|
||||
p = {10, 20};
|
||||
v = {1, 2};
|
||||
});
|
||||
|
||||
while (ecs.progress()) { }
|
||||
}
|
||||
```
|
||||
|
||||
## Projects using Flecs
|
||||
If you have a project you'd like to share, let me know on [Discord](https://discord.gg/BEzP5Rgrrp)!
|
||||
|
||||
### Tempest Rising
|
||||
[](https://store.steampowered.com/app/1486920/Tempest_Rising/)
|
||||
|
||||
### Territory Control 2
|
||||
[](https://store.steampowered.com/app/690290/Territory_Control_2/)
|
||||
|
||||
### Resistance is Brutal
|
||||
[](https://store.steampowered.com/app/3378140/Resistance_Is_Brutal/)
|
||||
|
||||
### Appulse
|
||||
[](https://store.steampowered.com/app/3498700/Appulse/)
|
||||
|
||||
### Rescue Ops: Wildfire
|
||||
[](https://store.steampowered.com/app/2915770/Rescue_Ops_Wildfire/)
|
||||
|
||||
### Age of Respair
|
||||
[](https://store.steampowered.com/app/3164360/Age_of_Respair/)
|
||||
|
||||
### FEAST
|
||||
[](https://store.steampowered.com/app/3823480/FEAST/)
|
||||
|
||||
### Gloam Vault
|
||||
[](https://store.steampowered.com/app/3460840/Gloamvault/)
|
||||
|
||||
### Antimatcher
|
||||
[](https://store.steampowered.com/app/4336520/AntiMatcher/)
|
||||
|
||||
### Writ of Battle
|
||||
[](https://store.steampowered.com/app/4445990/Writ_of_Battle/)
|
||||
|
||||
### Extermination Shock
|
||||
[](https://store.steampowered.com/app/2510820/Extermination_Shock/)
|
||||
|
||||
### The Forge
|
||||
[](https://github.com/ConfettiFX/The-Forge)
|
||||
|
||||
### ECS survivors
|
||||
[](https://laurent-voisard.itch.io/ecs-survivors/)
|
||||
|
||||
### Tome Tumble Tournament
|
||||
[](https://terzalo.itch.io/tome-tumble-tournament)
|
||||
|
||||
### Sol Survivor
|
||||
[](https://nicok.itch.io/sol-survivor-demo)
|
||||
|
||||
### After Sun
|
||||
[](https://github.com/foxnne/aftersun)
|
||||
|
||||
## Flecs Hub
|
||||
[Flecs Hub](https://github.com/flecs-hub) is a collection of repositories that show how Flecs can be used to build game systems like input handling, hierarchical transforms and rendering.
|
||||
|
||||
Module | Description
|
||||
------------|------------------
|
||||
[flecs.components.cglm](https://github.com/flecs-hub/flecs-components-cglm) | Component registration for cglm (math) types
|
||||
[flecs.components.input](https://github.com/flecs-hub/flecs-components-input) | Components that describe keyboard and mouse input
|
||||
[flecs.components.transform](https://github.com/flecs-hub/flecs-components-transform) | Components that describe position, rotation and scale
|
||||
[flecs.components.physics](https://github.com/flecs-hub/flecs-components-physics) | Components that describe physics and movement
|
||||
[flecs.components.geometry](https://github.com/flecs-hub/flecs-components-geometry) | Components that describe geometry
|
||||
[flecs.components.graphics](https://github.com/flecs-hub/flecs-components-graphics) | Components used for computer graphics
|
||||
[flecs.components.gui](https://github.com/flecs-hub/flecs-components-gui) | Components used to describe GUI components
|
||||
[flecs.systems.transform](https://github.com/flecs-hub/flecs-systems-transform) | Hierarchical transforms for scene graphs
|
||||
[flecs.systems.physics](https://github.com/flecs-hub/flecs-systems-physics) | Systems for moving objects and collision detection
|
||||
[flecs.systems.sokol](https://github.com/flecs-hub/flecs-systems-sokol) | Sokol-based renderer
|
||||
[flecs.game](https://github.com/flecs-hub/flecs-game) | Generic game systems, like a camera controller
|
||||
|
||||
## Language bindings
|
||||
The following language bindings have been developed with Flecs! Note that these are projects built and maintained by helpful community members, and may not always be up to date with the latest commit from master!
|
||||
- C#:
|
||||
- [BeanCheeseBurrito/Flecs.NET](https://github.com/BeanCheeseBurrito/Flecs.NET)
|
||||
- Rust:
|
||||
- [Flecs-Rust](https://github.com/Indra-db/Flecs-Rust)
|
||||
- [flecs-polyglot](https://github.com/flecs-hub/flecs-polyglot)
|
||||
- Zig:
|
||||
- [zig-gamedev/zflecs](https://github.com/zig-gamedev/zflecs)
|
||||
- Lua:
|
||||
- [sro5h/flecs-luajit](https://github.com/sro5h/flecs-luajit)
|
||||
- [flecs-hub/flecs-lua](https://github.com/flecs-hub/flecs-lua)
|
||||
- Clojure
|
||||
- [vybe-flecs](https://vybegame.dev/vybe-flecs)
|
||||
@@ -0,0 +1,51 @@
|
||||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
|
||||
|
||||
new_git_repository(
|
||||
name = "bake",
|
||||
remote = "[email protected]:SanderMertens/bake.git",
|
||||
commit = "cfc90745f9daa7b7fba80f229af18cdd5029b066",
|
||||
shallow_since = "1614835160 -0800",
|
||||
|
||||
build_file_content = """
|
||||
cc_library(
|
||||
name = "driver-test",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":util", ":bake"],
|
||||
|
||||
srcs = glob(["drivers/test/src/**/*.c"]),
|
||||
hdrs = glob(["drivers/test/include/**/*.h"]),
|
||||
includes = ["drivers/test/include"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "bake",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":util"],
|
||||
|
||||
srcs = glob(["src/*.c", "src/*.h"]),
|
||||
hdrs = glob(["include/*.h", "include/bake/*.h"]),
|
||||
includes = ["include"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "util",
|
||||
visibility = ["//visibility:public"],
|
||||
defines = ["__BAKE__", "_XOPEN_SOURCE=600"],
|
||||
|
||||
linkopts = select({
|
||||
"@bazel_tools//src/conditions:windows": [],
|
||||
"//conditions:default": ["-lrt -lpthread -ldl"],
|
||||
}),
|
||||
|
||||
srcs = glob(["util/src/*.c"]) + select({
|
||||
"@bazel_tools//src/conditions:windows": glob(["util/src/win/*.c"]),
|
||||
"//conditions:default": glob(["util/src/posix/*.c"]),
|
||||
}),
|
||||
hdrs = glob(["util/include/*.h", "util/include/bake-util/*.h"]) + select({
|
||||
"@bazel_tools//src/conditions:windows": glob(["util/include/bake-util/win/*.h"]),
|
||||
"//conditions:default": glob(["util/include/bake-util/posix/*.h"]),
|
||||
}),
|
||||
includes = ["util/include"],
|
||||
)
|
||||
"""
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
# Overrides WORKSPACE when building with bzlmod
|
||||
@@ -0,0 +1,21 @@
|
||||
files.extend-exclude = ["distr/*", "test/script/src/Fuzzing.c"]
|
||||
|
||||
# typos can't handle "2nd" as a word (yet?)
|
||||
default.extend-ignore-identifiers-re = ["_2nd"]
|
||||
|
||||
[default.extend-words]
|
||||
ser = "ser" # serialization
|
||||
thr = "thr" # thread
|
||||
|
||||
[default.extend-identifiers]
|
||||
ba = "ba" # block_allocator
|
||||
ecs_eis = "ecs_eis" # entity index store (entity_index.h)
|
||||
numer = "numer" # numerator (posix_impl.inl)
|
||||
ot = "ot" # Opaque Type or Other Table
|
||||
Fo = "Fo" # used in test cases/examples
|
||||
UE4 = "UE4"
|
||||
PNGs = "PNGs"
|
||||
Collet = "Collet"
|
||||
ECS_BIT_CLEARN = "ECS_BIT_CLEARN"
|
||||
arg_cpy = "arg_cpy"
|
||||
this_data_cpy = "this_data_cpy"
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
macro(list_targets RESULT)
|
||||
file(GLOB_RECURSE FILES LIST_DIRECTORIES true RELATIVE ${CUR_DIR} *)
|
||||
set(TARGET_LIST "")
|
||||
|
||||
foreach (FILE ${FILES})
|
||||
if (IS_DIRECTORY ${CUR_DIR}/${FILE})
|
||||
if (EXISTS ${CUR_DIR}/${FILE}/project.json)
|
||||
message(STATUS "Found example ${FILE}")
|
||||
list(APPEND TARGET_LIST ${FILE})
|
||||
endif()
|
||||
endif ()
|
||||
endforeach ()
|
||||
|
||||
set(${RESULT} ${TARGET_LIST})
|
||||
endmacro()
|
||||
|
||||
macro(create_target TARGET CONFIG)
|
||||
if (CONFIG STREQUAL "")
|
||||
set(TARGET_POSTFIX "")
|
||||
else()
|
||||
set(TARGET_POSTFIX "_${CONFIG}")
|
||||
endif()
|
||||
|
||||
set(TARGET_NAME "${TARGET}${TARGET_POSTFIX}")
|
||||
string(REPLACE "/" "_" TARGET_NAME "${TARGET_NAME}")
|
||||
string(REPLACE "\\" "_" TARGET_NAME "${TARGET_NAME}")
|
||||
set(TARGET_DIR "${CUR_DIR}/${TARGET}")
|
||||
|
||||
file(GLOB SRC ${TARGET_DIR}/src/*.c*)
|
||||
add_executable(${TARGET_NAME} ${SRC})
|
||||
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${FLECS_DIR}/include)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${TARGET_DIR}/include)
|
||||
target_link_libraries(${TARGET_NAME} flecs${TARGET_POSTFIX})
|
||||
endmacro()
|
||||
|
||||
function(create_target_c TARGET CONFIG)
|
||||
create_target("${TARGET}" "${CONFIG}")
|
||||
target_default_compile_options_c(${TARGET_NAME})
|
||||
target_default_compile_warnings_c(${TARGET_NAME})
|
||||
endfunction()
|
||||
|
||||
function(create_target_cxx TARGET CONFIG)
|
||||
create_target("${TARGET}" "${CONFIG}")
|
||||
target_default_compile_options_cxx(${TARGET_NAME})
|
||||
target_default_compile_warnings_cxx(${TARGET_NAME})
|
||||
endfunction()
|
||||
@@ -0,0 +1,28 @@
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
option(FLECS_STRICT "Stricter warning options and Werror" OFF)
|
||||
|
||||
function(target_default_compile_options_c THIS)
|
||||
|
||||
set_target_properties(${THIS} PROPERTIES
|
||||
LINKER_LANGUAGE C
|
||||
C_STANDARD 99
|
||||
C_STANDARD_REQUIRED ON
|
||||
C_VISIBILITY_PRESET hidden
|
||||
POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
endfunction()
|
||||
|
||||
function(target_default_compile_options_cxx THIS)
|
||||
|
||||
set_target_properties(${THIS} PROPERTIES
|
||||
LINKER_LANGUAGE CXX
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
endfunction()
|
||||
@@ -0,0 +1,132 @@
|
||||
function(target_default_compile_warnings_c THIS)
|
||||
|
||||
if (FLECS_STRICT)
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
# Less strict warnings for emscripten builds
|
||||
target_compile_options(${THIS} PRIVATE
|
||||
-Wall -Wextra
|
||||
-Wno-shadow
|
||||
-Wno-unused-parameter
|
||||
-Wno-missing-field-initializers
|
||||
-Wno-strict-prototypes)
|
||||
|
||||
elseif (CMAKE_C_COMPILER_ID STREQUAL "Clang"
|
||||
OR CMAKE_C_COMPILER_ID STREQUAL "AppleClang")
|
||||
|
||||
target_compile_options(${THIS} PRIVATE
|
||||
$<$<CONFIG:Debug>:-Wshadow>
|
||||
$<$<CONFIG:Debug>:-Wunused>
|
||||
-Wall -Wextra -Werror
|
||||
-Wcast-align
|
||||
-Wpedantic
|
||||
-Wconversion
|
||||
-Wsign-conversion
|
||||
-Wdouble-promotion
|
||||
-Wno-missing-prototypes
|
||||
-Wno-missing-variable-declarations
|
||||
-Wno-unknown-warning-option
|
||||
-Wno-padded
|
||||
-Wno-c++-compat
|
||||
-Wno-c++-keyword
|
||||
-Wno-implicit-void-ptr-cast
|
||||
-Wno-implicit-int-enum-cast
|
||||
-Wno-jump-misses-init
|
||||
-Wno-nrvo
|
||||
-Wno-shadow-header)
|
||||
|
||||
elseif (CMAKE_C_COMPILER_ID STREQUAL "GNU")
|
||||
|
||||
target_compile_options(${THIS} PRIVATE
|
||||
$<$<CONFIG:Debug>:-Wshadow>
|
||||
$<$<CONFIG:Debug>:-Wunused>
|
||||
-Wall -Wextra -Werror
|
||||
-Wcast-align
|
||||
-Wpedantic
|
||||
-Wconversion
|
||||
-Wsign-conversion
|
||||
-Wdouble-promotion
|
||||
-Wno-missing-prototypes
|
||||
-Wno-missing-variable-declarations)
|
||||
|
||||
elseif (CMAKE_C_COMPILER_ID STREQUAL "MSVC")
|
||||
|
||||
target_compile_options(${THIS} PRIVATE
|
||||
/W3 /WX
|
||||
/w14242 /w14254 /w14263
|
||||
/w14265 /w14287 /we4289
|
||||
/w14296 /w14311 /w14545
|
||||
/w14546 /w14547 /w14549
|
||||
/w14555 /w14619 /w14640
|
||||
/w14826 /w14905 /w14906
|
||||
/w14928)
|
||||
|
||||
else ()
|
||||
|
||||
message(WARNING
|
||||
"No warning settings available for ${CMAKE_C_COMPILER_ID}. ")
|
||||
|
||||
endif ()
|
||||
|
||||
endif ()
|
||||
|
||||
endfunction()
|
||||
|
||||
function(target_default_compile_warnings_cxx THIS)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
|
||||
OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||
|
||||
target_compile_options(${THIS} PRIVATE
|
||||
#$<$<CONFIG:RELEASE>:-Werror>
|
||||
$<$<CONFIG:Debug>:-Wshadow>
|
||||
$<$<CONFIG:Debug>:-Wunused>
|
||||
-Wall -Wextra
|
||||
-Wnon-virtual-dtor
|
||||
-Wold-style-cast
|
||||
-Wcast-align
|
||||
-Woverloaded-virtual
|
||||
-Wpedantic
|
||||
-Wconversion
|
||||
-Wsign-conversion
|
||||
-Wdouble-promotion
|
||||
-Wno-padded)
|
||||
|
||||
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
|
||||
target_compile_options(${THIS} PRIVATE
|
||||
#$<$<CONFIG:RELEASE>:-Werror>
|
||||
$<$<CONFIG:Debug>:-Wshadow>
|
||||
$<$<CONFIG:Debug>:-Wunused>
|
||||
-Wall -Wextra
|
||||
-Wnon-virtual-dtor
|
||||
-Wold-style-cast
|
||||
-Wcast-align
|
||||
-Woverloaded-virtual
|
||||
-Wpedantic
|
||||
-Wconversion
|
||||
-Wsign-conversion
|
||||
-Wdouble-promotion)
|
||||
|
||||
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
|
||||
target_compile_options(${THIS} PRIVATE
|
||||
#$<$<CONFIG:RELEASE>:/WX>
|
||||
/W3
|
||||
/w14242 /w14254 /w14263
|
||||
/w14265 /w14287 /we4289
|
||||
/w14296 /w14311 /w14545
|
||||
/w14546 /w14547 /w14549
|
||||
/w14555 /w14619 /w14640
|
||||
/w14826 /w14905 /w14906
|
||||
/w14928)
|
||||
|
||||
else ()
|
||||
|
||||
message(WARNING
|
||||
"No Warnings specified for ${CMAKE_CXX_COMPILER_ID}. "
|
||||
"Consider using one of the following compilers: Clang, GNU, MSVC, AppleClang.")
|
||||
|
||||
endif ()
|
||||
|
||||
endfunction()
|
||||
@@ -0,0 +1,21 @@
|
||||
codecov:
|
||||
notify:
|
||||
require_ci_to_pass: no
|
||||
|
||||
coverage:
|
||||
precision: 2
|
||||
round: down
|
||||
range: "70...100"
|
||||
|
||||
status:
|
||||
project: yes
|
||||
patch: yes
|
||||
changes: no
|
||||
|
||||
parsers:
|
||||
gcov:
|
||||
branch_detection:
|
||||
conditional: yes
|
||||
loop: yes
|
||||
method: no
|
||||
macro: no
|
||||
@@ -0,0 +1,467 @@
|
||||
# Building Flecs
|
||||
This manual describes how to compile the Flecs source code.
|
||||
|
||||
## Dependencies
|
||||
The following libraries need to be linked when building Flecs:
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">Windows</b>
|
||||
|
||||
| Library | Used by | Used for |
|
||||
|---------------|------------|----------------------|
|
||||
| dbghelp | Core | Logging stack traces |
|
||||
| ws2_32 | HTTP addon | Allows [explorer](https://www.flecs.dev/explorer/) to connect |
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">Linux</b>
|
||||
|
||||
| Library | Used by | Used for |
|
||||
|---------------|--------------|----------------------|
|
||||
| rt | Core | Time functions |
|
||||
| pthread | OS_API addon | Threading functions |
|
||||
| m | Core | Math functions |
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">MacOS</b>
|
||||
|
||||
No dependencies required on MacOS.
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
## Build with distr files
|
||||
The easiest way to add Flecs to a project is to add the [distr/flecs.c](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs.c) and [distr/flecs.h](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs.h) files to the project source. When building, make sure your build system is setup to do the following:
|
||||
|
||||
- If it is a C++ project, make sure to compile [distr/flecs.c](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs.c) as C code, for example by using `gcc`/`clang` instead of `g++`/`clang++`.
|
||||
|
||||
- If you are building on Windows and you're not using the Microsoft Visual Studio compiler, make sure to add `-lws2_32 -ldbghelp` to the end of the linker command.
|
||||
|
||||
- When compiling Flecs with `gcc`/`clang`, add `-std=gnu99` to the compiler command. This ensures that addons that rely on time & socket functions are compiled correctly.
|
||||
|
||||
- C++ files that use Flecs must be compiled with `-std=c++17` or higher.
|
||||
|
||||
### Command line examples
|
||||
These examples are just for illustration. For most projects it is recommended to use a build system like cmake, bazel or bake.
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">Windows</b>
|
||||
|
||||
**Clang, C**
|
||||
```bash
|
||||
clang my_project.c -c -o my_project.o
|
||||
clang flecs.c -c -o flecs.o
|
||||
clang my_project.o flecs.o -ldbghelp -lws2_32 -o my_project
|
||||
```
|
||||
|
||||
**Clang, C++**
|
||||
```bash
|
||||
clang++ my_project.cpp -std=c++17 -c -o my_project.o
|
||||
clang flecs.c -c -o flecs.o
|
||||
clang++ my_project.o flecs.o -ldbghelp -lws2_32 -o my_project
|
||||
```
|
||||
|
||||
**MSVC, C**
|
||||
```bash
|
||||
cl /nologo /c my_project.c /Fo:my_project.obj
|
||||
cl /nologo /c flecs.c /Fo:flecs.obj
|
||||
cl /nologo my_project.obj flecs.obj dbghelp.lib ws2_32.lib /Fe:my_project.exe
|
||||
```
|
||||
|
||||
**MSVC, C++**
|
||||
```bash
|
||||
cl /nologo /EHsc /c my_project.cpp /Fo:my_project.obj
|
||||
cl /nologo /TC /c flecs.c /Fo:flecs.obj
|
||||
cl /nologo my_project.obj flecs.obj dbghelp.lib ws2_32.lib /Fe:my_project.exe
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">Linux</b>
|
||||
|
||||
**GCC, C**
|
||||
```bash
|
||||
gcc my_project.c -std=gnu99 -c -o my_project.o
|
||||
gcc flecs.c -std=gnu99 -c -o flecs.o
|
||||
gcc my_project.o flecs.o -lrt -lpthread -lm -o my_project
|
||||
```
|
||||
|
||||
**GCC, C++**
|
||||
```bash
|
||||
g++ my_project.cpp -std=c++17 -c -o my_project.o
|
||||
gcc flecs.c -std=gnu99 -c -o flecs.o
|
||||
g++ my_project.o flecs.o -lrt -lpthread -lm -o my_project
|
||||
```
|
||||
|
||||
**Clang, C**
|
||||
```bash
|
||||
clang my_project.c -std=gnu99 -c -o my_project.o
|
||||
clang flecs.c -std=gnu99 -c -o flecs.o
|
||||
clang my_project.o flecs.o -lrt -lpthread -lm -o my_project
|
||||
```
|
||||
|
||||
**Clang, C++**
|
||||
```bash
|
||||
clang++ my_project.cpp -std=c++17 -c -o my_project.o
|
||||
clang flecs.c -std=gnu99 -c -o flecs.o
|
||||
clang++ my_project.o flecs.o -lrt -lpthread -lm -o my_project
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">Macos</b>
|
||||
|
||||
**Clang, C**
|
||||
```bash
|
||||
clang my_project.c -std=gnu99 -c -o my_project.o
|
||||
clang flecs.c -std=gnu99 -c -o flecs.o
|
||||
clang my_project.o flecs.o -o my_project
|
||||
```
|
||||
|
||||
**Clang, C++**
|
||||
```bash
|
||||
clang++ my_project.cpp -std=c++17 -c -o my_project.o
|
||||
clang flecs.c -std=gnu99 -c -o flecs.o
|
||||
clang++ my_project.o flecs.o -o my_project
|
||||
```
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
### Customizing distr files
|
||||
The distr folder contains two sets of files: one that contains the entire source code ([distr/flecs.c](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs.c), [distr/flecs.h](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs.h)) and one that strips all addons ([distr/flecs_no_addons.c](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs_no_addons.c), [distr/flecs_no_addons.h](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs_no_addons.h)).
|
||||
|
||||
It is possible to customize which compile-time flags get disabled when generating a distr build. In order to do this, add an `amalgamate` configuration to [project.json](project.json), and rerun bake on the repository. The following is an example of a configuration that generates a configuration without the C++ addon:
|
||||
|
||||
```json
|
||||
// Add to "amalgamate": []
|
||||
{
|
||||
"path": "distr",
|
||||
"prefix": "flecs_no_cpp",
|
||||
"disable-flags": [
|
||||
"FLECS_CPP"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Build as shared library
|
||||
To build the [distr/flecs.c](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs.c) and [distr/flecs.h](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs.h) files as a shared library, remove this line from the top of `flecs.h`:
|
||||
|
||||
```c
|
||||
#define flecs_STATIC
|
||||
```
|
||||
|
||||
When compiling [distr/flecs.c](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs.c), make sure to define `flecs_EXPORTS`, for example by adding `-Dflecs_EXPORTS` to the compiler command. This ensures that Flecs symbols are exported.
|
||||
|
||||
The following example shows how to create a shared library using flecs.c and flecs.h with clang on Linux:
|
||||
|
||||
```bash
|
||||
clang flecs.c -c -Dflecs_EXPORTS -o flecs.o
|
||||
clang flecs.o -lrt -lpthread -lm -shared -o libflecs.so
|
||||
```
|
||||
|
||||
## Updating the distr files
|
||||
If you've made a modification to the Flecs source code (in the `src` or `include` folders) and you want to update the flecs.c and flecs.h files in the `distr` folder, run bake once in the root of the repository:
|
||||
|
||||
```
|
||||
bake
|
||||
```
|
||||
|
||||
Make sure that the files in `distr` now have your modifications. If they have not been updated, it's possible that the date of the files is newer than your modifications. If this happens, remove the `distr` folder and run `bake` again. This will regenerate files with the latest modifications.
|
||||
|
||||
It is not recommended to modify the flecs.c and flecs.h directly.
|
||||
|
||||
## Build from source
|
||||
This will build the files in `src` and `include` with one of the following build systems:
|
||||
|
||||
### Cmake
|
||||
Run this command in the repository root to create flecs library binaries:
|
||||
|
||||
```bash
|
||||
cmake -S . -B cmake_build && cmake --build cmake_build
|
||||
```
|
||||
|
||||
After the command finishes, the `cmake_build` folder will contain one static and one dynamic library binary.
|
||||
|
||||
You can also add the Flecs repository as dependency for another cmake project by adding the following line to the `CMakeLists.txt` of the project:
|
||||
|
||||
```cmake
|
||||
add_subdirectory(external/flecs)
|
||||
```
|
||||
|
||||
Alternatively, you can use `FetchContent` to automatically download Flecs:
|
||||
|
||||
```cmake
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
flecs
|
||||
GIT_REPOSITORY https://github.com/SanderMertens/flecs.git
|
||||
GIT_TAG master
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(flecs)
|
||||
```
|
||||
|
||||
Then to link with flecs, add the following to `CMakeLists.txt`:
|
||||
|
||||
```cmake
|
||||
target_link_libraries(${PROJECT_NAME} flecs::flecs_static)
|
||||
```
|
||||
|
||||
### Meson
|
||||
To build Flecs with meson, run the following commands in the repository root:
|
||||
|
||||
```bash
|
||||
meson meson_build
|
||||
cd meson_build
|
||||
meson compile
|
||||
```
|
||||
|
||||
### Bazel
|
||||
To build Flecs with bazel, run the following command in the repository root:
|
||||
|
||||
```bash
|
||||
bazel build //:flecs
|
||||
```
|
||||
|
||||
### Bake
|
||||
To build Flecs with bake, run the following command in the repository root:
|
||||
|
||||
```bash
|
||||
bake
|
||||
```
|
||||
|
||||
After this Flecs should show up in the bake environment when doing:
|
||||
|
||||
```bash
|
||||
bake list
|
||||
```
|
||||
|
||||
To create a new Flecs C project, do:
|
||||
|
||||
```bash
|
||||
bake new my_project -t flecs
|
||||
```
|
||||
|
||||
To create a new Flecs C++ project, do:
|
||||
```bash
|
||||
bake new my_project -t flecs --language c++
|
||||
```
|
||||
|
||||
To run the project, do:
|
||||
|
||||
```bash
|
||||
cd my_project
|
||||
bake run
|
||||
```
|
||||
|
||||
## Building the documentation
|
||||
To generate the HTML version of the documentation, run the following command:
|
||||
|
||||
```
|
||||
doxygen docs/cfg/Doxyfile
|
||||
```
|
||||
|
||||
The generated HTML files will be written to docs/html.
|
||||
|
||||
## Running the test suite
|
||||
Flecs uses the test framework that ships with bake. While it is possible to run tests with other build systems, it is recommended to use bake.
|
||||
|
||||
### Bake
|
||||
Make sure to at least build flecs once, by running this in the repository root:
|
||||
|
||||
```bash
|
||||
bake
|
||||
```
|
||||
|
||||
Make sure that Flecs is listed in the bake environment by doing:
|
||||
|
||||
```bash
|
||||
bake list
|
||||
```
|
||||
|
||||
Then run the command for the test project you want to run:
|
||||
|
||||
```bash
|
||||
bake run test/core
|
||||
bake run test/addons
|
||||
bake run test/query
|
||||
bake run test/meta
|
||||
bake run test/script
|
||||
bake run test/cpp
|
||||
bake run test/collections
|
||||
```
|
||||
|
||||
To run a single test suite, do:
|
||||
|
||||
```bash
|
||||
bake run test/core -- Sparse
|
||||
```
|
||||
|
||||
To run a single test case, do:
|
||||
|
||||
```bash
|
||||
bake run test/core -- Sparse.get
|
||||
```
|
||||
|
||||
Some test suites are parameterized. To provide a value for a parameter, do:
|
||||
|
||||
```bash
|
||||
bake run test/core - Sparse --param fragment=no
|
||||
```
|
||||
|
||||
You can specify the number of threads with `-j` (default is 4):
|
||||
|
||||
```bash
|
||||
bake run test/core -- -j 10
|
||||
```
|
||||
|
||||
To add a new test case, first add it to the `project.json` of the project you want to add it to, for example:
|
||||
|
||||
```json
|
||||
"testcases": [
|
||||
// existing test cases...
|
||||
"my_new_testcase"
|
||||
]
|
||||
```
|
||||
|
||||
Then build the test project once:
|
||||
|
||||
```bash
|
||||
bake test/core
|
||||
```
|
||||
|
||||
The suite to which you added the test case will now have an empty test function:
|
||||
|
||||
```c
|
||||
void Sparse_my_test_case(void) {
|
||||
// Implement testcase
|
||||
}
|
||||
```
|
||||
|
||||
When running the test suite now you will see:
|
||||
|
||||
```
|
||||
EMPTY Sparse.my_test_case (add test statements)
|
||||
To run/debug your test, do:
|
||||
export $(bake env)
|
||||
test/core/bin/arm64-Darwin-debug/core Sparse.my_test_case --param fragment=yes
|
||||
```
|
||||
|
||||
This indicates that the test case isn't testing anything yet. Add one or more `test_assert` (or equivalent) calls to the code to make this message go away.
|
||||
|
||||
### Cmake (experimental)
|
||||
First make sure to clone [bake](https://github.com/SanderMertens/bake).
|
||||
|
||||
Run the following commands to run all the tests:
|
||||
|
||||
```bash
|
||||
# Generate make files for Flecs and tests
|
||||
cmake -DFLECS_TESTS=ON -DBAKE_DIRECTORY="path to cloned bake repository"
|
||||
|
||||
# Build flecs and test suites
|
||||
cmake --build . -j 4
|
||||
|
||||
# Run the tests
|
||||
ctest -C Debug --verbose
|
||||
```
|
||||
|
||||
### Bazel (experimental)
|
||||
Run the following commands to run all the tests:
|
||||
|
||||
```bash
|
||||
bazel test --test_output=all //test/...
|
||||
```
|
||||
|
||||
## Building on Emscripten
|
||||
When building for emscripten, add the following command line options to the `emcc` link command:
|
||||
```bash
|
||||
-s ALLOW_MEMORY_GROWTH=1
|
||||
-s STACK_SIZE=1mb
|
||||
-s EXPORTED_RUNTIME_METHODS=cwrap
|
||||
-s MODULARIZE=1
|
||||
-s EXPORT_NAME="my_app"
|
||||
```
|
||||
|
||||
## Building on Android
|
||||
When building for Android, you should:
|
||||
|
||||
- download NDK
|
||||
- check the cmake preset file and update the `cacheVariables` as you like
|
||||
- run a command like below:
|
||||
|
||||
```cmd
|
||||
cmake -S. -Bbuild_android -GNinja --preset=Android
|
||||
```
|
||||
|
||||
Then run command `ninja` in `build_android` directory to start build.
|
||||
|
||||
## Addons
|
||||
Flecs has a modular architecture that makes it easy to only build the features you really need. By default all addons are built. To customize a build, first define `FLECS_CUSTOM_BUILD`, then add defines for the addons you need. For example:
|
||||
|
||||
```c
|
||||
#define FLECS_CUSTOM_BUILD // Don't build all addons
|
||||
#define FLECS_SYSTEM // Build FLECS_SYSTEM
|
||||
```
|
||||
|
||||
Additionally, you can also specify addons to exclude from a build by adding `NO` to the define:
|
||||
|
||||
```c
|
||||
#define FLECS_NO_LOG
|
||||
```
|
||||
|
||||
The following addons can be configured:
|
||||
|
||||
Addon | Description | Define |
|
||||
--------------|--------------------------------------------------|---------------------|
|
||||
[Cpp](/flecs/group__cpp.html) | C++17 API | FLECS_CPP |
|
||||
[Module](/flecs/group__c__addons__module.html) | Organize game logic into reusable modules | FLECS_MODULE |
|
||||
[System](/flecs/group__c__addons__system.html) | Create & run systems | FLECS_SYSTEM |
|
||||
[Pipeline](/flecs/group__c__addons__pipeline.html) | Automatically schedule & multithread systems | FLECS_PIPELINE |
|
||||
[Timer](/flecs/group__c__addons__timer.html) | Run systems at time intervals or at a rate | FLECS_TIMER |
|
||||
[Meta](/flecs/group__c__addons__meta.html) | Flecs reflection system | FLECS_META |
|
||||
[Units](/flecs/group__c__addons__units.html) | Builtin unit types | FLECS_UNITS |
|
||||
[JSON](/flecs/group__c__addons__json.html) | JSON format | FLECS_JSON |
|
||||
[Doc](/flecs/group__c__addons__doc.html) | Add documentation to components, systems & more | FLECS_DOC |
|
||||
[Http](/flecs/group__c__addons__http.html) | Tiny HTTP server for processing simple requests | FLECS_HTTP |
|
||||
[Rest](/flecs/group__c__addons__rest.html) | REST API for showing entities in the browser | FLECS_REST |
|
||||
Parser | Parser utilities used by script & query DSL | FLECS_PARSER |
|
||||
[Query DSL](https://www.flecs.dev/flecs/md_docs_2FlecsQueryLanguage.html) | Query DSL parser | FLECS_QUERY_DSL |
|
||||
[Script](/flecs/group__c__addons__script.html) | DSL for scenes, assets and configuration | FLECS_SCRIPT |
|
||||
[Stats](/flecs/group__c__addons__stats.html) | Functions for collecting statistics | FLECS_STATS |
|
||||
[Metrics](/flecs/group__c__addons__metrics.html) | Create metrics from user-defined components | FLECS_METRICS |
|
||||
[Alerts](/flecs/group__c__addons__alerts.html) | Create alerts from user-defined queries | FLECS_ALERTS |
|
||||
[Log](/flecs/group__c__addons__log.html) | Extended tracing and error logging | FLECS_LOG |
|
||||
[Journal](/flecs/group__c__addons__journal.html) | Journaling of API functions | FLECS_JOURNAL |
|
||||
[App](/flecs/group__c__addons__app.html) | Flecs application framework | FLECS_APP |
|
||||
[OS API Impl](/flecs/group__c__addons__os__api__impl.html) | Default OS API implementation for Posix/Win32 | FLECS_OS_API_IMPL |
|
||||
|
||||
The following example shows how to build Flecs without any addons using bake. Run the command from the repository root:
|
||||
|
||||
```
|
||||
bake -D FLECS_CUSTOM_BUILD
|
||||
```
|
||||
|
||||
To only build Flecs with the system addon, do:
|
||||
|
||||
```
|
||||
bake -D FLECS_CUSTOM_BUILD -D FLECS_SYSTEM
|
||||
```
|
||||
|
||||
The following example shows how to build Flecs without any addons using clang:
|
||||
|
||||
```
|
||||
clang my_project.c -std=gnu99 -c -o my_project.o
|
||||
clang flecs.c -DFLECS_CUSTOM_BUILD -std=gnu99 -c -o flecs.o
|
||||
clang my_project.o flecs.o -lrt -lpthread -lm -o my_project
|
||||
```
|
||||
|
||||
To only build Flecs with clang with the system addon, do:
|
||||
|
||||
```
|
||||
clang my_project.c -std=gnu99 -c -o my_project.o
|
||||
clang flecs.c -DFLECS_CUSTOM_BUILD -DFLECS_SYSTEM -std=gnu99 -c -o flecs.o
|
||||
clang my_project.o flecs.o -lrt -lpthread -lm -o my_project
|
||||
```
|
||||
@@ -0,0 +1,203 @@
|
||||
# Designing with Flecs
|
||||
Designing games with ECS can be overwhelming and quite different from object oriented approaches, not to mention learning all the framework specific features! This guide provides a few quick tips for using the features provided by Flecs to build fast, readable and reusable code.
|
||||
|
||||
Note that these are my own guidelines, and as a result this is an opinionated document. There are many other approaches to designing with ECS, and it would be silly to claim that this is "the one true way". Take this with a grain of salt, and feel free to deviate when you feel it is appropriate.
|
||||
|
||||
One other note: this document is very light on feature documentation. The point of the document is to provide suggestions for how to design with Flecs features, not document their ins and outs. All of the features listed in this document are described in the manual and have example code.
|
||||
|
||||
## Entities
|
||||
Entities are probably the easiest thing to get used to. They typically map to in-game objects the same way you would create them in other game engines.
|
||||
|
||||
### Entity Initialization
|
||||
When creating entities, you typically want to initialize them with a set of default components, and maybe even default values. Flecs introduced prefabs for this use case. Prefabs let you create entity templates that contain components with default values. Creating entities from prefabs is not just an easy way to initialize entities, it is also faster, and helps with classifying the different kinds of entities you have.
|
||||
|
||||
### Entity Lifecycle
|
||||
Entities can be created and deleted dynamically. When entities are deleted, the existing handles to that entity are no longer valid. When you are working with entity handles, it is often good practice to make sure they are still alive. You can do this with the `is_alive()` function. Other than that, entity handles are stable, which means that you can safely store them in your components or elsewhere.
|
||||
|
||||
### Entity Names
|
||||
Flecs entities can be named. This makes it easy to identify entities in editors or while debugging, and also allows you to lookup entities by name. Names must be unique inside a scope, which is determined by the `ChildOf` relationship. For example, two entities with the same parent must have different names.
|
||||
|
||||
Names can be looked up using a relative path or absolute path using the `ecs_lookup` or `entity::lookup` functions. The default scope separator for C applications is a dot (`.`), whereas in C++ it is a double colon (`::`). Lookups use a hashmap internally, which provides O(1) performance.
|
||||
|
||||
Entities can be assigned user friendly names with the doc addon, using the `ecs_doc_set_name` or `entity::set_doc_name` functions. Names assigned by the doc framework do not have to be unique.
|
||||
|
||||
## Components
|
||||
Designing your components is probably the most important thing you will do in your ECS application. The reason is that if you change a component you have to update all systems that use it. Fortunately components can be designed in a way that minimizes refactoring and does not negatively impact performance.
|
||||
|
||||
### Component Size
|
||||
The first tip is to try keep your components small and atomic. If you are choosing between a Transform component or separate Position, Rotation, Scale, Matrix components pick the latter. If you have a Turret component with a target and a rotation angle, split them up into two components.
|
||||
|
||||
The first reason behind this is that querying for multiple components adds minimal overhead, because most queries are cached.
|
||||
|
||||
The second reason is that it improves caching performance. If your system only needs Position, but also has to load all of the other data in Transform, you end up loading a lot of data in your cache that is not used. This means that useful data will get evicted from the cache more often, and that data needs to be loaded from RAM more often, which is much slower than when data is cached.
|
||||
|
||||
The third reason is that it minimizes refactoring. There are only so many ways to split up components, and infinitely more ways to combine them. Once components are designed as atomic units of data there aren't many reasons to combine them which results in less refactoring.
|
||||
|
||||
The fourth and last reason is that code using smaller components is more reusable. Reasons to combine two components may work well in one project, but not in another project. Atomic components are less opinionated since it's up to the project how they are combined in queries, which makes it more likely that they work well across projects. This trickles down to systems, as systems written for atomic components also end up being more reusable.
|
||||
|
||||
A disadvantage of small components is that you get more of them in a project. This can make it harder to find the components a system needs, especially in large projects with hundreds of components. Tools like https://www.flecs.dev/explorer/ can help with finding and documenting components in a project.
|
||||
|
||||
### Complex component data
|
||||
There is a misconception that ECS components can only be plain data types, and should not have vectors, or more complex data structures. The reality is more nuanced. You may find yourself often needing specialized data structures, and it is perfectly fine to store these in components.
|
||||
|
||||
## Queries
|
||||
Queries are the primary method in Flecs for finding the entities for a set of components (or more specifically: a component expression). Queries are easy to use, but there a few things to keep in mind.
|
||||
|
||||
### Use the right query
|
||||
Flecs has cached queries and uncached queries. Cached queries are expensive to create but very cheap to iterate. Uncached queries are fast to create, but more expensive to iterate. If you need to do a quick ad-hoc query for which you couldn't know in advance what you had to query for, an uncached query is the best option. If you have a query that you know in advance and need to iterate many times, a cached query is preferred.
|
||||
|
||||
Another difference is that uncached queries can be created from systems, while cached queries cannot. If you need a cached query in a system, it has to be created in advance and passed into the system, either by setting it as system context, adding a component to the system with the query, or passing it in the lambda capture list (C++ only). Systems themselves use cached queries.
|
||||
|
||||
Make sure to not repeatedly create and destroy cached queries!
|
||||
|
||||
Here is an example of how to create uncached and cached queries:
|
||||
|
||||
```cpp
|
||||
// An uncached query:
|
||||
flecs::query<Position, Velocity> q = world.query<Position, Velocity>();
|
||||
|
||||
// A cached query:
|
||||
flecs::query<Position, Velocity> q = world.query_builder<Position, Velocity>()
|
||||
.cached()
|
||||
.build();
|
||||
|
||||
// Named queries are cached:
|
||||
flecs::query<Position, Velocity> q = world.query<Position, Velocity>("Move");
|
||||
|
||||
// Queries that use cascade(), group_by() or order_by() are cached:
|
||||
flecs::query<Position, Velocity> q = world.query_builder<Position, Velocity>()
|
||||
.group_by<WorldCell>()
|
||||
.build();
|
||||
|
||||
// System queries are automatically cached:
|
||||
world.system<Position, Velocity>("Move")
|
||||
.each([](Position& p, Velocity& v) {
|
||||
p += v;
|
||||
});
|
||||
```
|
||||
|
||||
For more information, see [the query manual](https://www.flecs.dev/flecs/md_docs_2Queries.html#performance-and-caching) for more details.
|
||||
|
||||
### Use in/inout/out annotations
|
||||
Flecs analyzes how components are read and written by queries and uses this for things like change tracking and deciding when to merge command buffers. By default components are marked as `inout`. If a system only reads a component, make sure to mark it as `in`, as this can reduce the time spent by other parts of the application that rely on change detection and sync points.
|
||||
|
||||
For more information, see [the query manual](https://www.flecs.dev/flecs/md_docs_2Queries.html#access-modifiers).
|
||||
|
||||
### Annotations
|
||||
You can further annotate queries with components that are not matched with the query, but that are written using ECS operations (like add, remove, set etc.). Such operations are automatically deferred and merged at the end of the frame. With annotations you can enforce merging at an earlier point, by specifying that a component modification has been queued. When Flecs sees this, it will merge back the modifications before the next read.
|
||||
|
||||
See the sync point examples for more detail:
|
||||
- C: https://github.com/SanderMertens/flecs/blob/master/examples/c/systems/sync_point
|
||||
- C++: https://github.com/SanderMertens/flecs/blob/master/examples/cpp/systems/sync_point
|
||||
|
||||
## Systems
|
||||
Designing systems is one of the hardest things to do when not coming from an ECS background. Object oriented code allows you to write logic that is local to a single object, whereas systems in ECS are ran for collections of similar objects. This requires a different approach towards design.
|
||||
|
||||
### System Scope
|
||||
Try to design your systems with a single responsibility. This can sometimes be difficult, especially if you are building new features and are not exactly sure yet what the end result will look like. That is fine. It is better to start with something that works, and refine it afterwards. If you find yourself with a system that does a lot of things, don't worry. Because systems in ECS are decoupled from everything else, it is generally pretty easy to split them up.
|
||||
|
||||
While it is perfectly fine to have large systems in an ECS application, there are a few advantages to keeping them small:
|
||||
|
||||
- Smaller systems make it easier to isolate behavior, as you can simply remove systems from your application that you don't want to test.
|
||||
- Smaller systems usually have less complicated code, which can allow for more compiler optimizations (like auto vectorization).
|
||||
- Smaller systems are easier to reuse across project.
|
||||
|
||||
### System Scheduling
|
||||
Flecs has the ability to run and schedule your systems for you. The advantage of this, versus manually listing and running systems, is that it is easier to import systems from multiple, reusable modules.
|
||||
|
||||
A system is basically a combination of three things: a query, a function, and ordering information. The query finds the right entities, and the function is invoked with the matched entities. The ordering information makes sure that the system is inserted in the right point in your frame. If you get this right, you can import any number of systems into your project, and you can just run them without spending any time on manually sorting them out.
|
||||
|
||||
The ordering information consists out of a phase (see phases and pipelines) and an implicit declaration order. Systems are ordered according to their phases first. Within a phase, they are ordered by declaration order. This may feel rigid, but is very deliberate. It prevents you from defining dependencies between systems, which can make it difficult to reuse systems across projects.
|
||||
|
||||
On the other hand, if you are working with an existing framework or engine, you may not have the luxury of scheduling everything yourself. The engine may for example provide you with callbacks in which you need to do certain logic. Maybe you want to build your own threading system. In those situations it can make sense to take control of running systems yourself.
|
||||
|
||||
Sometimes you may even not use systems at all, and just run queries. In this case you may want to disable the system addon (see the addons section in the README). Note that some Flecs features depend on systems, like the REST API and timers.
|
||||
|
||||
## Phases and Pipelines
|
||||
Phases and pipelines are the primitives that Flecs uses to order systems. A pipeline is a set of ordered phases. Systems can be assigned to those phases. When using phases and pipelines correctly, it allows you to write plug & play systems that are easy to reuse in different projects.
|
||||
|
||||
### Selecting a Phase
|
||||
When you create a system, you can assign a phase to it. By default, that phase is `OnUpdate`. Flecs comes with a whole bunch of phases though, and just looking at the whole list can feel a bit overwhelming:
|
||||
|
||||
- `OnLoad`
|
||||
- `PostLoad`
|
||||
- `PreUpdate`
|
||||
- `OnUpdate`
|
||||
- `OnValidate`
|
||||
- `PostUpdate`
|
||||
- `PreStore`
|
||||
- `OnStore`
|
||||
|
||||
So what do these all mean? Actually they mean nothing at all! They are just tags you can assign to systems, and those tags ensure that all systems in, say, the `PreUpdate` phase are executed _before_ the systems in the `OnUpdate` phase. What is also important to realize is that this list of phases is only the default provided by Flecs. Maybe your project needs only half of those, or maybe it needs entirely different ones! Flecs lets you specify your custom phases to match your project needs.
|
||||
|
||||
There are some conventions around the builtin phases, and following them helps to ensure that your code works well with the Flecs module ecosystem. Here they are:
|
||||
|
||||
### OnLoad
|
||||
This phase contains all the systems that load data into your ECS. This would be a good place to load keyboard and mouse inputs.
|
||||
|
||||
### PostLoad
|
||||
Often the imported data needs to be processed. Maybe you want to associate your key presses with high level actions rather than comparing explicitly in your game code if the user pressed the 'K' key. The `PostLoad` phase is a good place for this.
|
||||
|
||||
### PreUpdate
|
||||
Now that the input is loaded and processed, it's time to get ready to start processing our game logic. Anything that needs to happen after input processing but before processing the game logic can happen here. This can be a good place to prepare the frame, maybe clean up some things from the previous frame, etcetera.
|
||||
|
||||
### OnUpdate
|
||||
This is usually where the magic happens! This is where you put all of your game play systems. By default systems are added to this phase.
|
||||
|
||||
### OnValidate
|
||||
This phase was introduced to deal with validating the state of the game after processing the game play systems. Sometimes you moved entities too close to each other, or the speed of an entity is increased too much. This phase is for righting that wrong. A typical feature to implement in this phase would be collision detection.
|
||||
|
||||
### PostUpdate
|
||||
When your game logic has been updated, and your validation pass has ran, you may want to apply some corrections. For example, if your collision detection system detected collisions in the `OnValidate` phase, you may want to move the entities so that they no longer overlap.
|
||||
|
||||
### PreStore
|
||||
Now that all of the frame data is computed, validated and corrected for, it is time to prepare the frame for rendering. Any systems that need to run before rendering, but after processing the game logic should go here. A good example would be a system that calculates transform matrices from a scene graph.
|
||||
|
||||
### OnStore
|
||||
This is where it all comes together. Your frame is ready to be rendered, and that is exactly what you would do in this phase.
|
||||
|
||||
That was a quick overview of all the builtin phases. Note that these are just guidelines! Feel free to deviate if your project calls for it.
|
||||
|
||||
### Custom phases and pipelines
|
||||
An application can add phases to the existing list, or define a pipeline from scratch. See the following examples on how to do this:
|
||||
|
||||
- C:
|
||||
- https://github.com/SanderMertens/flecs/tree/master/examples/c/systems/custom_phases
|
||||
- https://github.com/SanderMertens/flecs/tree/master/examples/c/systems/custom_pipeline
|
||||
- C++:
|
||||
- https://github.com/SanderMertens/flecs/tree/master/examples/cpp/systems/custom_phases
|
||||
- https://github.com/SanderMertens/flecs/tree/master/examples/cpp/systems/custom_pipeline
|
||||
|
||||
## Modules
|
||||
Large applications can often contain many components and systems. Some large commercial projects have reported up to 800 components! Managing all those components and systems becomes important on that scale, and that is what modules are for. Modules are one of those features that you usually don't think about when selecting an ECS, but they can make your life a lot easier.
|
||||
|
||||
### Defining Modules
|
||||
The purpose of modules is really to enable reusability. A well written module can be imported into any project, and will do its thing without any tweaking or tinkering. To achieve this, make sure to define your modules around features. Features seldom consist out of a single system or component, but can have many. Examples are rendering, collision detection, input management, and so on.
|
||||
|
||||
### Module Dependencies and Ordering
|
||||
Modules can depend on each other. In fact, often do! Importing a module twice has no penalties in Flecs, it will not define your systems and components twice. This enables your application code to import the modules it needs, without having to worry about whether they were already loaded.
|
||||
|
||||
The order in which you import dependencies in a module is important. If you define systems before the import, those systems will be ran _before_ the systems from the imported module _within the same phase_ (read this line a few times until you understand, it's important). This allows you some degree of flexibility around how systems from different modules should be scheduled.
|
||||
|
||||
What is key to note here is that the granularity of control is at the module level, _never_ at the individual system level. The reason for this is that modules may reimplement their features with different systems. If you have inter-system dependencies, those could break easily every time you update a module. This also makes sure that you can replace one module for another without running into annoying compatibility issues.
|
||||
|
||||
### Modules and Feature Swapping
|
||||
A good practice to employ with modules is to split them up into components.* modules and systems.* modules. For example, you may have a module `components.physics` that contains all the components to store data for a physics system. You may then have a module called `systems.physics`, which imports the components module and "implements" the components. Because applications only have access to `components.physics` (they import it as well) but do not have direct access to the systems inside `systems.physics`, you can simply swap one physics implementation with another without changing application code.
|
||||
|
||||
This is a powerful pattern enabled by ECS that will give your projects a lot of flexibility and freedom in refactoring code.
|
||||
|
||||
### Module Overhead
|
||||
You might wonder whether a module with lots of systems, of which only a few are used by your application makes your application slower. The answer is fortunately no. Flecs only inserts systems into the main loop that have matched with actual entities. Any imported systems that have never matched with anything remain dormant, and will not negatively affect performance.
|
||||
|
||||
## Relationships
|
||||
When you are working with Flecs, chances are that at some point you'll want to use relationships. The two most common uses for relationships are hierarchies like a scene graph (the `ChildOf` relationship) and prefabs (the `IsA` relationship).
|
||||
|
||||
In many cases you might want to use your own relationships. Here are a few signs to look out for that can tell you to think about relationships:
|
||||
- You have a component with an entity handle, and you need to find all entities that point to a specific entity.
|
||||
- You have many components with the same or similar set of members, and systems that duplicate code for each component.
|
||||
- You need to store multiple instances of the same component on an entity.
|
||||
- You're designing some kind of container structure, like an inventory.
|
||||
- You are looking to group entities by something like world cells or layers, and want to be able to lookup all entities for a cell.
|
||||
- You're adding an enumeration type as component, but want to query for enumeration constants.
|
||||
|
||||
See the [relationships blog](https://ajmmertens.medium.com/building-games-in-ecs-with-entity-relationships-657275ba2c6c) and [relationships manual](https://www.flecs.dev/flecs/md_docs_2Relationships.html) for more information.
|
||||
@@ -0,0 +1,95 @@
|
||||
# Documentation
|
||||
|
||||
## Getting Started
|
||||
- [FAQ](FAQ.md)
|
||||
- [Quickstart](Quickstart.md)
|
||||
- [Designing with Flecs](DesignWithFlecs.md)
|
||||
- [Getting Started with Unreal Engine](https://github.com/PreyK/Unreal-Minimum-Viable-Flecs)
|
||||
- [Unreal-Flecs (Unreal Engine Flecs plugin)](https://github.com/Reddy-dev/Unreal-Flecs)
|
||||
- [Godot-flecs-sample (example)](https://github.com/paulfigiel/godot-flecs-sample)
|
||||
- [Flecs-Godot (GDExtension)](https://github.com/VoxInteractive/Godot-Flecs)
|
||||
- [Godot-turbo (engine module)](https://github.com/callmefloof/godot-turbo/) ([demo project](https://github.com/callmefloof/godot_turbo_demo_projects))
|
||||
- [Getting Started with Raylib](https://github.com/kranzky/raylib-flecs-starter-kit)
|
||||
- [Getting Started with SDL3, ImGui, Sqlite](https://github.com/ilyas-taouaou/CodotakuCMakeRepo) ([video](https://www.youtube.com/watch?v=T32B7nf6B-8))
|
||||
- [Getting Started with SDL3, Blend2D](https://github.com/james-j-obrien/flecs-sdl3-blend2d)
|
||||
- [Flecs an Raylib template](https://github.com/aog05/Raylib-and-Flecs-Template)
|
||||
|
||||
## Manuals
|
||||
- [Manual](Manual.md)
|
||||
- [Entities and Components Manual](EntitiesComponents.md)
|
||||
- [Query Manual](Queries.md)
|
||||
- [Query Language Manual](FlecsQueryLanguage.md)
|
||||
- [Systems Manual](Systems.md)
|
||||
- [Observers Manual](ObserversManual.md)
|
||||
- [Hierarchies Manual](HierarchiesManual.md)
|
||||
- [Prefabs Manual](PrefabsManual.md)
|
||||
- [Component Traits Manual](ComponentTraits.md)
|
||||
- [Relationships Manual](Relationships.md)
|
||||
- [Flecs Script Manual](FlecsScript.md)
|
||||
- [Flecs Remote API Manual](FlecsRemoteApi.md)
|
||||
- [v3 Migration Guide](MigrationGuide.md)
|
||||
|
||||
## API reference
|
||||
- [C API](@ref c)
|
||||
- [C++ API](@ref cpp)
|
||||
|
||||
## Examples
|
||||
- [C examples](https://github.com/SanderMertens/flecs/tree/master/examples/c)
|
||||
- [C++ examples](https://github.com/SanderMertens/flecs/tree/master/examples/cpp)
|
||||
- [C# examples](https://github.com/BeanCheeseBurrito/Flecs.NET/tree/main/src/Flecs.NET.Examples)
|
||||
- [Rust examples](https://github.com/Indra-db/Flecs-Rust/tree/main/flecs_ecs/examples/flecs)
|
||||
|
||||
## Videos
|
||||
- [Setting Up The Project (E1)](https://www.youtube.com/watch?v=O632xG7ItL8)
|
||||
- [Creating Your First World (E2)](https://www.youtube.com/watch?v=ph2FjgNvlO4)
|
||||
- [Components In Flecs (E3)](https://www.youtube.com/watch?v=4qtdgAN82uo)
|
||||
- [Tagging Your Creations (E4)](https://www.youtube.com/watch?v=u71cuiyQk-8)
|
||||
- [Beaty Of Pairs In Flecs (E5)](https://www.youtube.com/watch?v=fJuHMzVC_bE)
|
||||
- [What is a Prefab? (E6)](https://www.youtube.com/watch?v=qz5eblX8yoc)
|
||||
- [Some Pointers for C (E7)](https://www.youtube.com/watch?v=0LscDsVy-28)
|
||||
- [Demistifying Queries Part 1 (E8)](https://www.youtube.com/watch?v=PKvCpUO55yw&t=216s)
|
||||
- [Demistifying Queries Part 2 (E9)](https://www.youtube.com/watch?v=4KZGkT9MqHI)
|
||||
- [Flecs Systems (E10)](https://www.youtube.com/watch?v=gXzgO6unZkI)
|
||||
- [Observing Changes (E11)](https://www.youtube.com/watch?v=XaZOmWoSKfQ)
|
||||
- [One to Rule Them All (singletons, E12)](https://www.youtube.com/watch?v=IpuZIFcaUBU)
|
||||
|
||||
(Shoutout to [jellycake222](https://www.youtube.com/@jellycake222) for making the awesome tutorial series <3!)
|
||||
|
||||
## Open Source Projects
|
||||
- [Extermination Shock Source](https://github.com/Wizard-Of-Chaos/ExShock_code)
|
||||
- [ECS Survivors Source](https://github.com/ptidejteam/ecs-survivors)
|
||||
- [Playground (Flecs Script)](https://www.flecs.dev/explorer/?host=flecs_explorer.wasm)
|
||||
- [Tower Defense (C++)](https://github.com/SanderMertens/tower_defense)
|
||||
- [City (C)](https://github.com/flecs-hub/city)
|
||||
|
||||
## Articles
|
||||
- [Where are my entities and components](https://ajmmertens.medium.com/building-an-ecs-1-where-are-my-entities-and-components-63d07c7da742)
|
||||
- [Archetypes and vectorization](https://ajmmertens.medium.com/building-an-ecs-2-archetypes-and-vectorization-fe21690805f9)
|
||||
- [Storage in pictures](https://ajmmertens.medium.com/building-an-ecs-storage-in-pictures-642b8bfd6e04)
|
||||
- [Data Oriented Hierarchies](https://ajmmertens.medium.com/building-an-ecs-data-oriented-hierarchies-62fb2847d100)
|
||||
- [Making the most of entity identifiers](https://ajmmertens.medium.com/doing-a-lot-with-a-little-ecs-identifiers-25a72bd2647)
|
||||
- [Building games in ECS with entity relationships](https://ajmmertens.medium.com/building-games-in-ecs-with-entity-relationships-657275ba2c6c)
|
||||
- [Why it is time to start thinking of games as databases](https://ajmmertens.medium.com/why-it-is-time-to-start-thinking-of-games-as-databases-e7971da33ac3)
|
||||
- [A Roadmap to ECS relationships](https://ajmmertens.medium.com/a-roadmap-to-entity-relationships-5b1d11ebb4eb)
|
||||
- [Why storing state machines in ECS is a bad idea](https://ajmmertens.medium.com/why-storing-state-machines-in-ecs-is-a-bad-idea-742de7a18e59)
|
||||
- [Why vanilla ECS is not enough](https://ajmmertens.medium.com/why-vanilla-ecs-is-not-enough-d7ed4e3bebe5)
|
||||
- [ECS: From tool to paradigm](https://ajmmertens.medium.com/ecs-from-tool-to-paradigm-350587cdf216)
|
||||
|
||||
## Resources
|
||||
|
||||
### Resources provided by the community
|
||||
Some of these are quite old, and might contain information that is out of date!
|
||||
|
||||
- [Bgfx/Imgui module](https://github.com/flecs-hub/flecs-systems-bgfx/tree/bgfx_imgui)
|
||||
- [Tower defense example](https://gist.github.com/oldmanauz/b4ced44737bf9d248233538fa06a989e)
|
||||
- [Unreal + Flecs example](https://github.com/PreyK/Unreal-Minimum-Viable-Flecs)
|
||||
- [Building a space battle with Flecs in UE4](https://twitter.com/ajmmertens/status/1361070033334456320)
|
||||
- [Flecs + SDL + Web ASM example](https://github.com/HeatXD/flecs_web_demo) ([live demo](https://heatxd.github.io/flecs_web_demo/))
|
||||
- [Flecs + Raylib example](https://github.com/Lexxicon/FlecsRaylib)
|
||||
- [Flecs + gunslinger example](https://github.com/MrFrenik/gs_examples/blob/main/ex_demos/flecs/source/main.c)
|
||||
|
||||
### Flecs around the web
|
||||
- [Discord](https://discord.gg/BEzP5Rgrrp)
|
||||
- [Medium](https://ajmmertens.medium.com)
|
||||
- [Twitter](https://twitter.com/ajmmertens)
|
||||
- [Reddit](https://www.reddit.com/r/flecs)
|
||||
@@ -0,0 +1,186 @@
|
||||
# FAQ
|
||||
Frequently asked questions.
|
||||
|
||||
## What is an ECS?
|
||||
See the [ECS FAQ](https://github.com/SanderMertens/ecs-faq)
|
||||
|
||||
## Why is Flecs written in C?
|
||||
There are a lot of reasons, but the main ones are:
|
||||
- Faster compile times, especially when compared with header-only C++ libraries
|
||||
- C can be called from almost any programming language
|
||||
- C is super portable and has a tiny runtime so you can run it on a toaster if you want
|
||||
- The rules and limitations of the ECS aren't dictated by any type system
|
||||
- You can create a zero-overhead C++ API on top of C, but not the other way around
|
||||
|
||||
## Can I use Flecs with C++20 or higher?
|
||||
You can! Even though the C++ API is C++17, you can use it with any revision at or above 17.
|
||||
|
||||
## Can I use std::vector or other types inside components?
|
||||
You can! Components can contain almost any C++ type.
|
||||
|
||||
## What is an archetype?
|
||||
Archetype-based refers to the way the ECS stores components. It means that all entities with the same components are stored together in an archetype. This provides efficient CPU cache utilization and allows for things like vectorization and fast querying.
|
||||
|
||||
Other examples of archetype implementations are Unity DOTS, Unreal Mass and Bevy ECS.
|
||||
|
||||
For more information, see this blog: https://ajmmertens.medium.com/building-an-ecs-2-archetypes-and-vectorization-fe21690805f9
|
||||
|
||||
## How does Flecs compare with EnTT?
|
||||
Flecs and EnTT both are ECS libraries, but other than that they are different in almost every way, which can make comparing the two frameworks tricky. When you are comparing Flecs and EnTT, you can _generally_ expect to see the following:
|
||||
|
||||
- Add/remove operations are faster in EnTT
|
||||
- Single component queries are faster in EnTT
|
||||
- Multi-component queries are faster in Flecs
|
||||
- Bulk-creating entities are faster in Flecs
|
||||
- Entity destruct are faster in Flecs (especially for worlds/registries with lots of components)
|
||||
- Iterating a single entity's components are faster in Flecs
|
||||
|
||||
When doing a benchmark comparison don't rely on someone else's numbers, always test for your own use case!
|
||||
|
||||
## Is Flecs used for commercial projects?
|
||||
Yes, Flecs is used commercially (see the README).
|
||||
|
||||
## Why are queries slow?
|
||||
This is likely because queries (`flecs::query`) are created repeatedly in a loop or system. Queries are the fastest way to iterate over entities, but are expensive to create, so make sure to create them in advance:
|
||||
|
||||
```cpp
|
||||
// GOOD
|
||||
flecs::query<Position> q = world.query<Position>();
|
||||
|
||||
world.system()
|
||||
.run([=](flecs::iter& it) {
|
||||
q.each([&](Position& p) {
|
||||
// ...
|
||||
});
|
||||
});
|
||||
```
|
||||
```cpp
|
||||
// BAD
|
||||
world.system()
|
||||
.run([](flecs::iter& it) {
|
||||
flecs::query<Position> q = world.query<Position>();
|
||||
q.each([&](Position& p) {
|
||||
// ...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Why are queries taking up a lot of RAM?
|
||||
Likely because of the same reason as above. Queries are registered with the world, and unless they are deleted explicitly they will take up space. To delete a query, do:
|
||||
|
||||
```cpp
|
||||
q.destruct();
|
||||
```
|
||||
|
||||
## Why is my system called multiple times per frame?
|
||||
System functions are called for each matching archetype (see above for "What is an archetype"). In short, the reason for this is that it provides systems with direct access to component arrays.
|
||||
|
||||
If you have code that needs to run only once per frame or you want to take control over the entire iteration, take a look the `custom_runner` examples, which show how to use the `run` callback.
|
||||
|
||||
## Can Flecs be compiled to web assembly?
|
||||
Yes it can! See the [quickstart manual](Quickstart.md) for more information.
|
||||
|
||||
## Why am I getting an “use of undeclared identifier 'FLECS_ID …’” compiler error?
|
||||
This happens in C if the variable that holds the component id can't be found. This example shows how to fix it: https://github.com/SanderMertens/flecs/tree/master/examples/c/entities/fwd_declare_component
|
||||
|
||||
## Why are my entity ids so large?
|
||||
When you inspect the integer value of an entity you may see that this value is very large, usually around 4 billion. This is not a bug, and instead means that the entity id has been recycled. This example shows when recycling happens:
|
||||
|
||||
```cpp
|
||||
flecs::entity e1 = world.entity(); // small id
|
||||
e1.destruct(); // id is made available for recycling
|
||||
|
||||
flecs::entity e2 = world.entity(); // recycles e1
|
||||
e1.is_alive(); // false
|
||||
e2.is_alive(); // true
|
||||
|
||||
std::cout << e2.id(); // large id, upper 32 bits contains liveliness count
|
||||
```
|
||||
|
||||
## What is the difference between add & set? Why do both exist?
|
||||
An `add` just adds a component without assigning a value to it. A `set` assigns a value to the component. Both operations ensure that the entity will have the component afterwards.
|
||||
|
||||
An `add` is used mostly for adding things that don't have a value, like empty types/tags and most relationships. If `add` is used with a component that has a constructor, adding the component will invoke its constructor.
|
||||
|
||||
Additionally you can use `emplace` to construct a component in place in the storage (similar to `std::vector::emplace`).
|
||||
|
||||
## Can Flecs serialize components?
|
||||
Yes it can! See the reflection examples:
|
||||
|
||||
- https://github.com/SanderMertens/flecs/tree/master/examples/c/reflection
|
||||
- https://github.com/SanderMertens/flecs/tree/master/examples/cpp/reflection
|
||||
|
||||
## Why is Flecs so large?
|
||||
If you look at the size of the [distr/flecs.c](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs.c) and [distr/flecs.h](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs.c) files you might wonder why they are so large. There are a few reasons:
|
||||
|
||||
- The files contain a _lot_ of comments and in-code documentation!
|
||||
- Flecs has a small core with a lot of addons. The [distr/flecs.c](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs.c) and [distr/flecs.h](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs.c) files are the full source code, including addons.
|
||||
- The [distr/flecs.h](https://raw.githubusercontent.com/SanderMertens/flecs/master/distr/flecs.c) file contains the full C++ API.
|
||||
- Flecs implements its own data structures like vectors and maps, vs. depending on something like the STL.
|
||||
- C tends to be a bit more verbose than other languages.
|
||||
|
||||
Not all addons are useful in any project. You can customize a Flecs build to only build the things you need, which reduces executable size and improves build speed. See the quickstart on how to customize a build.
|
||||
|
||||
## Why does the explorer not work?
|
||||
Make sure that:
|
||||
- The REST API is enabled (see the [Remote API manual](FlecsRemoteApi.md))
|
||||
- You can reach the REST API by testing http://localhost:27750/entity/flecs
|
||||
- You call `ecs_progress`/`world::progress` in your main loop
|
||||
|
||||
If that doesn't work, see the [README of the explorer](https://github.com/flecs-hub/explorer) for potential issues with browser security settings and how to run a local instance of the explorer.
|
||||
|
||||
## Does the explorer collect data from my application?
|
||||
No! The https://flecs.dev/explorer page is a 100% client side application that does not talk to a backend. When you navigate to the page it will attempt to connect to http://localhost:27750 to find a running Flecs application. The data that the explorer fetches never leaves your machine.
|
||||
|
||||
## Why can't I see component values in the explorer?
|
||||
The explorer can only display component values if the reflection data has been registered for the component. See the [C reflection examples](https://github.com/SanderMertens/flecs/tree/master/examples/c/reflection) and [C++ reflection examples](https://github.com/SanderMertens/flecs/tree/master/examples/cpp/reflection) for more information.
|
||||
|
||||
## How do I detect which entities have changed?
|
||||
Flecs has builtin change detection. Additionally, you can use an `OnSet` observer to get notified of changes to component values. See the query change detection and observer examples for more information.
|
||||
|
||||
## Are relationships just a component with an entity handle?
|
||||
No, relationships are a deeply integrated feature that is faster in many ways than a component with an entity handle. See the [relationship manual](Relationships.md) for more information.
|
||||
|
||||
## Can I create systems outside of the main function?
|
||||
You can! Systems can be created from any function, except other systems.
|
||||
|
||||
## Can I use my own scheduler implementation?
|
||||
You can! The flecs scheduler (implemented in the pipeline addon) is fully optional and is built on top of Flecs. You can create a custom pipeline, or a custom schedule implementation that does something else entirely than what Flecs provides.
|
||||
|
||||
## Can I use Flecs without using systems?
|
||||
You can! Systems are an optional addon that can be disabled. You can build applications that just use Flecs queries.
|
||||
|
||||
## Why does the lookup function not find my entity?
|
||||
This is likely because the entity has a parent. A lookup by name requires you to provide the full path to an entity, like:
|
||||
|
||||
```c
|
||||
ecs_lookup(world, "parent.child");
|
||||
```
|
||||
|
||||
or in C++:
|
||||
|
||||
```cpp
|
||||
world.lookup("parent::child");
|
||||
```
|
||||
|
||||
## Can I add or remove components from within a system?
|
||||
You can! By default ECS operations are deferred when called from inside a system, which means that they are added to a queue and processed later when it's safe to do so. Flecs does not have a dedicated command API, if you call an operation from a system it will be automatically added to the command queue!
|
||||
|
||||
## What does a LOCKED_STORAGE error mean?
|
||||
A LOCKED_STORAGE error means that you're trying to add or remove an entity to an archetype that is currently being accessed, usually during query iteration. An example:
|
||||
|
||||
```cpp
|
||||
q.each([](flecs::entity e) {
|
||||
e.destruct(); // LOCKED_STORAGE error: removes entity from table
|
||||
});
|
||||
```
|
||||
|
||||
Most LOCKED_STORAGE errors can be resolved by putting `defer_begin` and `defer_end` around the iteration, which postpones the table-changing operations until after the iteration:
|
||||
|
||||
```cpp
|
||||
world.defer_begin();
|
||||
q.each([](flecs::entity e) {
|
||||
e.destruct();
|
||||
});
|
||||
world.defer_end(); // OK: entities are deleted here
|
||||
```
|
||||
@@ -0,0 +1,492 @@
|
||||
# Flecs Query Language
|
||||
|
||||
## Introduction
|
||||
The Flecs Query Language is a string-based representation of queries. The query language makes it easy to create queries at runtime, which makes it useful for tooling, modding or for requesting data from game servers.
|
||||
|
||||
This manual is primarily focused on describing the query syntax. For more details on specific query feature, see the query manual.
|
||||
|
||||
## Example
|
||||
```c
|
||||
// Match spaceship entities that are docked to a planet
|
||||
SpaceShip, (DockedTo, $planet), Planet($planet)
|
||||
```
|
||||
|
||||
## The Basics
|
||||
An expression in the Flecs Query Language consists of a list of comma separated "terms", where each term in the query is a condition that entities matching the query must satisfy.
|
||||
|
||||
### Components
|
||||
The most basic kind of condition is "the entity must have this component". The following expression is an example of a query that matches all entities that have both `Position` and `Velocity`:
|
||||
|
||||
```c
|
||||
// Match entities that have Position and Velocity
|
||||
Position, Velocity
|
||||
```
|
||||
|
||||
Note that this query matches all entities that _at least_ have the `Position` and `Velocity` components; entities with components in addition to `Position` and `Velocity` also match the query.
|
||||
|
||||
### Pairs
|
||||
The following expression is an example of a query that matches two pairs:
|
||||
```c
|
||||
// Match entities that have (Likes, Bob) and (Eats, Apples)
|
||||
(Likes, Bob), (Eats, Apples)
|
||||
```
|
||||
|
||||
## Operators
|
||||
Query operators change how a term is matched against entities. Only a single operator can be applied to a term at a time. The following sections go over the different operators in the query language.
|
||||
|
||||
### Not
|
||||
The `not` operator, specified with the `!` character, inverts the result of a term and makes it possible to match entities that _do not_ satisfy a condition. The following expression is an example of a query with a `not` operator:
|
||||
|
||||
```c
|
||||
// Match entities that have Position but not Velocity
|
||||
Position, !Velocity
|
||||
```
|
||||
|
||||
### Or
|
||||
The `or` operator, specified with the `||` character, makes it possible to chain together a list of terms of which at least one term must be true. The following expression is an example of a query with an `or` operator:
|
||||
|
||||
```c
|
||||
// Match entities that have Position and Velocity or Mass
|
||||
Position, Velocity || Mass
|
||||
```
|
||||
|
||||
### Optional
|
||||
The `optional` operator, specified with the `?` character, optionally matches a component. Optional terms do not change which entities are matched by a query, but can be useful for various reasons:
|
||||
|
||||
- Fetching a component in a query is more efficient than `get`
|
||||
- It allows for a single query to do what would otherwise have to be split up across several queries
|
||||
|
||||
The following expression is an example of a query with an `optional` operator:
|
||||
|
||||
```c
|
||||
// Match entities that have Position and optionally Velocity
|
||||
Position, ?Velocity
|
||||
```
|
||||
|
||||
### AndFrom
|
||||
the `andfrom` operator allows a query to match a list of components that another entity has. The entities used for the component list are typically prefabs, as they are not matched with queries themselves.
|
||||
|
||||
The following expression is an example of a query with an `andfrom` operator:
|
||||
|
||||
```c
|
||||
// Match entities with Position and all components that MyType has
|
||||
Position, and|MyType
|
||||
```
|
||||
|
||||
The MyType entity could be created as shown in the following example:
|
||||
|
||||
```cpp
|
||||
flecs::entity my_type = world.prefab("MyType")
|
||||
.add<Velocity>()
|
||||
.add<Mass>();
|
||||
```
|
||||
|
||||
This would cause the above query to be equivalent to:
|
||||
|
||||
```c
|
||||
Position, Velocity, Mass
|
||||
```
|
||||
|
||||
### NotFrom
|
||||
the `notfrom` operator allows a query to not match a list of components that another entity has. The entities used for the component list are typically prefabs, as they are not matched with queries themselves.
|
||||
|
||||
The following expression is an example of a query with an `notfrom` operator:
|
||||
|
||||
```c
|
||||
// Match entities with Position and not any of the components that MyType has
|
||||
Position, not|MyType
|
||||
```
|
||||
|
||||
The MyType entity could be created as shown in the following example:
|
||||
|
||||
```cpp
|
||||
flecs::entity my_type = world.prefab("MyType")
|
||||
.add<Velocity>()
|
||||
.add<Mass>();
|
||||
```
|
||||
|
||||
This would cause the above query to be equivalent to:
|
||||
|
||||
```c
|
||||
Position, !Velocity, !Mass
|
||||
```
|
||||
|
||||
### OrFrom
|
||||
the `orfrom` operator allows a query to match at least one of a list of components that another entity has. The entities used for the component list are typically prefabs, as they are not matched with queries themselves.
|
||||
|
||||
The following expression is an example of a query with an `orfrom` operator:
|
||||
|
||||
```c
|
||||
// Match entities with Position and at least one of the components that MyType has
|
||||
Position, or|MyType
|
||||
```
|
||||
|
||||
The MyType entity could be created as shown in the following example:
|
||||
|
||||
```cpp
|
||||
flecs::entity my_type = world.prefab("MyType")
|
||||
.add<Velocity>()
|
||||
.add<Mass>();
|
||||
```
|
||||
|
||||
This would cause the above query to be equivalent to:
|
||||
|
||||
```c
|
||||
Position, Velocity || Mass
|
||||
```
|
||||
|
||||
## Access Modifiers
|
||||
The following access modifiers can be added to terms to specify how term data is accessed:
|
||||
|
||||
| Modifier | Description |
|
||||
| -------- | ----------- |
|
||||
| `[in]` | Matched component is read-only |
|
||||
| `[out]` | Matched component is read-write |
|
||||
| `[inout]` | Matched component is read-write |
|
||||
| `[none]` | Matched component is not accessed |
|
||||
| `[filter]` | Term does not produce events (for use with observers only) |
|
||||
|
||||
An example:
|
||||
|
||||
```c
|
||||
[inout] Position, [in] Velocity
|
||||
```
|
||||
|
||||
When no access modifier is specified, the term defaults to `InOutDefault`. This defaults to `[inout]` for terms that match owned components, and `[in]` for terms that match shared components (like singleton terms or terms matched through `up` traversal).
|
||||
|
||||
## Wildcards
|
||||
Query expressions can use wildcards to match any or all instances of a matching component or pair. Wildcards may appear in all parts of a term. The following examples are all valid wildcard queries:
|
||||
|
||||
```c
|
||||
Position, (Likes, *)
|
||||
Position, (*, Dogs)
|
||||
Position, (*, *)
|
||||
Position, *
|
||||
```
|
||||
|
||||
Query results contain information about the exact component or pair that was matched with the `ecs_field_id()` and `flecs::iter::id()` functions. This allows an application to inspect what was actually matched by a wildcard.
|
||||
|
||||
### Wildcard wildcard (*)
|
||||
The following expression is an example of a query that uses a wildcard:
|
||||
|
||||
```c
|
||||
// Match entities with a (Likes, *) pair
|
||||
// Return all matching pairs
|
||||
(Likes, *)
|
||||
```
|
||||
|
||||
The `*` wildcard returns all matching instances of the wildcard. If an entity has both `(Likes, Dogs)` and `(Likes, Cats)`, it will be returned twice by the query, once for each pair.
|
||||
|
||||
If a query has multiple wildcards, each permutation of the matched results will be returned. The following expression is an example of a query that has multiple wildcards:
|
||||
|
||||
```c
|
||||
// Match entities with (Likes, *) and (Eats, *) pairs
|
||||
// Return all pair permutations
|
||||
(Likes, *), (Eats, *)
|
||||
```
|
||||
|
||||
If a single entity has `(Likes, Dogs)` and `(Likes, Cats)`, and has `(Eats, Pizza)` and `(Eats, Salad)`, that entity will yield four results:
|
||||
|
||||
- `(Likes, Dogs)`, `(Eats, Pizza)`
|
||||
- `(Likes, Dogs)`, `(Eats, Salad)`
|
||||
- `(Likes, Cats)`, `(Eats, Pizza)`
|
||||
- `(Likes, Cats)`, `(Eats, Salad)`
|
||||
|
||||
### Any wildcard (_)
|
||||
The `any` (`_`) wildcard returns at most one result per wildcard. The following expression is an example of a query that uses an `any` wildcard:
|
||||
|
||||
```c
|
||||
// Match entities with a (Likes, *) pair
|
||||
// Return at most one result per entity
|
||||
(Likes, _)
|
||||
```
|
||||
|
||||
If an entity has both `(Likes, Dogs)` and `(Likes, Cats)`, the query will return only one result. The location of the `any` wildcard in the matched id will be replaced with `*`, indicating that no specific pair was matched. The above query would return the following id:
|
||||
|
||||
- `(Likes, *)`
|
||||
|
||||
If a query has multiple `any` wildcards, only a single result is returned. The following expression is an example of a query that has multiple wildcards:
|
||||
|
||||
```c
|
||||
// Match entities with (Likes, *) and (Eats, *) pairs
|
||||
// Return at most one result per entity
|
||||
(Likes, _), (Eats, _)
|
||||
```
|
||||
|
||||
If a single entity has `(Likes, Dogs)` and `(Likes, Cats)`, and has `(Eats, Pizza)` and `(Eats, Salad)`, that entity will yield one result:
|
||||
|
||||
- `(Likes, *)`, `(Eats, *)`
|
||||
|
||||
## Variables
|
||||
Query variables constrain which values a wildcard can assume by ensuring that the value that was matched by a wildcard in one term is used in all other terms. The following expression is an example of a query that uses variables:
|
||||
|
||||
```c
|
||||
// Match all entities that eat what they like
|
||||
(Likes, $food), (Eats, $food)
|
||||
```
|
||||
|
||||
If a single entity has `(Likes, Dogs)` and `(Likes, Pizza)`, and has `(Eats, Pizza)` and `(Eats, Salad)`, that entity will yield only one result:
|
||||
|
||||
- `(Likes, Pizza)`, `(Eats, Pizza)`
|
||||
|
||||
Note how this is a strict subset of the results that would be returned by the following query:
|
||||
|
||||
```c
|
||||
(Likes, *), (Eats, *)
|
||||
```
|
||||
|
||||
Which would return:
|
||||
|
||||
- `(Likes, Dogs)`, `(Eats, Pizza)`
|
||||
- `(Likes, Dogs)`, `(Eats, Salad)`
|
||||
- `(Likes, Pizza)`, `(Eats, Pizza)`
|
||||
- `(Likes, Pizza)`, `(Eats, Salad)`
|
||||
|
||||
Variables with names that that start with a `_` are treated as anonymous, and are not accessible when a query is iterated.
|
||||
|
||||
## Source
|
||||
All query terms have a "source", which is the entity on which the term is matched. If no term source is specified, it defaults to the `$this` variable. The following expressions show the same query without and with explicit source:
|
||||
|
||||
```c
|
||||
// Implicit source
|
||||
Position, Velocity
|
||||
```
|
||||
```c
|
||||
// Explicit source
|
||||
Position($this), Velocity($this)
|
||||
```
|
||||
|
||||
Note how both terms have the same `$this` source. Using the same variable ensures that both components are matched on the same entity.
|
||||
|
||||
The following expressions show how to use pair queries without and with explicit source:
|
||||
|
||||
```c
|
||||
// Implicit source
|
||||
(Likes, Dogs), (Eats, Salad)
|
||||
```
|
||||
```c
|
||||
// Explicit source
|
||||
Likes($this, Dogs), Eats($this, Salad)
|
||||
```
|
||||
|
||||
A single query can have terms that are matched on more than one source. The following sections describe the supported source kinds.
|
||||
|
||||
### Static source
|
||||
A static source is a term that is always matched on an entity that is known at query creation time. A static source is specified by just using the name of the entity on which the component should be matched:
|
||||
|
||||
```c
|
||||
// Match TimeOfDay component on 'Game' entity
|
||||
TimeOfDay(Game)
|
||||
```
|
||||
|
||||
### Variable source
|
||||
A variable source is a variable that is used as term source. As mentioned already, when no source is specified, a term implicitly uses the builtin `$this` variable as source:
|
||||
|
||||
```c
|
||||
// Match entities with both Position and Velocity
|
||||
Position($this), Velocity($this)
|
||||
```
|
||||
|
||||
A variable used as source may appear in a different location in other terms. For example, the following expression uses a variable to match all entities that have components with the `Serializable` component:
|
||||
|
||||
```c
|
||||
Serializable($component), $component($this)
|
||||
```
|
||||
|
||||
The following example matches all spaceship entities that are docked to a planet:
|
||||
|
||||
```c
|
||||
SpaceShip($this), DockedTo($this, $planet), Planet($planet)
|
||||
```
|
||||
|
||||
The following example matches all entities that are eating healthy, but do not like what they are eating:
|
||||
|
||||
```c
|
||||
Eats($this, $food), !Likes($this, $food), Healthy($food)
|
||||
```
|
||||
|
||||
## Traversal
|
||||
Query traversal makes it possible to match a component by traversing a relationship until an entity with the component has been found. A common use case for this is a transform system, where a `Transform` component is matched both on an entity and its parent.
|
||||
|
||||
The following expression shows an example of a query that matches a `Transform` component both on an entity and its parent:
|
||||
|
||||
```c
|
||||
Transform($this), Transform($this|up ChildOf)
|
||||
```
|
||||
|
||||
The same query can be specified without the `$this` variable:
|
||||
|
||||
```c
|
||||
Transform, Transform(up ChildOf)
|
||||
```
|
||||
|
||||
As `ChildOf` is the default traversal relationship, this query can be further shortened to:
|
||||
|
||||
```c
|
||||
Transform, Transform(up)
|
||||
```
|
||||
|
||||
The `cascade` modifier is similar to `up` but returns results in breadth-first order. This is typically used in transform systems to ensure parents are transformed before children. The following expression shows an example with `cascade`:
|
||||
|
||||
```c
|
||||
Transform, Transform(cascade)
|
||||
```
|
||||
|
||||
The `desc` modifier can be used in combination with `cascade` to return results in reverse order:
|
||||
|
||||
```c
|
||||
Transform, Transform(cascade|desc)
|
||||
```
|
||||
|
||||
The `self` traversal modifier can be used in combination with `up` to first test if the entity itself has the component before traversing the hierarchy:
|
||||
|
||||
```c
|
||||
// First try matching Style on self, find on parent if not found
|
||||
Position, Style(self|up)
|
||||
```
|
||||
|
||||
When a component has the `(OnInstantiate, Inherit)` trait, queries will automatically insert `self|up` traversal for the `IsA` relationship. The following two queries are equivalent, if `Style` has the `(OnInstantiate, Inherit)` trait:
|
||||
|
||||
```cpp
|
||||
Position, Style
|
||||
```
|
||||
```cpp
|
||||
Position, Style(self|up IsA)
|
||||
```
|
||||
|
||||
Traversal modifiers can be used with any relationship that has the `Traversable` trait:
|
||||
|
||||
```cpp
|
||||
world.component<MyRelationship>().add(flecs::Traversable);
|
||||
```
|
||||
|
||||
When a query matches a component that is inherited from, a query will automatically traverse the `IsA` relationship downwards to find all subclasses. For example, if `MeleeUnit` has an `IsA` relationship to `Unit`, the following query matches entities with `Unit` and `MeleeUnit`:
|
||||
|
||||
```c
|
||||
Unit
|
||||
```
|
||||
|
||||
To prevent queries from evaluating component inheritance, the `self` modifier can be added to the component:
|
||||
|
||||
```c
|
||||
Unit|self
|
||||
```
|
||||
|
||||
For terms with an explicit source, the `self` modifier comes before the parentheses:
|
||||
|
||||
```c
|
||||
Unit|self($this)
|
||||
```
|
||||
|
||||
When a query matches a relationship that has the `Transitive` trait, it will traverse the relationship up or down depending on which parts of the query are variable. To prevent a query from matching results transitively, add the `self` modifier to the second element of a pair:
|
||||
|
||||
```c
|
||||
LocatedIn($this, SanFrancisco|self)
|
||||
```
|
||||
|
||||
This will only match entities that have `(LocatedIn, SanFrancisco)` and not, for example, entities with `(LocatedIn, GoldenGateBridge)`.
|
||||
|
||||
## Advanced
|
||||
|
||||
### Equality operators
|
||||
Equality operators allow queries to match variables with specific values or names. The following example shows a query that matches a variable against with a specific entity:
|
||||
|
||||
```c
|
||||
SpaceShip($this), $this == UssEnterprise || $this == Voyager
|
||||
```
|
||||
|
||||
The `!=` operator can be used to negate a result:
|
||||
|
||||
```c
|
||||
SpaceShip($this), $this != UssEnterprise
|
||||
```
|
||||
|
||||
Queries may also compare two variables:
|
||||
|
||||
```c
|
||||
PoweredBy($this, $source), $this != $source
|
||||
```
|
||||
|
||||
When a string is used as operand, the operation will test if the name of the entity matches:
|
||||
|
||||
```c
|
||||
SpaceShip($this), $this == "UssEnterprise"
|
||||
```
|
||||
|
||||
The `~=` operator can be used to do a fuzzy comparison, equivalent to the behavior of the `substr` function:
|
||||
|
||||
```c
|
||||
SpaceShip($this), $this ~= "Uss"
|
||||
```
|
||||
|
||||
The result of `~=` can be negated by prefixing the expression with a `!`:
|
||||
|
||||
```c
|
||||
SpaceShip($this), $this ~= "!Uss"
|
||||
```
|
||||
|
||||
When an equality operator is the first term that populates a variable, it will assign the variable:
|
||||
|
||||
```c
|
||||
$this == "UssEnterprise", SpaceShip($this)
|
||||
```
|
||||
|
||||
### Lookup variables
|
||||
Variables can be used as the starting point of a by-name lookup. This can be useful when matching hierarchies that have a well-defined structure. The following expression is an example of a query with a lookup variable:
|
||||
|
||||
```c
|
||||
// Match all spaceship entities where the cockpit has no power
|
||||
SpaceShip($this), !Powered($this.cockpit)
|
||||
```
|
||||
|
||||
This query will look for an child entity named `cockpit` in the scope of the matched entity for `$this`, and use that entity to match with `Powered`. If no entity with the name `cockpit` is found, the term will evaluate to false.
|
||||
|
||||
### Member matching
|
||||
Queries can match against the values of component members if they are of the `ecs_entity_t` type. The following expression shows an example of how to match against a `direction` member in a `Movement` component:
|
||||
|
||||
```c
|
||||
Movement.direction($this, Left)
|
||||
```
|
||||
|
||||
The same query with an implicit source:
|
||||
|
||||
```c
|
||||
(Movement.direction, Left)
|
||||
```
|
||||
|
||||
A member expression can be used in combination with variables:
|
||||
|
||||
```c
|
||||
(Thrusters.left, $thruster), Active($thruster)
|
||||
```
|
||||
|
||||
### Dependent variables
|
||||
When a variable is used first in a term that is conditionally evaluated, any subsequent terms that use the variable will only be evaluated if the variable was set. This allows for the creation of simple branches within queries. The following expression shows an example of dependent variables:
|
||||
|
||||
```c
|
||||
// $animal and $food are set conditionally
|
||||
(Likes, $animal) || (Eats, $food),
|
||||
Friendly($animal), // Evaluated if (Likes, $animal) matched
|
||||
Healthy($food) // Evaluated if (Eats, $food) matched
|
||||
```
|
||||
|
||||
Dependent variables can also be created from optional terms:
|
||||
|
||||
```c
|
||||
// Planet($object) is only evaluated if (DockedTo, $object)
|
||||
// returned a result.
|
||||
SpaceShip, ?(DockedTo, $object), Planet($object)
|
||||
```
|
||||
|
||||
### Query scopes
|
||||
Query scopes can be used to apply an operator to the result of more than one term. Currently query scopes are only supported in combination with `not` operators. The following expressions show examples of query scopes:
|
||||
|
||||
```c
|
||||
// Match spaceships where none of the engines are healthy
|
||||
SpaceShip, !{ (Engine, $engine), Healthy($engine) }
|
||||
```
|
||||
|
||||
```c
|
||||
// Match spaceships where all of the engines are healthy
|
||||
SpaceShip, !{ (Engine, $engine), !Healthy($engine) }
|
||||
```
|
||||
@@ -0,0 +1,771 @@
|
||||
This tutorial hasn't been updated for v4 yet! Use the [Script Manual](FlecsScript.md) for now.
|
||||
|
||||
# Flecs Script Tutorial
|
||||
This tutorial shows you how to use Flecs script, which is a declarative language for creating entities without having to write and compile code. Flecs script can be used for different things, such as building scenes, assets, or as a language for configuration files.
|
||||
|
||||
In this tutorial we will be designing a simple fence asset from scratch, and make it parameterizable so it can be easily reused across scenes. By the end of the tutorial we'll have an asset that looks like this:
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0Ausing%20flecs.meta%0Ausing%20flecs.game%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Atemplate%20Fence%20%7B%0A%20%20%2F%2F%20Fence%20parameters%0A%20%20prop%20width%20%3A%20f32%20%3D%2040%0A%20%20prop%20height%20%3A%20f32%20%3D%2020%0A%20%20prop%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%0A%20%20%2F%2F%20Pillar%20parameters%0A%20%20const%20pillar_width%20%3D%202%0A%20%20const%20pillar_spacing%20%3D%2010%0A%20%20const%20pillar_count%20%3D%20%24width%20%2F%20%24pillar_spacing%0A%20%20const%20p_grid_spacing%20%3D%20%24width%20%2F%20(%24pillar_count%20-%201)%0A%20%0A%20%20%2F%2F%20Bar%20parameters%0A%20%20const%20bar_spacing%20%3D%203%0A%20%20const%20bar_height%20%3D%202%0A%20%20const%20bar_depth%20%3D%20%24pillar_width%20%2F%202%0A%20%20const%20bar_count%20%3D%20%24height%20%2F%20%24bar_spacing%0A%20%20const%20b_grid_spacing%20%3D%20%24height%20%2F%20%24bar_count%0A%20%20%20%0A%20%20with%20Prefab%2C%20%24color%20%7B%0A%20%20%20%20Pillar%20%3A-%20Box%20%7B%0A%20%20%20%20%20%20%24pillar_width%2C%20%24height%2C%20%24pillar_width%0A%20%20%20%20%7D%0A%20%20%20%20Bar%20%3A-%20Box%20%7B%24width%2C%20%24bar_height%2C%20%24bar_depth%7D%0A%20%20%7D%20%20%20%20%0A%20%20%0A%20%20%2F%2F%20Pillars%0A%20%20pillars%20%7B%0A%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%7D%0A%20%20%20%20-%20Grid%7B%0A%20%20%20%20%20%20x.count%3A%20%24pillar_count%2C%0A%20%20%20%20%20%20x.spacing%3A%20%24p_grid_spacing%0A%20%20%20%20%20%20prefab%3A%20Pillar%20%20%20%20%0A%20%20%20%20%7D%0A%20%20%7D%0A%0A%20%20%2F%2F%20Bars%0A%20%20bars%20%7B%0A%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%7D%0A%20%20%20%20-%20Grid%7B%0A%20%20%20%20%20%20y.count%3A%20%24bar_count%2C%0A%20%20%20%20%20%20y.spacing%3A%20%24b_grid_spacing%0A%20%20%20%20%20%20prefab%3A%20Bar%20%20%20%20%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%20%2F%2F%20template%20Fence%0A%0A%0Atemplate%20Enclosing%20%7B%0A%20%20prop%20width%3A%20f32%20%3D%2040%0A%20%20prop%20height%3A%20f32%20%3D%2010%0A%20%20prop%20depth%3A%20f32%20%3D%2040%0A%20%20prop%20color%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%20%20%0A%20%20const%20width_half%20%3D%20%24width%20%2F%202%0A%20%20const%20depth_half%20%3D%20%24depth%20%2F%202%0A%20%20const%20PI%20%3D%203.1415926%0A%0A%20%20left%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20-%24width_half%7D%0A%20%20%20%20-%20Rotation3%7By%3A%20%24PI%20%2F%202%7D%0A%20%20%20%20-%20Fence%7Bwidth%3A%20%24depth%2C%20height%3A%24%2C%20color%3A%24%7D%0A%20%20%7D%0A%20%20right%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20%24width_half%7D%0A%20%20%20%20-%20Rotation3%7By%3A%20%24PI%20%2F%202%7D%0A%20%20%20%20-%20Fence%7Bwidth%3A%20%24depth%2C%20height%3A%24%2C%20color%3A%24%7D%0A%20%20%7D%0A%20%20back%20%7B%0A%20%20%20%20-%20Position3%7Bz%3A%20-%24depth_half%7D%0A%20%20%20%20-%20Fence%7Bwidth%3A%20%24width%2C%20height%3A%24%2C%20color%3A%24%7D%0A%20%20%7D%0A%20%20front%20%7B%0A%20%20%20%20-%20Position3%7Bz%3A%20%24depth_half%7D%0A%20%20%20%20-%20Fence%7Bwidth%3A%20%24width%2C%20height%3A%24%2C%20color%3A%24%7D%0A%20%20%7D%0A%7D%20%2F%2F%20template%20Enclosing%0A%0Afence_a%20%7B%0A%20%20-%20Enclosing%7B%7D%0A%7D%0A%0Acamera%20%7B%0A%20%20-%20Position3%7B21%2C%2019%2C%20-37%7D%0A%20%20-%20Rotation3%7B-0.42%2C%20-0.52%7D%0A%7D%0A)
|
||||
|
||||
Note: in order to use Flecs script an app needs to be built with the `FLECS_SCRIPT` addon.
|
||||
|
||||
## Getting Started with the Explorer
|
||||
The Flecs explorer is a web-based application that lets us write scripts and see the results directly. In the tutorial we will use the explorer in combination with the Flecs playground, which has a rendering canvas and comes preloaded with a number of modules and assets.
|
||||
|
||||
Go to this URL to open the explorer with the playground:
|
||||
https://www.flecs.dev/explorer/?wasm=https://www.flecs.dev/explorer/playground.js
|
||||
|
||||
The page should look similar to this:
|
||||
[](https://www.flecs.dev/explorer/?wasm=https://www.flecs.dev/explorer/playground.js)
|
||||
|
||||
The panel on the left is the entity treeview, which shows all of the entities in our scene. The center view is the canvas, which shows us the renderable entities of our scene (more on that later). Finally, the pane on the right is the editor, where we can write flecs scripts.
|
||||
|
||||
At any point in time you can disable panels by clicking on the "x" in the top-right corner. Panels can be brought back by pressing on their button in the menu bar on the left.
|
||||
|
||||
One other important thing is the link button in the top-right of the screen. You can use that button to obtain a link to your content, which is a good way to save progress, or to share what you've created with other people.
|
||||
|
||||
## The Basics
|
||||
Currently the explorer is showing the default scene. Let's clear it by removing all code from the editor. You should now see an empty canvas:
|
||||

|
||||
|
||||
Lets create an entity by typing its name into the editor:
|
||||
|
||||
```js
|
||||
my_entity
|
||||
```
|
||||
|
||||
Notice that as we are typing the entity shows up in the treeview:
|
||||
[](https://www.flecs.dev/explorer/?wasm=https://www.flecs.dev/explorer/playground.js&script=%0Amy_entity%0A)
|
||||
|
||||
Entities are automatically created if they did not exist yet. Try entering the same entity twice:
|
||||
|
||||
```js
|
||||
my_entity
|
||||
my_entity
|
||||
```
|
||||
|
||||
Only one entity shows up in the treeview. The second time `my_entity` was parsed it already existed, so nothing needed to be done.
|
||||
|
||||
## Adding Components
|
||||
Now that we have an entity, let's add a few components and tags to it. Change the text in the editor to this, to create an entity with a tag called `SpaceShip`:
|
||||
|
||||
```js
|
||||
my_entity :- SpaceShip
|
||||
```
|
||||
|
||||
Note that we didn't have to explicitly declare `SpaceShip` in advance, and that it also shows up as entity in the treeview. We can add multiple things to an entity this way:
|
||||
|
||||
```js
|
||||
my_entity :- SpaceShip
|
||||
my_entity :- FasterThanLight
|
||||
```
|
||||
|
||||
To avoid repeating the entity name many times, we can use the `{}` operators to open a scope for the entity. Inside the scope we can list components for the entity by prefixing them with a dash (`-`):
|
||||
|
||||
```js
|
||||
my_entity {
|
||||
- SpaceShip
|
||||
- FasterThanLight
|
||||
}
|
||||
```
|
||||
|
||||
We can inspect the entity and its contents by opening it in the entity inspector. To do this, click on the entity in the treeview. You should now see this:
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=%0Amy_entity%20%7B%0A%20%20-%20SpaceShip%0A%20%20-%20FasterThanLight%0A%7D%0A&entity=my_entity)
|
||||
|
||||
Note how the `SpaceShip` and `FasterThanLight` tags show up in the editor. There is also a `Script: main` tag, which exists for Flecs to keep track of which entities got created by our script.
|
||||
|
||||
Adding a component is similar to adding tag with a value. Let's add the `Position3` component from the `flecs.components.transform` module which comes preloaded with the playground. Note how it also shows up in the inspector when we add this code:
|
||||
|
||||
```js
|
||||
my_entity {
|
||||
- SpaceShip
|
||||
- FasterThanLight
|
||||
- flecs.components.transform.Position3{1, 2, 3}
|
||||
}
|
||||
```
|
||||
|
||||
Having to type the full module name each time we want to use the `Position3` component would be annoying. Add this line to the top of the script:
|
||||
|
||||
```js
|
||||
using flecs.components.*
|
||||
```
|
||||
|
||||
We can now use the component without the module name, which looks much cleaner:
|
||||
|
||||
```js
|
||||
my_entity {
|
||||
- SpaceShip
|
||||
- FasterThanLight
|
||||
- Position3{1, 2, 3}
|
||||
}
|
||||
```
|
||||
|
||||
If all went well, the playground should now look like this:
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Amy_entity%20%7B%0A%20%20-%20SpaceShip%0A%20%20-%20FasterThanLight%0A%20%20-%20Position3%7B1%2C%202%2C%203%7D%0A%7D%0A&entity=my_entity)
|
||||
|
||||
Note how after we added the `Position3` component, the inspector also shows the `Transform` and `WorldCell` components. This happens because the playground imports modules that implement world partitioning and transforms, which we get for free just by using `flecs.components.transform.Position3` component.
|
||||
|
||||
In addition to components and tags we can also add relationship pairs to entities. To add a pair, add this line to the scope of the entity, and note how it shows up in the inspector:
|
||||
|
||||
```js
|
||||
- (OwnedBy, Player)
|
||||
```
|
||||
|
||||
Entities can be created in hierarchies. A child entity is created in the scope of an entity just like the components and tags, but without the preceding `-`. Add this to the scope of the entity:
|
||||
|
||||
```js
|
||||
cockpit {
|
||||
pilot :- (Faction, Earth)
|
||||
}
|
||||
```
|
||||
|
||||
You can see the hierarchy this created in the treeview by expanding `my_entity`:
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Amy_entity%20%7B%0A%20%20-%20SpaceShip%0A%20%20-%20FasterThanLight%0A%20%20-%20Position3%7B1%2C%202%2C%203%7D%0A%20%20%0A%20%20cockpit%20%7B%0A%20%20%20%20pilot%20%3A-%20(Faction%2C%20Earth)%0A%20%20%7D%0A%7D%0A&entity=my_entity)
|
||||
|
||||
Congratulations! You now know how to create entities, hierarchies, and how to add components and tags. None of the entities we created so far are visible in the canvas however, so lets do something about that.
|
||||
|
||||
## Drawing Shapes
|
||||
We will be building a fence asset from just primitive shapes, where each entity is a single shape (note that in a real game you would likely want to use actual meshes).
|
||||
|
||||
The renderer uses regular ECS queries to find the entities to render. For our entities to show up in these queries, they need to have at least three components:
|
||||
|
||||
- `Position3`
|
||||
- `Rgb` (a color)
|
||||
- `Box` or `Rectangle`
|
||||
|
||||
Let's start by drawing a plane. First remove all code from the editor except for this line:
|
||||
|
||||
```js
|
||||
using flecs.components.*
|
||||
```
|
||||
|
||||
Now add these lines into the editor to create our ground `plane`:
|
||||
|
||||
```js
|
||||
plane {
|
||||
- Position3{}
|
||||
Rectangle: {100, 100}
|
||||
- Rgb{0.9, 0.9, 0.9}
|
||||
}
|
||||
```
|
||||
|
||||
Something happened! But it doesn't look quite right:
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rectangle%7B100%2C%20100%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A)
|
||||
|
||||
The rectangle is rotated the wrong way for our plane. To fix this we need to rotate it 90 degrees or `π/2` radians on the x axis. First lets define `π` as a constant value in our script:
|
||||
|
||||
```js
|
||||
const PI = 3.1415926
|
||||
```
|
||||
|
||||
Now add this line to the scope of `plane`:
|
||||
|
||||
```js
|
||||
- Rotation3{$PI / 2}
|
||||
```
|
||||
|
||||
That looks better:
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B100%2C%20100%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A)
|
||||
|
||||
Let's increase the sides of the plane to `10000` so that the fog effect makes it blends in with the background, which gives the illusion of a horizon:
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A)
|
||||
|
||||
Note that the `PI` variable does not show up in the treeview. Variables do not create entities, and only exist within the context of a script.
|
||||
|
||||
Let's now add a cube to the scene. The code for this looks similar to the plane:
|
||||
|
||||
```js
|
||||
box {
|
||||
- Position3{}
|
||||
Box: {10, 10, 10}
|
||||
Rgb: {1, 0, 0}
|
||||
}
|
||||
```
|
||||
|
||||
The box is showing up, but it's intersecting with our plane:
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Abox%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Box%7B10%2C%2010%2C%2010%7D%0A%20%20-%20Rgb%7B1%2C%200%2C%200%7D%0A%7D%0A)
|
||||
|
||||
To fix this, we can move it up by setting the `y` member of `Position3` to half its size:
|
||||
|
||||
```js
|
||||
box {
|
||||
- Position3{y: 5}
|
||||
Box: {10, 10, 10}
|
||||
Rgb: {1, 0, 0}
|
||||
}
|
||||
```
|
||||
|
||||
Now the entire cube is visible, and should look like this:
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Abox%20%7B%0A%20%20-%20Position3%7By%3A%205%7D%0A%20%20-%20Box%7B10%2C%2010%2C%2010%7D%0A%20%20-%20Rgb%7B1%2C%200%2C%200%7D%0A%7D%0A)
|
||||
|
||||
We now have all of the basic knowledge to start drawing a fence! Note that if you want to move the camera around, first click on the canvas to give it focus. You can now move the camera around. To release focus from the canvas, click on it again (the green border should disappear).
|
||||
|
||||
## Drawing a Fence
|
||||
To draw a fence we just need to combine differently sized boxes together. First remove the code for the cube from the editor.
|
||||
|
||||
Before drawing the boxes, lets first make sure they're all created with the same color. We could add a color component with the same values to each entity, but that gets tedious if we want to change the color. Instead, we can use the `with` statement:
|
||||
|
||||
```js
|
||||
with Rgb{0.15, 0.1, 0.05} {
|
||||
// Boxes go here
|
||||
}
|
||||
```
|
||||
|
||||
Inside the `with` statement we can now create two box entities for the left and right pillar of the fence:
|
||||
|
||||
```js
|
||||
with Rgb{0.15, 0.1, 0.05} {
|
||||
left_pillar {
|
||||
- Position3{x: -10, y: 5}
|
||||
Box: {2, 10, 2}
|
||||
}
|
||||
right_pillar {
|
||||
- Position3{x: 10, y: 5}
|
||||
Box: {2, 10, 2}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The playground should now look like this:
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Awith%20Rgb%7B0.15%2C%200.1%2C%200.05%7D%20%7B%0A%20%20left_pillar%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20-10%2C%20y%3A%205%7D%0A%20%20%20%20-%20Box%7B2%2C%2010%2C%202%7D%0A%20%20%7D%0A%20%20right_pillar%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%2010%2C%20y%3A%205%7D%0A%20%20%20%20-%20Box%7B2%2C%2010%2C%202%7D%0A%20%20%7D%0A%7D%0A)
|
||||
|
||||
That works, but the code is starting to look a bit unwieldy. There are lots of magic numbers which will become difficult to maintain as our asset gets more complex. Let's see if we can clean this up a bit.
|
||||
|
||||
First lets define two variables for the color and box shape of the pillars. We already saw an example of a variable when we defined `PI`. These looks similar, except that because they are composite values, we also define their type:
|
||||
|
||||
```js
|
||||
const color = Rgb: {0.15, 0.1, 0.05}
|
||||
const pillar_box : Box = {2, 10, 2}
|
||||
```
|
||||
|
||||
This is better, but `pillar_box` still contains values that we have to update each time we want to change the shape of our fence. Instead we can do this, which is a bit more typing now but will be easier to maintain later:
|
||||
|
||||
```js
|
||||
const height = 10
|
||||
const color = Rgb: {0.15, 0.1, 0.05}
|
||||
|
||||
const pillar_width = 2
|
||||
const pillar_box : Box = {
|
||||
$pillar_width,
|
||||
$height,
|
||||
$pillar_width
|
||||
}
|
||||
```
|
||||
|
||||
Let's also add an additional `width` variable which stores the width of the fence (or the distance between two pillars):
|
||||
|
||||
```js
|
||||
const width = 20
|
||||
```
|
||||
|
||||
We can now update the code that creates the pillars to this:
|
||||
|
||||
```js
|
||||
with $color, $pillar_box {
|
||||
left_pillar {
|
||||
- Position3{x: -$width/2, y: $height/2}
|
||||
}
|
||||
right_pillar {
|
||||
- Position3{x: $width/2, y: $height/2}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This will reproduce the exact same scene, but we can now tweak the variables to change the shape of our fence. Try playing around with the values of the different variables to see their effect!
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Aconst%20width%20%3D%2010%0Aconst%20height%20%3D%2015%0Aconst%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%0Aconst%20pillar_width%20%3D%204%0Aconst%20pillar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%24pillar_width%2C%20%0A%20%20%24height%2C%20%0A%20%20%24pillar_width%0A%7D%0A%0Awith%20%24color%2C%20%24pillar_box%20%7B%0A%20%20left_pillar%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20-%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%7D%0A%20%20right_pillar%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%7D%0A%7D%0A)
|
||||
|
||||
This is no fence yet. We're still missing the crossbars. Let's add another entity to see what it looks like:
|
||||
|
||||
```js
|
||||
bar {
|
||||
- Position3{y: $height / 2}
|
||||
Box: {$width, 2, 1}
|
||||
- $color
|
||||
}
|
||||
```
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Aconst%20width%20%3D%2020%0Aconst%20height%20%3D%2010%0Aconst%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%0Aconst%20pillar_width%20%3D%202%0Aconst%20pillar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%24pillar_width%2C%20%0A%20%20%24height%2C%20%0A%20%20%24pillar_width%0A%7D%0A%0Awith%20%24color%2C%20%24pillar_box%20%7B%0A%20%20left_pillar%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20-%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%7D%0A%20%20right_pillar%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%7D%0A%7D%0A%0Abar%20%7B%0A%20%20-%20Position3%7By%3A%20%24height%20%2F%202%7D%0A%20%20-%20Box%7B%24width%2C%202%2C%201%7D%0A%20%20-%20%24color%20%20%0A%7D%0A)
|
||||
|
||||
Let's add a second entity and space the two entities out a bit so they don't overlap. Also make sure to give both entities a different name, or else we'll end up overwriting the previous values:
|
||||
|
||||
```js
|
||||
top_bar {
|
||||
- Position3{y: $height/2 + 2}
|
||||
Box: {$width, 2, 1}
|
||||
- $color
|
||||
}
|
||||
bottom_bar {
|
||||
- Position3{y: $height/2 - 2}
|
||||
Box: {$width, 2, 1}
|
||||
- $color
|
||||
}
|
||||
```
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Aconst%20width%20%3D%2020%0Aconst%20height%20%3D%2010%0Aconst%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%0Aconst%20pillar_width%20%3D%202%0Aconst%20pillar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%24pillar_width%2C%20%0A%20%20%24height%2C%20%0A%20%20%24pillar_width%0A%7D%0A%0Awith%20%24color%2C%20%24pillar_box%20%7B%0A%20%20left_pillar%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20-%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%7D%0A%20%20right_pillar%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%7D%0A%7D%0A%0Atop_bar%20%7B%0A%20%20-%20Position3%7By%3A%20%24height%2F2%20%2B%202%7D%0A%20%20-%20Box%7B%24width%2C%202%2C%201%7D%0A%20%20-%20%24color%20%20%0A%7D%0Abottom_bar%20%7B%0A%20%20-%20Position3%7By%3A%20%24height%2F2%20-%202%7D%0A%20%20-%20Box%7B%24width%2C%202%2C%201%7D%0A%20%20-%20%24color%20%20%0A%7D%0A)
|
||||
|
||||
That starts to look more like a fence! Just like before however, we have a bunch of magic values and redundancies, but we now know how to fix that. First, let's define a `bar_sep` variable to specify how far apart the bars should be separated:
|
||||
|
||||
```js
|
||||
const bar_sep = 4
|
||||
```
|
||||
|
||||
Let's also create a variable for the bar shape and bar height, similar to what we did before. Let's make the bar thickness scale with the width of a pillar:
|
||||
|
||||
```js
|
||||
const bar_height = 2
|
||||
const bar_depth = $pillar_width/2
|
||||
const bar_box : Box = {$width, $bar_height, $bar_depth}
|
||||
```
|
||||
|
||||
With that in place, we can cleanup the entity code for the bars:
|
||||
|
||||
```js
|
||||
with $color, $bar_box {
|
||||
top_bar {
|
||||
- Position3{y: $height/2 + $bar_sep/2}
|
||||
}
|
||||
bottom_bar {
|
||||
- Position3{y: $height/2 - $bar_sep/2}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We now have our fence, which we can fully tweak with by changing the values of the variables! There are a few problems with the current code however:
|
||||
|
||||
- The code is not packaged in a way that we can easily use
|
||||
- We can make the fence very wide or very high, but it will end up looking weird. Ideally we increase the number of pillars and bars as we scale up the fence.
|
||||
|
||||
The code is also growing. To make sure you don't lose track of what you're doing you can add comments:
|
||||
|
||||
```js
|
||||
// Create two bar entities
|
||||
with $color, $bar_box {
|
||||
top_bar {
|
||||
- Position3{y: $height/2 + $bar_sep/2}
|
||||
}
|
||||
bottom_bar {
|
||||
- Position3{y: $height/2 - $bar_sep/2}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Prefabs
|
||||
We have the basic structure of a fence, but what if we wanted two fences? We could of course duplicate all of the code, but that would be impractical. Instead we can package up what we have in a _prefab_.
|
||||
|
||||
Turning the existing code into a prefab is easy. Simply add these lines around the fence code:
|
||||
|
||||
```js
|
||||
Prefab Fence {
|
||||
// fence code goes here
|
||||
}
|
||||
```
|
||||
|
||||
This creates an entity `Fence` with the tag `Prefab`, and stores all entities we created so far as children of the `Fence` entity. This is the same as running the following code, but a bit more convenient:
|
||||
|
||||
```js
|
||||
Fence {
|
||||
- Prefab
|
||||
|
||||
// fence code goes here
|
||||
}
|
||||
```
|
||||
|
||||
Once you put the fence entities inside the prefab scope, you'll notice a few things:
|
||||
- The fence disappears from the canvas
|
||||
- The fence entities now show up under `Fence` in the treeview
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0APrefab%20Fence%20%7B%0A%0Aconst%20width%20%3D%2020%0Aconst%20height%20%3D%2010%0Aconst%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%0Aconst%20pillar_width%20%3D%202%0Aconst%20pillar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%24pillar_width%2C%20%0A%20%20%24height%2C%20%0A%20%20%24pillar_width%0A%7D%0A%0Awith%20%24color%2C%20%24pillar_box%20%7B%0A%20%20left_pillar%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20-%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%7D%0A%20%20right_pillar%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%7D%0A%7D%0A%0Aconst%20bar_sep%20%3D%204%0Aconst%20bar_height%20%3D%202%0Aconst%20bar_depth%20%3D%20%24pillar_width%2F2%0Aconst%20bar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%24width%2C%20%0A%20%20%24bar_height%2C%20%0A%20%20%24bar_depth%0A%7D%0A%0Awith%20%24color%2C%20%24bar_box%20%7B%0A%20%20top_bar%20%7B%0A%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20%2B%20%24bar_sep%2F2%7D%0A%20%20%7D%0A%20%20bottom_bar%20%7B%0A%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20-%20%24bar_sep%2F2%7D%0A%20%20%7D%0A%7D%0A%7D%20%2F%2F%20Prefab%20Fence%0A)
|
||||
|
||||
The reason that the fence disappears is that `Prefab` is a builtin tag that is by default ignored by queries, and as a result also by the renderer. Children of a prefab also become prefabs, so they also get ignored.
|
||||
|
||||
That seems a bit underwhelming, but what we can now do is create an entity that _inherits_ from the `Fence` prefab, by adding this line to the bottom of the script:
|
||||
|
||||
```
|
||||
my_fence : Fence
|
||||
```
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0APrefab%20Fence%20%7B%0A%20%20const%20width%20%3D%2020%0A%20%20const%20height%20%3D%2010%0A%20%20const%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%20%20%0A%20%20const%20pillar_width%20%3D%202%0A%20%20const%20pillar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%20%20%24pillar_width%2C%20%0A%20%20%20%20%24height%2C%20%0A%20%20%20%20%24pillar_width%0A%20%20%7D%0A%20%20%0A%20%20with%20%24color%2C%20%24pillar_box%20%7B%0A%20%20%20%20left_pillar%20%7B%0A%20%20%20%20%20%20-%20Position3%7Bx%3A%20-%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%20%20%7D%0A%20%20%20%20right_pillar%20%7B%0A%20%20%20%20%20%20-%20Position3%7Bx%3A%20%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20%0A%20%20const%20bar_sep%20%3D%204%0A%20%20const%20bar_height%20%3D%202%0A%20%20const%20bar_depth%20%3D%20%24pillar_width%2F2%0A%20%20const%20bar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%20%20%24width%2C%20%0A%20%20%20%20%24bar_height%2C%20%0A%20%20%20%20%24bar_depth%0A%20%20%7D%0A%20%20%0A%20%20with%20%24color%2C%20%24bar_box%20%7B%0A%20%20%20%20top_bar%20%7B%0A%20%20%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20%2B%20%24bar_sep%2F2%7D%0A%20%20%20%20%7D%0A%20%20%20%20bottom_bar%20%7B%0A%20%20%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20-%20%24bar_sep%2F2%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%20%2F%2F%20Prefab%20Fence%0A%0Amy_fence%20%3A%20Fence%0A)
|
||||
|
||||
Great, the fence is back! Even better, we can now create two fences simply by instantiating the prefab twice. Just make sure to give both instances different positions:
|
||||
|
||||
```js
|
||||
fence_a : Fence {
|
||||
- Position3{-10}
|
||||
}
|
||||
fence_b : Fence {
|
||||
- Position3{10}
|
||||
}
|
||||
```
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0APrefab%20Fence%20%7B%0A%20%20const%20width%20%3D%2020%0A%20%20const%20height%20%3D%2010%0A%20%20const%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%20%20%0A%20%20const%20pillar_width%20%3D%202%0A%20%20const%20pillar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%20%20%24pillar_width%2C%20%0A%20%20%20%20%24height%2C%20%0A%20%20%20%20%24pillar_width%0A%20%20%7D%0A%20%20%0A%20%20with%20%24color%2C%20%24pillar_box%20%7B%0A%20%20%20%20left_pillar%20%7B%0A%20%20%20%20%20%20-%20Position3%7Bx%3A%20-%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%20%20%7D%0A%20%20%20%20right_pillar%20%7B%0A%20%20%20%20%20%20-%20Position3%7Bx%3A%20%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20%0A%20%20const%20bar_sep%20%3D%204%0A%20%20const%20bar_height%20%3D%202%0A%20%20const%20bar_depth%20%3D%20%24pillar_width%2F2%0A%20%20const%20bar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%20%20%24width%2C%20%0A%20%20%20%20%24bar_height%2C%20%0A%20%20%20%20%24bar_depth%0A%20%20%7D%0A%20%20%0A%20%20with%20%24color%2C%20%24bar_box%20%7B%0A%20%20%20%20top_bar%20%7B%0A%20%20%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20%2B%20%24bar_sep%2F2%7D%0A%20%20%20%20%7D%0A%20%20%20%20bottom_bar%20%7B%0A%20%20%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20-%20%24bar_sep%2F2%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%20%2F%2F%20Prefab%20Fence%0A%0Afence_a%20%3A%20Fence%20%7B%0A%20%20-%20Position3%7B-10%7D%0A%7D%0Afence_b%20%3A%20Fence%20%7B%0A%20%20-%20Position3%7B10%7D%0A%7D%0A)
|
||||
|
||||
We could save this script as is, load it into our game, and instantiate the prefab from regular C/C++ code. This is what that would look like:
|
||||
|
||||
```c
|
||||
// In C
|
||||
ecs_script_run_file(world, "fence.flecs");
|
||||
|
||||
ecs_entity_t fence = ecs_lookup(world, "Fence");
|
||||
ecs_entity_t fence_a = ecs_new_w_pair(world, EcsIsA, fence);
|
||||
ecs_entity_t fence_b = ecs_new_w_pair(world, EcsIsA, fence);
|
||||
|
||||
ecs_set(world, fence_a, EcsPosition3, {-10});
|
||||
ecs_set(world, fence_b, EcsPosition3, {10});
|
||||
```
|
||||
```cpp
|
||||
// In C++
|
||||
using namespace flecs::components::transform;
|
||||
|
||||
ecs_script_run_file(world, "fence.flecs");
|
||||
|
||||
auto fence = world.lookup("Fence");
|
||||
auto fence_a = world.entity().is_a(fence);
|
||||
auto fence_b = world.entity().is_a(fence);
|
||||
|
||||
fence_a.set<Position3>({-10});
|
||||
fence_b.set<Position3>({10});
|
||||
```
|
||||
|
||||
We still have some problems, which we'll address in the next section:
|
||||
- Our prefab is static, we cannot change the parameters of the fence
|
||||
- The pillars and bars don't increase when the fence size increases
|
||||
|
||||
## Templates
|
||||
Prefabs are static collections of entities and components that we can instantiate multiple times. Templates are _prefab compositions_ that can be parameterized. In other words, templates let us to create our Fence, while also specifying the width and height.
|
||||
|
||||
Changing the above code into an template is easy. Just change this line:
|
||||
|
||||
```js
|
||||
Prefab Fence {
|
||||
// fence code
|
||||
}
|
||||
```
|
||||
|
||||
into this:
|
||||
|
||||
```js
|
||||
template Fence {
|
||||
// fence code
|
||||
}
|
||||
```
|
||||
|
||||
The editor will throw the following error:
|
||||
|
||||
```
|
||||
template 'Fence' has no properties
|
||||
```
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Atemplate%20Fence%20%7B%0A%20%20const%20width%20%3D%2020%0A%20%20const%20height%20%3D%2010%0A%20%20const%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%20%20%0A%20%20const%20pillar_width%20%3D%202%0A%20%20const%20pillar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%20%20%24pillar_width%2C%20%0A%20%20%20%20%24height%2C%20%0A%20%20%20%20%24pillar_width%0A%20%20%7D%0A%20%20%0A%20%20with%20%24color%2C%20%24pillar_box%20%7B%0A%20%20%20%20left_pillar%20%7B%0A%20%20%20%20%20%20-%20Position3%7Bx%3A%20-%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%20%20%7D%0A%20%20%20%20right_pillar%20%7B%0A%20%20%20%20%20%20-%20Position3%7Bx%3A%20%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20%0A%20%20const%20bar_sep%20%3D%204%0A%20%20const%20bar_height%20%3D%202%0A%20%20const%20bar_depth%20%3D%20%24pillar_width%2F2%0A%20%20const%20bar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%20%20%24width%2C%20%0A%20%20%20%20%24bar_height%2C%20%0A%20%20%20%20%24bar_depth%0A%20%20%7D%0A%20%20%0A%20%20with%20%24color%2C%20%24bar_box%20%7B%0A%20%20%20%20top_bar%20%7B%0A%20%20%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20%2B%20%24bar_sep%2F2%7D%0A%20%20%20%20%7D%0A%20%20%20%20bottom_bar%20%7B%0A%20%20%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20-%20%24bar_sep%2F2%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%20%2F%2F%20Prefab%20Fence%0A%0Afence_a%20%3A%20Fence%20%7B%0A%20%20-%20Position3%7B-10%7D%0A%7D%0Afence_b%20%3A%20Fence%20%7B%0A%20%20-%20Position3%7B10%7D%0A%7D%0A)
|
||||
|
||||
What does this mean? Templates, unlike prefabs, need _properties_, which are like the inputs to the template. For our fence, these could be `width` and `height` values.
|
||||
|
||||
To add properties to the template, we can take some of our existing `const` variables, and change `const` to `prop`. This will make the variables visible to the outside. Another thing we must do for properties is explicitly specify their type.
|
||||
|
||||
Let's take the existing `width`, `height` and `color` variables, and change them to properties. It is also good practice to put the props at the top of the template code, so it's easy to see which inputs it has:
|
||||
|
||||
```js
|
||||
template Fence {
|
||||
prop width = flecs.meta.f32: 20
|
||||
prop height = flecs.meta.f32: 10
|
||||
prop color = Rgb: {0.15, 0.1, 0.05}
|
||||
|
||||
// fence code
|
||||
}
|
||||
```
|
||||
|
||||
We can get rid of the `flecs.meta.` prefix by adding this to the top of our script:
|
||||
|
||||
```js
|
||||
using flecs.meta
|
||||
```
|
||||
|
||||
The code can now be changed to this:
|
||||
|
||||
```js
|
||||
template Fence {
|
||||
prop width = f32: 20
|
||||
prop height = f32: 10
|
||||
prop color = Rgb: {0.15, 0.1, 0.05}
|
||||
|
||||
// fence code
|
||||
}
|
||||
```
|
||||
|
||||
Note that while we were doing this, the fence disappeared again from the canvas. This happened because while we _inherit_ a prefab, we _assign_ an template. To make the fences visible again, change the code that creates the fences to this:
|
||||
|
||||
```js
|
||||
fence_a {
|
||||
- Fence{}
|
||||
- Position3{-10}
|
||||
}
|
||||
fence_b {
|
||||
- Fence{}
|
||||
- Position3{10}
|
||||
}
|
||||
```
|
||||
|
||||
The fences are back, with the default values we provided to our props. But we now have a new power! Try changing it to this:
|
||||
|
||||
```js
|
||||
fence_a {
|
||||
Fence: {width: 10, height: 20}
|
||||
- Position3{-10}
|
||||
}
|
||||
fence_b {
|
||||
Fence: {width: 25, height: 10}
|
||||
- Position3{10}
|
||||
}
|
||||
```
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0Ausing%20flecs.meta%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Atemplate%20Fence%20%7B%0A%20%20prop%20width%20%3A%20f32%20%3D%2020%0A%20%20prop%20height%20%3A%20f32%20%3D%2010%0A%20%20prop%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%20%20%0A%20%20const%20pillar_width%20%3D%202%0A%20%20const%20pillar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%20%20%24pillar_width%2C%20%0A%20%20%20%20%24height%2C%20%0A%20%20%20%20%24pillar_width%0A%20%20%7D%0A%20%20%0A%20%20with%20%24color%2C%20%24pillar_box%20%7B%0A%20%20%20%20left_pillar%20%7B%0A%20%20%20%20%20%20-%20Position3%7Bx%3A%20-%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%20%20%7D%0A%20%20%20%20right_pillar%20%7B%0A%20%20%20%20%20%20-%20Position3%7Bx%3A%20%24width%2F2%2C%20y%3A%20%24height%2F2%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20%0A%20%20const%20bar_sep%20%3D%204%0A%20%20const%20bar_height%20%3D%202%0A%20%20const%20bar_depth%20%3D%20%24pillar_width%2F2%0A%20%20const%20bar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%20%20%24width%2C%20%0A%20%20%20%20%24bar_height%2C%20%0A%20%20%20%20%24bar_depth%0A%20%20%7D%0A%20%20%0A%20%20with%20%24color%2C%20%24bar_box%20%7B%0A%20%20%20%20top_bar%20%7B%0A%20%20%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20%2B%20%24bar_sep%2F2%7D%0A%20%20%20%20%7D%0A%20%20%20%20bottom_bar%20%7B%0A%20%20%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20-%20%24bar_sep%2F2%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%20%2F%2F%20Prefab%20Fence%0A%0Afence_a%20%7B%0A%20%20-%20Fence%7Bwidth%3A%2010%2C%20height%3A%2020%7D%0A%20%20-%20Position3%7B-10%7D%0A%7D%0Afence_b%20%7B%0A%20%20-%20Fence%7Bwidth%3A%2025%2C%20height%3A%2010%7D%0A%20%20-%20Position3%7B10%7D%0A%7D%0A)
|
||||
|
||||
We are now well on our way to define a procedural fence. Note how similar assigning an template looks to assigning a component. This is no coincidence: an template is translated to a component, where each property becomes a member of that component.
|
||||
|
||||
This is something we can exploit in C/C++ code to make assigning templates to entities as easy as assigning a component:
|
||||
|
||||
```c
|
||||
// In C
|
||||
typedef struct {
|
||||
float width;
|
||||
float height;
|
||||
EcsRgb color;
|
||||
} Fence;
|
||||
|
||||
// Register a regular component
|
||||
ECS_COMPONENT(world, Fence);
|
||||
|
||||
// Because the Fence template has the same name as the
|
||||
// component it will "bind" to it.
|
||||
ecs_script_run_file(world, "fence.flecs");
|
||||
|
||||
// Set the component as usual
|
||||
ecs_entity_t fence_a = ecs_insert(world, ecs_value(Fence, {10, 20}));
|
||||
ecs_entity_t fence_b = ecs_insert(world, ecs_value(Fence, {25, 10}));
|
||||
|
||||
ecs_set(world, fence_a, EcsPosition3, {-10});
|
||||
ecs_set(world, fence_b, EcsPosition3, {10});
|
||||
```
|
||||
```cpp
|
||||
// In C++
|
||||
using namespace flecs::components::transform;
|
||||
|
||||
struct Fence {
|
||||
float width;
|
||||
float height;
|
||||
flecs::components::graphics::Rgb color;
|
||||
}
|
||||
|
||||
// Because the Fence template has the same name as the
|
||||
// component it will "bind" to it.
|
||||
ecs_script_run_file(world, "fence.flecs");
|
||||
|
||||
auto fence_a = world.entity().set<Fence>({10, 20});
|
||||
auto fence_b = world.entity().set<Fence>({25, 10});
|
||||
|
||||
fence_a.set<Position3>({-10});
|
||||
fence_b.set<Position3>({10});
|
||||
```
|
||||
|
||||
For this to work, we have to make sure that the types and ordering of the properties in the script match up with the types and ordering of members in the component type.
|
||||
|
||||
## Grids
|
||||
Let's now see if we can change the number of pillars and bars depending on the fence width and height.
|
||||
|
||||
At first glance this sounds like something that would require loops, and those are not supported in Flecs script. There is a way around this however, which is to use a `Grid` component that does the instantiating for us.
|
||||
|
||||
The `Grid` component is provided by the `flecs.game` module, so before using it lets add this to the top of the script:
|
||||
|
||||
```js
|
||||
using flecs.game
|
||||
```
|
||||
|
||||
The implementation for the `Grid` component can be found [here](https://github.com/flecs-hub/flecs-game/blob/main/src/main.c). We only use it in the tutorial for demonstration purposes. In practice we could create many different components that handle layout for us.
|
||||
|
||||
The grid component lets us create multiple instances of an entity that are laid out in a 1, 2 or 3 dimensional grid. Let's apply this to the number of pillars, which we want to scale with the width of our fence. For this we need two things:
|
||||
|
||||
- The width of the fence
|
||||
- The minimum spacing between pillars
|
||||
|
||||
We already have the fence width. Let's create another variable for the spacing:
|
||||
|
||||
```js
|
||||
const pillar_spacing = 10
|
||||
```
|
||||
|
||||
We can now calculate the number of pillars we want:
|
||||
|
||||
```js
|
||||
const pillar_count = $width / $pillar_spacing
|
||||
```
|
||||
|
||||
The `Grid` component can't consume the pillar code directly. Instead it accepts a prefab, so lets turn the existing pillars code into a prefab:
|
||||
|
||||
```js
|
||||
Prefab Pillar {
|
||||
- $color
|
||||
- $pillar_box
|
||||
}
|
||||
```
|
||||
|
||||
Note how we left out `Position3`, as this will be set by the `Grid` component. Without further ado, let's create a row of pillars:
|
||||
|
||||
```js
|
||||
pillars {
|
||||
- Position3{y: $height/2}
|
||||
- Grid{
|
||||
x.count: $pillar_count
|
||||
x.spacing: $pillar_spacing
|
||||
prefab: Pillar
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's dissect what this code does:
|
||||
- Create a `pillars` entity
|
||||
- Create it at position `y: $height/2` so pillars don't sink into the ground
|
||||
- With `$pillar_count` pillars on the x axis
|
||||
- Spaced `$pillar_spacing` apart
|
||||
- Using `Pillar` as prefab to instantiate the entities
|
||||
|
||||
Let's also change the code that instantiates our two fences to just one:
|
||||
|
||||
```
|
||||
fence :- Fence{}
|
||||
```
|
||||
|
||||
And increase the `width` of the fence to `60`:
|
||||
|
||||
```js
|
||||
prop width = f32: 60
|
||||
```
|
||||
|
||||
We now get a number of pillars that matches the fence length:
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0Ausing%20flecs.meta%0Ausing%20flecs.game%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Atemplate%20Fence%20%7B%0A%20%20prop%20width%20%3A%20f32%20%3D%2060%0A%20%20prop%20height%20%3A%20f32%20%3D%2010%0A%20%20prop%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%20%20%0A%20%20const%20pillar_width%20%3D%202%0A%20%20const%20pillar_spacing%20%3D%2010%0A%20%20const%20pillar_count%20%3D%20%24width%20%2F%20%24pillar_spacing%0A%20%20const%20pillar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%20%20%24pillar_width%2C%20%0A%20%20%20%20%24height%2C%20%0A%20%20%20%20%24pillar_width%0A%20%20%7D%0A%20%20%0A%20%20Prefab%20Pillar%20%7B%0A%20%20%20%20-%20%24color%0A%20%20%20%20-%20%24pillar_box%0A%20%20%7D%0A%20%20%20%20%0A%20%20pillars%20%7B%0A%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%7D%0A%20%20%20%20-%20Grid%7B%0A%20%20%20%20%20%20x.count%3A%20%24pillar_count%0A%20%20%20%20%20%20x.spacing%3A%20%24pillar_spacing%0A%20%20%20%20%20%20prefab%3A%20Pillar%20%20%20%20%0A%20%20%20%20%7D%0A%20%20%7D%20%20%0A%20%20%0A%20%20const%20bar_sep%20%3D%204%0A%20%20const%20bar_height%20%3D%202%0A%20%20const%20bar_depth%20%3D%20%24pillar_width%2F2%0A%20%20const%20bar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%20%20%24width%2C%20%0A%20%20%20%20%24bar_height%2C%20%0A%20%20%20%20%24bar_depth%0A%20%20%7D%0A%20%20%0A%20%20with%20%24color%2C%20%24bar_box%20%7B%0A%20%20%20%20top_bar%20%7B%0A%20%20%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20%2B%20%24bar_sep%2F2%7D%0A%20%20%20%20%7D%0A%20%20%20%20bottom_bar%20%7B%0A%20%20%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20-%20%24bar_sep%2F2%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%20%2F%2F%20Prefab%20Fence%0A%0Afence%20%3A-%20Fence%7B%7D%0A)
|
||||
|
||||
There's still something off though, which is that the pillars don't exactly line up with the ends up of the fence. We can fix this with a simple calculation that takes the number of pillars, and uses that to recompute the grid spacing.
|
||||
|
||||
```js
|
||||
const grid_spacing = $width / ($pillar_count - 1)
|
||||
```
|
||||
|
||||
Note that this takes into account that there is one more pillar than there are spaces between pillars. When we replace `pillar_spacing` in the grid with `grid_spacing`, we get the correct pillar alignment:
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0Ausing%20flecs.meta%0Ausing%20flecs.game%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Atemplate%20Fence%20%7B%0A%20%20prop%20width%20%3A%20f32%20%3D%2060%0A%20%20prop%20height%20%3A%20f32%20%3D%2010%0A%20%20prop%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%20%20%0A%20%20const%20pillar_width%20%3D%202%0A%20%20const%20pillar_spacing%20%3D%2010%0A%20%20const%20pillar_count%20%3D%20%24width%20%2F%20%24pillar_spacing%0A%20%20const%20grid_spacing%20%3D%20%24width%20%2F%20(%24pillar_count%20-%201)%0A%20%20const%20pillar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%20%20%24pillar_width%2C%20%0A%20%20%20%20%24height%2C%20%0A%20%20%20%20%24pillar_width%0A%20%20%7D%0A%20%20%0A%20%20Prefab%20Pillar%20%7B%0A%20%20%20%20-%20%24color%0A%20%20%20%20-%20%24pillar_box%0A%20%20%7D%0A%20%20%20%20%0A%20%20pillars%20%7B%0A%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%7D%0A%20%20%20%20-%20Grid%7B%0A%20%20%20%20%20%20x.count%3A%20%24pillar_count%0A%20%20%20%20%20%20x.spacing%3A%20%24grid_spacing%0A%20%20%20%20%20%20prefab%3A%20Pillar%20%20%20%20%0A%20%20%20%20%7D%0A%20%20%7D%20%20%0A%20%20%0A%20%20const%20bar_sep%20%3D%204%0A%20%20const%20bar_height%20%3D%202%0A%20%20const%20bar_depth%20%3D%20%24pillar_width%2F2%0A%20%20const%20bar_box%20%3A%20Box%20%3D%20%7B%0A%20%20%20%20%24width%2C%20%0A%20%20%20%20%24bar_height%2C%20%0A%20%20%20%20%24bar_depth%0A%20%20%7D%0A%20%20%0A%20%20with%20%24color%2C%20%24bar_box%20%7B%0A%20%20%20%20top_bar%20%7B%0A%20%20%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20%2B%20%24bar_sep%2F2%7D%0A%20%20%20%20%7D%0A%20%20%20%20bottom_bar%20%7B%0A%20%20%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%20-%20%24bar_sep%2F2%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%20%2F%2F%20Prefab%20Fence%0A%0Afence_a%20%7B%0A%20%20-%20Fence%7B%7D%0A%20%20-%20Position3%7B-10%7D%0A%7D%0A)
|
||||
|
||||
We can do the same thing for the bars. I'll skip right to the result since it involves very similar steps:
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0Ausing%20flecs.meta%0Ausing%20flecs.game%0A%0Aconst%20PI%20%3D%203.1415926%0A%0A%2F%2F%20The%20ground%20plane%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%2F2%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Atemplate%20Fence%20%7B%0A%20%20%2F%2F%20Fence%20parameters%0A%20%20prop%20width%20%3A%20f32%20%3D%2040%0A%20%20prop%20height%20%3A%20f32%20%3D%2020%0A%20%20const%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%0A%20%20%2F%2F%20Pillar%20parameters%0A%20%20const%20pillar_width%20%3D%202%0A%20%20const%20pillar_spacing%20%3D%2010%0A%20%20const%20pillar_count%20%3D%20%24width%20%2F%20%24pillar_spacing%0A%20%20const%20p_grid_spacing%20%3D%20%24width%20%2F%20(%24pillar_count%20-%201)%0A%20%0A%20%20%2F%2F%20Bar%20parameters%0A%20%20const%20bar_spacing%20%3D%203%0A%20%20const%20bar_height%20%3D%202%0A%20%20const%20bar_depth%20%3D%20%24pillar_width%20%2F%202%0A%20%20const%20bar_count%20%3D%20%24height%20%2F%20%24bar_spacing%0A%20%20const%20b_grid_spacing%20%3D%20%24height%20%2F%20%24bar_count%0A%20%20%20%0A%20%20with%20Prefab%2C%20%24color%20%7B%0A%20%20%20%20Pillar%20%3A-%20Box%20%7B%0A%20%20%20%20%20%20%24pillar_width%2C%20%24height%2C%20%24pillar_width%0A%20%20%20%20%7D%0A%20%20%20%20Bar%20%3A-%20Box%20%7B%24width%2C%20%24bar_height%2C%20%24bar_depth%7D%0A%20%20%7D%20%20%20%20%0A%20%20%0A%20%20%2F%2F%20Pillars%0A%20%20pillars%20%7B%0A%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%7D%0A%20%20%20%20-%20Grid%7B%0A%20%20%20%20%20%20x.count%3A%20%24pillar_count%2C%0A%20%20%20%20%20%20x.spacing%3A%20%24p_grid_spacing%0A%20%20%20%20%20%20prefab%3A%20Pillar%20%20%20%20%0A%20%20%20%20%7D%0A%20%20%7D%0A%0A%20%20%2F%2F%20Bars%0A%20%20bars%20%7B%0A%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%7D%0A%20%20%20%20-%20Grid%7B%0A%20%20%20%20%20%20y.count%3A%20%24bar_count%2C%0A%20%20%20%20%20%20y.spacing%3A%20%24b_grid_spacing%0A%20%20%20%20%20%20prefab%3A%20Bar%20%20%20%20%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%20%2F%2F%20template%20Fence%0A%0Afence%20%3A-%20Fence%7B%7D)
|
||||
|
||||
The values got tweaked a bit to get a more aesthetically pleasing result when scaling up. Feel free to tune the variables to values that produce something you like!
|
||||
|
||||
We're almost done! There is only one thing left, which is to combine the fence template in a nested template so we can create an enclosing.
|
||||
|
||||
## Nested Templates
|
||||
Now that we have our Fence template ready to go, we can build a higher level templates which reuses existing the `Fence` template. One typical thing we could do is instantiate a `Fence` four times, so that it creates an enclosed space.
|
||||
|
||||
The code for this doesn't introduce anything beyond what we've already learned. Let's first setup a new template at the bottom of our script.
|
||||
Inside the template, lets create `width` and `depth` properties, so we can define a rectangular area. Let's also add a `color` and `height` property that we can passthrough to our `Fence` template.
|
||||
|
||||
When put together, this is what it looks like:
|
||||
|
||||
```js
|
||||
template Enclosing {
|
||||
prop width = f32: 40
|
||||
prop height = f32: 10
|
||||
prop depth = f32: 40
|
||||
prop color = Rgb: {0.15, 0.1, 0.05}
|
||||
|
||||
// enclosing code goes here
|
||||
}
|
||||
```
|
||||
|
||||
Now we need to instantiate `Fence` four times, for each side of the rectangle. We'll also add a few convenience variables so we don't end
|
||||
up writing the same divisions multiple times.
|
||||
|
||||
```js
|
||||
const width_half = $width / 2
|
||||
const depth_half = $depth / 2
|
||||
const PI = 3.1415926
|
||||
|
||||
left {
|
||||
- Position3{x: -$width_half}
|
||||
- Rotation3{y: $PI/2}
|
||||
Fence: {width: $depth, height:$, color:$}
|
||||
}
|
||||
right {
|
||||
- Position3{x: $width_half}
|
||||
- Rotation3{y: $PI/2}
|
||||
Fence: {width: $depth, height:$, color:$}
|
||||
}
|
||||
back {
|
||||
- Position3{z: -$depth_half}
|
||||
Fence: {width: $width, height:$, color:$}
|
||||
}
|
||||
front {
|
||||
- Position3{z: $depth_half}
|
||||
Fence: {width: $width, height:$, color:$}
|
||||
}
|
||||
```
|
||||
|
||||
Note how the passthrough parameters are specified as `height:$`. This is a shorthand notation for `height: $height`, and can save a lot of typing when working with nested templates.
|
||||
|
||||
Let's now use our new `Enclosing` template by changing
|
||||
|
||||
```js
|
||||
fence :- Fence{}
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```
|
||||
enclosing :- Enclosing{}
|
||||
```
|
||||
|
||||
Here is what that looks like. You might need to zoom out a bit with the camera to see the full fence. Remember that you can do this by first clicking on the canvas to give it focus, then using the WASD keys to move the camera around. Don't forget to click on the canvas again to give focus back to the explorer.
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0Ausing%20flecs.meta%0Ausing%20flecs.game%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Atemplate%20Fence%20%7B%0A%20%20%2F%2F%20Fence%20parameters%0A%20%20prop%20width%20%3A%20f32%20%3D%2040%0A%20%20prop%20height%20%3A%20f32%20%3D%2020%0A%20%20prop%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%0A%20%20%2F%2F%20Pillar%20parameters%0A%20%20const%20pillar_width%20%3D%202%0A%20%20const%20pillar_spacing%20%3D%2010%0A%20%20const%20pillar_count%20%3D%20%24width%20%2F%20%24pillar_spacing%0A%20%20const%20p_grid_spacing%20%3D%20%24width%20%2F%20(%24pillar_count%20-%201)%0A%20%0A%20%20%2F%2F%20Bar%20parameters%0A%20%20const%20bar_spacing%20%3D%203%0A%20%20const%20bar_height%20%3D%202%0A%20%20const%20bar_depth%20%3D%20%24pillar_width%20%2F%202%0A%20%20const%20bar_count%20%3D%20%24height%20%2F%20%24bar_spacing%0A%20%20const%20b_grid_spacing%20%3D%20%24height%20%2F%20%24bar_count%0A%20%20%20%0A%20%20with%20Prefab%2C%20%24color%20%7B%0A%20%20%20%20Pillar%20%3A-%20Box%20%7B%0A%20%20%20%20%20%20%24pillar_width%2C%20%24height%2C%20%24pillar_width%0A%20%20%20%20%7D%0A%20%20%20%20Bar%20%3A-%20Box%20%7B%24width%2C%20%24bar_height%2C%20%24bar_depth%7D%0A%20%20%7D%20%20%20%20%0A%20%20%0A%20%20%2F%2F%20Pillars%0A%20%20pillars%20%7B%0A%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%7D%0A%20%20%20%20-%20Grid%7B%0A%20%20%20%20%20%20x.count%3A%20%24pillar_count%2C%0A%20%20%20%20%20%20x.spacing%3A%20%24p_grid_spacing%0A%20%20%20%20%20%20prefab%3A%20Pillar%20%20%20%20%0A%20%20%20%20%7D%0A%20%20%7D%0A%0A%20%20%2F%2F%20Bars%0A%20%20bars%20%7B%0A%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%7D%0A%20%20%20%20-%20Grid%7B%0A%20%20%20%20%20%20y.count%3A%20%24bar_count%2C%0A%20%20%20%20%20%20y.spacing%3A%20%24b_grid_spacing%0A%20%20%20%20%20%20prefab%3A%20Bar%20%20%20%20%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%20%2F%2F%20template%20Fence%0A%0A%0Atemplate%20Enclosing%20%7B%0A%20%20prop%20width%3A%20f32%20%3D%2040%0A%20%20prop%20height%3A%20f32%20%3D%2010%0A%20%20prop%20depth%3A%20f32%20%3D%2040%0A%20%20prop%20color%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%20%20%0A%20%20const%20width_half%20%3D%20%24width%20%2F%202%0A%20%20const%20depth_half%20%3D%20%24depth%20%2F%202%0A%20%20const%20PI%20%3D%203.1415926%0A%0A%20%20left%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20-%24width_half%7D%0A%20%20%20%20-%20Rotation3%7By%3A%20%24PI%20%2F%202%7D%0A%20%20%20%20-%20Fence%7Bwidth%3A%20%24depth%2C%20height%3A%24%2C%20color%3A%24%7D%0A%20%20%7D%0A%20%20right%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20%24width_half%7D%0A%20%20%20%20-%20Rotation3%7By%3A%20%24PI%20%2F%202%7D%0A%20%20%20%20-%20Fence%7Bwidth%3A%20%24depth%2C%20height%3A%24%2C%20color%3A%24%7D%0A%20%20%7D%0A%20%20back%20%7B%0A%20%20%20%20-%20Position3%7Bz%3A%20-%24depth_half%7D%0A%20%20%20%20-%20Fence%7Bwidth%3A%20%24width%2C%20height%3A%24%2C%20color%3A%24%7D%0A%20%20%7D%0A%20%20front%20%7B%0A%20%20%20%20-%20Position3%7Bz%3A%20%24depth_half%7D%0A%20%20%20%20-%20Fence%7Bwidth%3A%20%24width%2C%20height%3A%24%2C%20color%3A%24%7D%0A%20%20%7D%0A%7D%20%2F%2F%20template%20Enclosing%0A%0Afence_a%20%7B%0A%20%20-%20Enclosing%7B%7D%0A%7D%0A%0Acamera%20%7B%0A%20%20-%20Position3%7B21%2C%2019%2C%20-37%7D%0A%20%20-%20Rotation3%7B-0.42%2C%20-0.52%7D%0A%7D%0A)
|
||||
|
||||
We can now easily modify our enclosing by passing in parameters for width and height:
|
||||
|
||||
```
|
||||
enclosing { Enclosing: {width: 100, height: 30} }
|
||||
```
|
||||
|
||||
[](https://www.flecs.dev/explorer/?local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0Ausing%20flecs.meta%0Ausing%20flecs.game%0A%0Aconst%20PI%20%3D%203.1415926%0A%0A%2F%2F%20The%20ground%20plane%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%2F2%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Atemplate%20Fence%20%7B%0A%20%20%2F%2F%20Fence%20parameters%0A%20%20prop%20width%20%3A%20f32%20%3D%2040%0A%20%20prop%20height%20%3A%20f32%20%3D%2020%0A%20%20const%20color%20%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%0A%20%20%2F%2F%20Pillar%20parameters%0A%20%20const%20pillar_width%20%3D%202%0A%20%20const%20pillar_spacing%20%3D%2010%0A%20%20const%20pillar_count%20%3D%20%24width%20%2F%20%24pillar_spacing%0A%20%20const%20p_grid_spacing%20%3D%20%24width%20%2F%20(%24pillar_count%20-%201)%0A%20%0A%20%20%2F%2F%20Bar%20parameters%0A%20%20const%20bar_spacing%20%3D%203%0A%20%20const%20bar_height%20%3D%202%0A%20%20const%20bar_depth%20%3D%20%24pillar_width%20%2F%202%0A%20%20const%20bar_count%20%3D%20%24height%20%2F%20%24bar_spacing%0A%20%20const%20b_grid_spacing%20%3D%20%24height%20%2F%20%24bar_count%0A%20%20%20%0A%20%20with%20Prefab%2C%20%24color%20%7B%0A%20%20%20%20Pillar%20%3A-%20Box%20%7B%0A%20%20%20%20%20%20%24pillar_width%2C%20%24height%2C%20%24pillar_width%0A%20%20%20%20%7D%0A%20%20%20%20Bar%20%3A-%20Box%20%7B%24width%2C%20%24bar_height%2C%20%24bar_depth%7D%0A%20%20%7D%20%20%20%20%0A%20%20%0A%20%20%2F%2F%20Pillars%0A%20%20pillars%20%7B%0A%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%7D%0A%20%20%20%20-%20Grid%7B%0A%20%20%20%20%20%20x.count%3A%20%24pillar_count%2C%0A%20%20%20%20%20%20x.spacing%3A%20%24p_grid_spacing%0A%20%20%20%20%20%20prefab%3A%20Pillar%20%20%20%20%0A%20%20%20%20%7D%0A%20%20%7D%0A%0A%20%20%2F%2F%20Bars%0A%20%20bars%20%7B%0A%20%20%20%20-%20Position3%7By%3A%20%24height%2F2%7D%0A%20%20%20%20-%20Grid%7B%0A%20%20%20%20%20%20y.count%3A%20%24bar_count%2C%0A%20%20%20%20%20%20y.spacing%3A%20%24b_grid_spacing%0A%20%20%20%20%20%20prefab%3A%20Bar%20%20%20%20%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D%20%2F%2F%20Prefab%20Fence%0A%0Atemplate%20Enclosing%20%7B%0A%20%20prop%20width%3A%20f32%20%3D%2040%0A%20%20prop%20height%3A%20f32%20%3D%2010%0A%20%20prop%20depth%3A%20f32%20%3D%2040%0A%20%20prop%20color%3A%20Rgb%20%3D%20%7B0.15%2C%200.1%2C%200.05%7D%0A%20%20%0A%20%20const%20width_half%20%3D%20%24width%20%2F%202%0A%20%20const%20depth_half%20%3D%20%24depth%20%2F%202%0A%20%20const%20PI%20%3D%203.1415926%0A%0A%20%20left%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20-%24width_half%7D%0A%20%20%20%20-%20Rotation3%7By%3A%20%24PI%20%2F%202%7D%0A%20%20%20%20-%20Fence%7Bwidth%3A%20%24depth%2C%20height%3A%24%7D%0A%20%20%7D%0A%20%20right%20%7B%0A%20%20%20%20-%20Position3%7Bx%3A%20%24width_half%7D%0A%20%20%20%20-%20Rotation3%7By%3A%20%24PI%20%2F%202%7D%0A%20%20%20%20-%20Fence%7Bwidth%3A%20%24depth%2C%20height%3A%24%7D%0A%20%20%7D%0A%20%20back%20%7B%0A%20%20%20%20-%20Position3%7Bz%3A%20-%24depth_half%7D%0A%20%20%20%20-%20Fence%7Bwidth%3A%20%24width%2C%20height%3A%24%7D%0A%20%20%7D%0A%20%20front%20%7B%0A%20%20%20%20-%20Position3%7Bz%3A%20%24depth_half%7D%0A%20%20%20%20-%20Fence%7Bwidth%3A%20%24width%2C%20height%3A%24%7D%0A%20%20%7D%0A%7D%0A%0Apasture%20%3A-%20Enclosing%7B%0A%20%20width%3A%20100%2C%20%0A%20%20depth%3A%20100%0A%20%20height%3A%2030%0A%7D%0A%0Acamera%20%7B%0A%20%20-%20Position3%7B52%2C%2041%2C%20-82%7D%0A%20%20-%20Rotation3%7B-0.45%2C%20-0.54%7D%0A%7D%0A)
|
||||
|
||||
Congratulations! You now know how to create assets in Flecs Script! If you'd like to see more examples of how templates can be used to create complex compositions of assets, take a look at the templates in the Flecs playground project:
|
||||
|
||||
https://github.com/flecs-hub/playground/tree/main/etc/assets
|
||||
|
||||
Additionally you can also try instantiating one of the assets that come preloaded in the playground. Try replacing the editor content with:
|
||||
|
||||
```js
|
||||
using flecs.components.*
|
||||
using templates
|
||||
|
||||
const PI = 3.1415926
|
||||
|
||||
plane {
|
||||
- Position3{}
|
||||
- Rotation3{$PI / 2}
|
||||
Rectangle: {10000, 10000}
|
||||
- Rgb{0.9, 0.9, 0.9}
|
||||
}
|
||||
|
||||
town :- Town{}
|
||||
```
|
||||
|
||||
[](https://www.flecs.dev/explorer/?show=flecs script,explorer_canvas&local=true&wasm=https://www.flecs.dev/explorer/playground.js&script=using%20flecs.components.*%0Ausing%20templates%0A%0Aconst%20PI%20%3D%203.1415926%0A%0Aplane%20%7B%0A%20%20-%20Position3%7B%7D%0A%20%20-%20Rotation3%7B%24PI%20%2F%202%7D%0A%20%20-%20Rectangle%7B10000%2C%2010000%7D%0A%20%20-%20Rgb%7B0.9%2C%200.9%2C%200.9%7D%0A%7D%0A%0Atown%20%3A-%20Town%7B%7D)
|
||||
|
||||
That's all for this tutorial. Have fun creating, and don't hesitate to share the results in the `#showcase` channel on Discord! (link: https://discord.gg/caR2WmY)
|
||||
@@ -0,0 +1,789 @@
|
||||
# Hierarchies
|
||||
|
||||
## Introduction
|
||||
Entities can be arranged in parent/child hierarchies using the built-in hierarchy implementation. Hierarchies are useful for:
|
||||
|
||||
- Organizing entities into scenes
|
||||
- Organizing components/systems/observers/prefabs into modules
|
||||
- Representing asset hierarchies (see also the [Prefabs manual](PrefabsManual.md))
|
||||
- Representing UI widgets
|
||||
- Representing hierarchically organized data (like a file system)
|
||||
|
||||
The following example shows how to create a simple hierarchy:
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
ecs_entity_t spaceship = ecs_new(world);
|
||||
ecs_entity_t cockpit = ecs_new_w_pair(world, EcsChildOf, spaceship);
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
flecs::entity spaceship = world.entity();
|
||||
flecs::entity cockpit = world.entity().child_of(spaceship);
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C#</b>
|
||||
|
||||
```cs
|
||||
Entity spaceship = world.Entity();
|
||||
Entity cockpit = world.Entity()
|
||||
.ChildOf(spaceship);
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">Rust</b>
|
||||
|
||||
```rust
|
||||
let spaceship = world.entity();
|
||||
let cockpit = world.entity().child_of_id(spaceship);
|
||||
```
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
The following sections cover the different features of Flecs hierarchies.
|
||||
|
||||
## Get parent and children
|
||||
Applications can get the parent and children of an entity using the following APIs:
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
// Get parent for entity
|
||||
ecs_entity_t parent = ecs_get_parent(world, entity);
|
||||
|
||||
// Iterate children for entity
|
||||
ecs_iter_t it = ecs_children(world, parent);
|
||||
while (ecs_children_next(&it)) {
|
||||
for (int i = 0; i < it.count; i ++) {
|
||||
ecs_entity_t child = it.entities[i];
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
// Get parent for entity
|
||||
flecs::entity parent = entity.parent();
|
||||
|
||||
// Iterate children for entity
|
||||
parent.children([](flecs::entity child) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C#</b>
|
||||
|
||||
```cs
|
||||
// TODO
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">Rust</b>
|
||||
|
||||
```rust
|
||||
// TODO
|
||||
```
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
## Recursive cleanup
|
||||
When a parent is deleted, all of its children will also be deleted in depth-last order (children will be deleted before their parents). An example:
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
ecs_entity_t spaceship = ecs_new(world);
|
||||
ecs_entity_t cockpit = ecs_new_w_pair(world, EcsChildOf, spaceship);
|
||||
ecs_entity_t pilot = ecs_new_w_pair(world, EcsChildOf, cockpit);
|
||||
|
||||
ecs_delete(world, spaceship);
|
||||
ecs_is_alive(world, spaceship); // false
|
||||
ecs_is_alive(world, cockpit); // false
|
||||
ecs_is_alive(world, pilot); // false
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
flecs::entity spaceship = world.entity();
|
||||
flecs::entity cockpit = world.entity().child_of(spaceship);
|
||||
flecs::entity pilot = world.entity().child_of(cockpit);
|
||||
|
||||
spaceship.destruct();
|
||||
spaceship.is_alive(); // false
|
||||
cockpit.is_alive(); // false
|
||||
pilot.is_alive(); // false
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C#</b>
|
||||
|
||||
```cs
|
||||
Entity spaceship = world.Entity();
|
||||
Entity cockpit = world.Entity()
|
||||
.ChildOf(spaceship);
|
||||
Entity pilot = world.Entity()
|
||||
.ChildOf(cockpit);
|
||||
|
||||
spaceship.Destruct();
|
||||
spaceship.IsAlive(); // false
|
||||
cockpit.IsAlive(); // false
|
||||
pilot.IsAlive(); // false
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">Rust</b>
|
||||
|
||||
```rust
|
||||
let spaceship = world.entity();
|
||||
let cockpit = world.entity().child_of_id(spaceship);
|
||||
let pilot = world.entity().child_of_id(cockpit);
|
||||
|
||||
spaceship.destruct();
|
||||
spaceship.is_alive(); // false
|
||||
cockpit.is_alive(); // false
|
||||
pilot.is_alive(); // false
|
||||
```
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
## Depth-first traversal
|
||||
Hierarchies can be traversed depth-first by iterating the children of a parent recursively. For an example, see:
|
||||
|
||||
- C: https://github.com/SanderMertens/flecs/blob/master/examples/c/entities/hierarchy/src/main.c
|
||||
- C++: https://github.com/SanderMertens/flecs/blob/master/examples/cpp/entities/hierarchy/src/main.cpp
|
||||
|
||||
## Breadth-first traversal
|
||||
Hierarchies can be traversed breadth-first by using the `cascade` feature of [queries](https://www.flecs.dev/flecs/md_docs_2Queries.html#relationship-traversal). For an example, see:
|
||||
|
||||
- C: https://github.com/SanderMertens/flecs/blob/master/examples/c/queries/hierarchies/src/main.c
|
||||
- C++: https://github.com/SanderMertens/flecs/blob/master/examples/cpp/queries/hierarchies/src/main.cpp
|
||||
|
||||
## Namespacing
|
||||
Entities are named, which allows them to be looked up in the world by name. Names are namespaced according to the entity hierarchy. For example, an entity named "Cockpit" with a parent called "SpaceShip" will have to be looked up as "SpaceShip.Cockpit" (C) or "SpaceShip::Cockpit" (C++).
|
||||
|
||||
For more details, see the [names section](https://www.flecs.dev/flecs/md_docs_2EntitiesComponents.html#names) in the Entities and Components manual.
|
||||
|
||||
## OrderedChildren
|
||||
The `OrderedChildren` trait can be added to entities to indicate that creation order or a custom order should be preserved.
|
||||
|
||||
When this trait is added to a parent, the entity ids returned by the `ecs_children` / `entity::children` operations will be in creation or custom order. Children of a parent with the `OrderedChildren` trait are guaranteed to be returned in a single result.
|
||||
|
||||
The trait does not affect the order in which entities are returned by queries.
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
ecs_entity_t parent = ecs_new(world);
|
||||
ecs_add_id(world, parent, EcsOrderedChildren);
|
||||
|
||||
ecs_entity_t child_1 = ecs_new_w_pair(world, EcsChildOf, parent);
|
||||
ecs_entity_t child_2 = ecs_new_w_pair(world, EcsChildOf, parent);
|
||||
ecs_entity_t child_3 = ecs_new_w_pair(world, EcsChildOf, parent);
|
||||
|
||||
// Adding/removing components usually changes the order in which children are
|
||||
// iterated, but with the OrderedChildren trait order is preserved.
|
||||
ecs_set(world, child_2, Position, {10, 20});
|
||||
|
||||
ecs_iter_t it = ecs_children(world, parent);
|
||||
while (ecs_children_next(&it)) {
|
||||
// it.table will be set to NULL when iterating ordered children.
|
||||
for (int i = 0; i < it.count; i ++) {
|
||||
ecs_entity_t e = it.entities[i];
|
||||
// i == 0: child_1
|
||||
// i == 1: child_2
|
||||
// i == 2: child_3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
flecs::entity parent = world.entity().add(flecs::OrderedChildren);
|
||||
|
||||
flecs::entity child_1 = world.entity().child_of(parent);
|
||||
flecs::entity child_2 = world.entity().child_of(parent);
|
||||
flecs::entity child_3 = world.entity().child_of(parent);
|
||||
|
||||
// Adding/removing components usually changes the order in which children are
|
||||
// iterated, but with the OrderedChildren trait order is preserved.
|
||||
child_2.set(Position{10, 20});
|
||||
|
||||
parent.children([](flecs::entity child) {
|
||||
// 1st result: child_1
|
||||
// 2nd result: child_2
|
||||
// 3rd result: child_3
|
||||
});
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C#</b>
|
||||
|
||||
```cs
|
||||
Entity parent = world.Entity().Add(Ecs.OrderedChildren);
|
||||
|
||||
Entity child_1 = world.Entity().ChildOf(parent);
|
||||
Entity child_2 = world.Entity().ChildOf(parent);
|
||||
Entity child_3 = world.Entity().ChildOf(parent);
|
||||
|
||||
// Adding/removing components usually changes the order in which children are
|
||||
// iterated, but with the OrderedChildren trait order is preserved.
|
||||
child_2.Set<Position>(new(10, 20));
|
||||
|
||||
parent.Children((Entity child) => {
|
||||
// 1st result: child_1
|
||||
// 2nd result: child_2
|
||||
// 3rd result: child_3
|
||||
});
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">Rust</b>
|
||||
|
||||
```rust
|
||||
let parent = world.entity().add_trait::<flecs::OrderedChildren>();
|
||||
|
||||
let child_1 = world.entity().child_of_id(parent);
|
||||
let child_2 = world.entity().child_of_id(parent);
|
||||
let child_3 = world.entity().child_of_id(parent);
|
||||
|
||||
// Adding/removing components usually changes the order in which children are
|
||||
// iterated, but with the OrderedChildren trait order is preserved.
|
||||
child_2.set(Position{10, 20});
|
||||
|
||||
parent.each_child(|child| {
|
||||
// 1st result: child_1
|
||||
// 2nd result: child_2
|
||||
// 3rd result: child_3
|
||||
});
|
||||
```
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
The stored order can be modified by an application with the `ecs_set_child_order` / `entity::set_child_order` operation.
|
||||
|
||||
## Hierarchy storage
|
||||
Flecs implements two hierarchy storages that are optimized for different use cases: `ChildOf` hierarchies and `Parent` hierarchies. The following sections go over the differences between the two storages, and when to use which one.
|
||||
|
||||
### Overview
|
||||
`ChildOf` hierarchies are optimized for large, "unstructured" hierarchies. An example of a large unstructured hierarchy is a scene. A scene root can have many (think thousands of) children that are dynamically created and deleted while the game is running. Which entities exist at any point in time is often not known beforehand, which is why this is an unstructured hierarchy.
|
||||
|
||||
`Parent` hierarchies are optimized for small structured hierarchies. An example of a small structured hierarchy is a [prefab](PrefabsManual.md). A prefab typically has a small number (think dozens) of children that are created when a prefab is instantiated, and deleted when an instance is deleted. At any point in the game it is usually known which entities exist.
|
||||
|
||||
Use `ChildOf` hierarchies when:
|
||||
|
||||
- A parent has many children
|
||||
- The number and/or kind of children is not known beforehand
|
||||
|
||||
Use `Parent` hierarchies when:
|
||||
|
||||
- A parent has a small number (dozens) of children
|
||||
- The number and kind of children is known beforehand
|
||||
- The hierarchy is deeply nested
|
||||
|
||||
`ChildOf` hierarchies are created by adding a `(ChildOf, parent)` relationship pair to an entity. `Parent` hierarchies are created by setting a `Parent` component on an entity. An example:
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
ecs_entity_t spaceship = ecs_new(world);
|
||||
|
||||
// Create child with ChildOf storage
|
||||
ecs_entity_t cockpit = ecs_new_w_pair(world, EcsChildOf, spaceship);
|
||||
|
||||
// Create child with Parent storage
|
||||
ecs_entity_t cockpit = ecs_new_w_parent(world, spaceship, NULL /* optional name */);
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
flecs::entity spaceship = world.entity();
|
||||
|
||||
// Create child with ChildOf storage
|
||||
flecs::entity cockpit = world.entity(spaceship, nullptr /* optional name */);
|
||||
|
||||
// Create child with Parent storage
|
||||
flecs::entity cockpit = world.entity(flecs::Parent{spaceship}, nullptr /* optional name */);
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C#</b>
|
||||
|
||||
```cs
|
||||
// TODO
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">Rust</b>
|
||||
|
||||
```rust
|
||||
// TODO
|
||||
```
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
A single parent can contain children that use both `ChildOf` and `Parent` hierarchies. A single entity cannot both have a `ChildOf` pair and a `Parent` component. For example:
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
ecs_entity_t spaceship = ecs_new(world);
|
||||
|
||||
// OK: single parent with children using mixed storages
|
||||
ecs_entity_t cockpit = ecs_new_w_pair(world, EcsChildOf, spaceship);
|
||||
ecs_entity_t engine = ecs_new_w_parent(world, spaceship, NULL);
|
||||
|
||||
// Not OK: single entity with both storages
|
||||
ecs_entity_t engineering = ecs_new_w_parent(world, spaceship, NULL);
|
||||
ecs_add_pair(world, engineering, EcsChildOf, spoaceship);
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
flecs::entity spaceship = world.entity();
|
||||
|
||||
// OK: single parent with children using mixed storages
|
||||
flecs::entity cockpit = world.entity(spaceship, nullptr);
|
||||
flecs::entity engine = world.entity(flecs::Parent{spaceship}, nullptr);
|
||||
|
||||
// Not OK: single entity with both storages
|
||||
flecs::entity engineering = world.entity(flecs::Parent{spaceship}, nullptr);
|
||||
engineering.child_of(spaceship); // will remove Parent component
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C#</b>
|
||||
|
||||
```cs
|
||||
// TODO
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">Rust</b>
|
||||
|
||||
```rust
|
||||
// TODO
|
||||
```
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
The behavior of `ChildOf` hierarchies and `Parent` hierarchies is mostly the same:
|
||||
|
||||
- Children are recursively deleted when their parent is deleted.
|
||||
- Queries with `ChildOf` terms work with both storages.
|
||||
- Relationship traversal works for both storages (`Position(up)` or `Position(cascade)`).
|
||||
- Name lookups work with both storages (`parent.lookup("child_name")`).
|
||||
- `ecs_get_parent` / `e.parent()` can be used to obtain a parent with both storages.
|
||||
- `ecs_children` / `e.children()` can be used to iterate children with both storages.
|
||||
- [Prefab instantiation](https://www.flecs.dev/flecs/md_docs_2PrefabsManual.html#prefab-hierarchies) works with both storages.
|
||||
- [JSON serialization](https://www.flecs.dev/flecs/md_docs_2FlecsRemoteApi.html#json-serialization) works for both storages.
|
||||
|
||||
There are some differences between `ChildOf` and `Parent` hierarchies:
|
||||
|
||||
- When using `Parent` hierarchies, the parent will not show up in the entity's type (`ecs_get_type()` / `e.type()`)
|
||||
- `Parent` hierarchies are built on top of the `OrderedChildren` feature, which means that children are stored in well defined order
|
||||
- When instantiating a prefab with a `Parent` hierarchy, instance children will inherit from the prefab children. Instantiating a prefab with a `ChildOf` hierarchy copies the prefab child components to the instance child.
|
||||
- When instantiating a prefab with a `Parent` hierarchy, the names of the prefab children are not copied over to the instance children.
|
||||
- The `Or` and `Not` [query operators](https://www.flecs.dev/flecs/md_docs_2Queries.html#operator-overview) do not yet work with `Parent` hierarchies.
|
||||
|
||||
The following two sections describe in more detail how the different hierarchy storages are implemented.
|
||||
|
||||
### ChildOf storage details
|
||||
`ChildOf` hierarchies are implemented as regular "fragmenting" Flecs relationships, where each unique `(ChildOf, parent)` pair is conceptually similar to a component. As a result the performance of `ChildOf` hierarchies is predictable, as almost all component operations have O(1) time complexity. Additionally, it also means that querying for components of children for a specific parent is fast, as components can be accessed as contiguous plain arrays. For example:
|
||||
|
||||
```cpp
|
||||
ecs_query_t *q = ecs_query(world, {
|
||||
.expr = "(ChildOf, parent), Position"
|
||||
});
|
||||
|
||||
ecs_iter_t it = ecs_query_iter(world, q);
|
||||
while (ecs_query_next(&it)) {
|
||||
Position *p = ecs_field(&it, Position, 0);
|
||||
for (int i = 0; i < it.count; i ++) {
|
||||
p[i].x ++; // Position can be accessed efficiently as array
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These benefits are most noticeable if a parent has many children, or when an application only needs to access children of a specific parent. For example, an application may load multiple scenes into memory, where only one scene is rendered and actively updated. With `ChildOf` hierarchies, children for different parents are stored separately from each other, which means that filtering out entities for a different scene can be a near zero cost operation.
|
||||
|
||||
When an application has both many different parents _and_ a need to access children of different parents simultaneously however, `ChildOf` hierarchies can become expensive. There are several factors contributing to that cost:
|
||||
|
||||
- Children for different parents will be stored in different tables (archetypes), which fragments memory access. The performance difference between a system that iterates over 1 vs. 1000 tables can be over an order of magnitude.
|
||||
- Each table consumes memory, which scales linearly with the number of components in the table.
|
||||
- Cached queries store matching tables. If an application has many tables, the memory footprint of queries will grow as well.
|
||||
- Dynamic hierarchies cause table creation & deletion which is expensive relative to the cost of creating & deleting entities. In addition, tables may have to get matched/unmatched with queries, which further increases the cost of dynamic hierarchies.
|
||||
- Relationship traversal (a query feature) under certain conditions requires rematching, which rebuilds the cache. If a query matches many tables, rematching can be costly and responsible for frame time spikes.
|
||||
|
||||
These downsides are most noticeable when an application has many parents where each parent only has a small number of children. In the degenerate case, an application ends up with a single child per table, which can happen if each child of a parent has a different set of components.
|
||||
|
||||
When deciding whether `ChildOf` hierarchies are a good fit, consider:
|
||||
|
||||
- Is the application only accessing children for a specific parent?
|
||||
- Is the number of parents reasonable (not higher than thousands)?
|
||||
- Is the memory overhead acceptable (use the statistics API or explorer dashboard to see detailed memory statistics)
|
||||
|
||||
When the answer to one or more is "no", consider using `Parent` hierarchies.
|
||||
|
||||
### Parent storage details
|
||||
`Parent` hierarchies are enabled by setting a `Parent` component with as value the parent. Unlike `ChildOf` hierarchies, children for multiple parents can be stored in the same table when using `Parent` hierarchies.. Children in a `Parent` hierarchy are stored in a `vector<ecs_entity_t>` on the component index record for `(ChildOf, parent)`. This is the same vector that is used for the `OrderedChildren` storage.
|
||||
|
||||
When a `Parent` component is set, a `(ParentDepth, depth)` pair is automatically added to the child. When inspecting the type of a child, this will show up as:
|
||||
|
||||
```
|
||||
Parent, Position, Velocity, (ParentDepth, @3)
|
||||
```
|
||||
|
||||
This means that the child is stored 3 levels deep in the hierarchy. Hierarchy depth is used by queries to efficiently implement the `cascade`/`group_by` features for `Parent` hierarchies. The `@` indicates that this is a value pair, as opposed to 3 specifying an entity id. User defined queries can also take advantage of `ParentDepth`. For example, the following query iterates entities breadth-first:
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
ecs_query_t *q = ecs_query(world, {
|
||||
.terms = {
|
||||
{ ecs_id(Position) }
|
||||
},
|
||||
.group_by = EcsParentDepth
|
||||
});
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
auto q = world.query_builder<Position>()
|
||||
.group_by(flecs::ParentDepth)
|
||||
.build();
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C#</b>
|
||||
|
||||
```cs
|
||||
// TODO
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">Rust</b>
|
||||
|
||||
```rust
|
||||
// TODO
|
||||
```
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
#### Advantages vs. ChildOf
|
||||
The main benefit of `Parent` hierarchies is that they do not significantly change the memory layout of components in the ECS storage. A query that does not interact with the hierarchy will perform the same whether entities are organized in a hierarchy or not. This can be a significant benefit over `ChildOf` hierarchies, especially if the application contains many small (prefab) hierarchies. Switching to `Parent` hierarchies can improve performance and reduce memory footprint by over an order of magnitude.
|
||||
|
||||
In addition to not fragmenting the storage, `Parent` hierarchies have additional performance benefits when used in combination with prefabs. This is because:
|
||||
|
||||
- Prefab hierarchy instantiation does not cause table creation when using `Parent` hierarchies.
|
||||
- Because no new tables are created, no tables have to be matched with queries. The more queries an application has, the larger the performance improvement will be.
|
||||
- Prefabs that use `Parent` hierarchies build a "TreeSpawner", which caches the structure and tables of the prefab hierarchy. This is not possible for `ChildOf` hierarchies, because tables are not the same across instances.
|
||||
- Instance children can inherit components from prefab children, which reduces memory footprint and copy overhead.
|
||||
|
||||
#### Disadvantages vs. ChildOf
|
||||
In most cases `Parent` hierarchies will outperform `ChildOf` hierarchies, but there are a few exceptions:
|
||||
|
||||
- Children in a `Parent` hierarchy are stored in a vector, which means that deleting a child is an O(n) operation. If a parent has many (think thousands) children,this can be an expensive operation. If this is the case, consider using a `ChildOf` hierarchy.
|
||||
- Queries with multiple terms where one is a `ChildOf` term may perform worse. This is largely due to children of multiple parents being stored in the same table, which means a query needs to do more work for `ChildOf` terms to filter out matching entities.
|
||||
|
||||
The O(n) cost of deleting children does not apply when deleting the parent. In that case children are deleted in bulk, which does not require scanning the children vector.
|
||||
|
||||
#### Query performance
|
||||
How a query will perform with the new hierarchy storage highly depends on the kind of query. This section provides an overview of the different kinds of queries, and how their performance compares to the `ChildOf` hierarchy.
|
||||
|
||||
---
|
||||
```
|
||||
Position, Velocity
|
||||
```
|
||||
Queries such as these that do not interact with the hierarchy will almost always perform better with `Parent` hierarchies. `ChildOf` hierarchies result in more tables, which decreases query performance. When using `Parent` hierarchies it is much more likely that components are densely packed in memory.
|
||||
|
||||
---
|
||||
```
|
||||
(ChildOf, my_parent)
|
||||
```
|
||||
Queries in this form will typically perform the same or better for `Parent` hierarchies. The reason for this is that the query returns the children vector of a parent directly. This means that it never has to iterate multiple tables, which is something that can occur for `ChildOf` hierarchies.
|
||||
|
||||
---
|
||||
```
|
||||
(ChildOf, my_parent), Position
|
||||
```
|
||||
This query will perform _worse_ for `Parent` hierarchies. The reason is that this query cannot be evaluated at the table level. Instead the query has to individually check whether each child of `my_parent` has `Position`.
|
||||
|
||||
---
|
||||
```
|
||||
(ChildOf, *), Position
|
||||
```
|
||||
This query will perform _worse_ for `Parent` hierarchies. The reason for this is that this query will first find all tables with either a `ChildOf` pair or a `Parent` component, and then check if the resulting table has `Position`. For tables with the `Parent` component, the query then has to return each individual entity, because it needs to return the actual pair matched by the wildcard. This is to support code that uses `ecs_field_id`, `it.id()` or `it.pair()`:
|
||||
|
||||
```cpp
|
||||
auto q = world.query_builder<Position>()
|
||||
.with(flecs::ChildOf, flecs::Wildcard)
|
||||
.each([](flecs::iter& it, size_t, Position&) {
|
||||
flecs::entity parent = it.pair().second() // value is set by query
|
||||
});
|
||||
```
|
||||
|
||||
Both storages will perform better if the terms are reversed. The first term is likely to match significantly more tables than the second term, which increases the cost of uncached queries, and the cost of populating a query cache.
|
||||
|
||||
---
|
||||
```
|
||||
Position, (ChildOf, my_parent)
|
||||
```
|
||||
This query will perform _worse_ for `Parent` hierarchies. This query will first find all tables with `Position`, and then has to check for each entity in that table whether it is a child of `my_parent`. If the table contains many entities, this can be a very expensive operation.
|
||||
|
||||
If the table only contains a single child for `my_parent`, the query uses a faster path that does not require scanning each entity, but the query will still perform worse than with `ChildOf` hierarchies.
|
||||
|
||||
---
|
||||
```
|
||||
Position, (ChildOf, *)
|
||||
```
|
||||
This query will perform _worse_ for `Parent` hierarchies. This query will first find all tables with `Position`, and then check if the table has the `Parent` component. If so, the query now knows that each entity in the table matches the query. However, it has to return entities one by one because the matched parent needs to be communicated to the application (see above).
|
||||
|
||||
For a more efficient way to do this with `Parent` hierarchies, see the next query:
|
||||
|
||||
---
|
||||
```
|
||||
Position, (ChildOf, _)
|
||||
```
|
||||
This query will perform the same for `Parent` hierarchies. This query will first find all tables with `Position`, and then check if the table has the `Parent` component. If so, the query now knows that each entity in the table matches the query. Because the any (`_`) wildcard does not require communicating the parent to the application, the entire table can be returned.
|
||||
|
||||
To also find the entities' parent, an application can add the `flecs::Parent` component to the query:
|
||||
|
||||
```cpp
|
||||
auto q = world.query_builder<Position, const flecs::Parent>()
|
||||
.with(flecs::ChildOf, flecs::Wildcard)
|
||||
.each([](flecs::iter& it, size_t, Position&, const flecs::Parent& p) {
|
||||
flecs::entity parent = it.world().entity(p.value);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
```
|
||||
Position, Position(up)
|
||||
```
|
||||
This query will perform _worse_ for `Parent` queries. This query will first find all tables with `Position`. If it finds a table with a `Parent` component (as opposed to one with a `ChildOf` pair) the query will have to iterate each entity, and traverse the hierarchy upwards for that entity. The result of this cannot be cached, which further hurts performance.
|
||||
|
||||
The reason up traversal is supported for `Parent` hierarchies is mostly to provide an easier migration path from `ChildOf` hierarchies. Performance critical queries should generally not use up traversal in combination with `Parent` hierarchies. See below on how to migrate to alternative queries.
|
||||
|
||||
---
|
||||
```
|
||||
Position(up), Position
|
||||
```
|
||||
This query will perform _worse_ for `Parent` queries. Though not as bad as the previous query, this query will also perform worse with `Parent` hierarchies than for `ChildOf` hierarchies, because the results that use the `Parent` hierarchy cannot be cached.
|
||||
|
||||
#### Optimizing Relationship Traversal
|
||||
As outlined in the previous section, queries that use relationship traversal can be slower for entities with `Parent` hierarchies. To get around this, an application can split up a query that uses relationship traversal into two queries, one for `ChildOf` hierarchies, and one for `Parent` hierarchies. Consider the following query:
|
||||
|
||||
```cpp
|
||||
Position, Position(up)
|
||||
```
|
||||
|
||||
This can be split up into two queries like so:
|
||||
|
||||
```cpp
|
||||
// Query for ChildOf hierarchies: uses relationship traversal
|
||||
Position, Position(up), !Parent
|
||||
|
||||
// Query for Parent hierarchies: does not use relationship traversal
|
||||
Position, Parent
|
||||
```
|
||||
|
||||
Then in the iteration logic, manually fetch the `Position` component from the parent. For example:
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
ecs_query_t *q = ecs_query(world, {
|
||||
.terms = {
|
||||
{ ecs_id(Position) },
|
||||
{ ecs_id(EcsParent) }
|
||||
},
|
||||
.cache_kind = EcsQueryCacheAuto
|
||||
});
|
||||
|
||||
ecs_iter_t it = ecs_query_iter(world, q);
|
||||
while (ecs_query_next(&it)) {
|
||||
Position *p = ecs_field(&it, Position, 0);
|
||||
EcsParent *parents = ecs_field(&it, EcsParent, 1);
|
||||
|
||||
for (int i = 0; i < it.count; i ++) {
|
||||
const Position *p_parent = ecs_get(world, parents[i].value, Position);
|
||||
// logic as usual
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
auto q = world.query<Position, const flecs::Parent>();
|
||||
|
||||
q.each([](Position& p, const flecs::Parent& parent) {
|
||||
const Position& p_parent = parent.get<Position>();
|
||||
// logic as usual
|
||||
});
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C#</b>
|
||||
|
||||
```cs
|
||||
// TODO
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">Rust</b>
|
||||
|
||||
```rust
|
||||
// TODO
|
||||
```
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
For queries that use `cascade`, applications can use `group_by(flecs::ParentDepth)` instead, which will iterate entities ordered by hierarchy depth. The following code shows how to do breadth-first traversal with a query per storage kind. Note how the implementation iterates each depth individually; this prevents deeper `ChildOf` levels from being processed before higher-level `Parent` entities.
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
ecs_query_t *q_childof = ecs_query(world, {
|
||||
.terms = {
|
||||
{ ecs_id(Position) },
|
||||
{ ecs_id(Position), .src.id = EcsCascade },
|
||||
{ ecs_id(EcsParent), .oper = EcsNot }
|
||||
},
|
||||
.cache_kind = EcsQueryCacheAuto
|
||||
});
|
||||
|
||||
ecs_query_t *q_parent = ecs_query(world, {
|
||||
.terms = {
|
||||
{ ecs_id(Position) },
|
||||
{ ecs_id(EcsParent) }
|
||||
},
|
||||
.group_by = EcsParentDepth,
|
||||
.cache_kind = EcsQueryCacheAuto
|
||||
});
|
||||
|
||||
// Replace 16 with max hierarchy depth in application
|
||||
for (int depth = 0; depth < 16; depth ++) {
|
||||
// Iterate ChildOf hierarchy at current depth
|
||||
{
|
||||
ecs_iter_t it = ecs_query_iter(world, q_childof);
|
||||
ecs_iter_set_group(&it, depth);
|
||||
while (ecs_query_next(&it)) {
|
||||
Position *p = ecs_field(&it, Position, 0);
|
||||
Position *p_parent = ecs_field(&it, Position, 1);
|
||||
for (int i = 0; i < it.count; i ++) {
|
||||
p[i].x += p_parent->x;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate Parent hierarchy at current depth
|
||||
{
|
||||
ecs_iter_t it = ecs_query_iter(world, q_parent);
|
||||
ecs_iter_set_group(&it, depth);
|
||||
while (ecs_query_next(&it)) {
|
||||
Position *p = ecs_field(&it, Position, 0);
|
||||
EcsParent *parents = ecs_field(&it, EcsParent, 1);
|
||||
for (int i = 0; i < it.count; i ++) {
|
||||
const Position *p_parent = ecs_get(
|
||||
world, parents[i].value, Position);
|
||||
p[i].x += p_parent->x;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
auto q_childof = world.query_builder<Position, const Position>()
|
||||
.term_at(1).cascade()
|
||||
.without<flecs::Parent>()
|
||||
.build();
|
||||
|
||||
auto q_parent = world.query_builder<Position, const flecs::Parent>()
|
||||
.group_by(flecs::ParentDepth)
|
||||
.build();
|
||||
|
||||
// Replace 16 with max hierarchy depth in application
|
||||
for (int depth = 0; depth < 16; depth ++) {
|
||||
q_childof.set_group(depth)
|
||||
.each([](Position& p, const Position& p_parent) {
|
||||
p.x += p_parent.x;
|
||||
});
|
||||
|
||||
q_parent.set_group(depth)
|
||||
.each([](Position& p, const flecs::Parent& parent) {
|
||||
const Position& p_parent = parent.get<Position>();
|
||||
p.x += p_parent.x;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C#</b>
|
||||
|
||||
```cs
|
||||
// TODO
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">Rust</b>
|
||||
|
||||
```rust
|
||||
// TODO
|
||||
```
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,337 @@
|
||||
# Manual
|
||||
|
||||
## Introduction
|
||||
Nobody likes to read manuals, and you should be able to get up and running with Flecs by using the quickstart, by looking at examples and by checking the documentation in the flecs header files. However, if you truly want to know how something works, or why it works that way, the manual is the right place to go. With that said, the manual is not exhaustive, and it complements the other sources of documentation.
|
||||
|
||||
## Design Goals
|
||||
|
||||
### 1. Performance
|
||||
Flecs is designed from the ground up to provide blazing fast iteration speeds in systems that can be vectorized by default, while minimizing cache misses. In addition, Flecs has a unique graph-based storage engine that allows for extremely fast add, remove and bulk operations. These features, amongst others, ensure that applications can get the most out of the underlying hardware.
|
||||
|
||||
### 2. Portability
|
||||
Flecs has been implemented in C99 and features an external interface that is C89 compatible to ensure it is portable to a wide range of platforms. The framework contains a flexible operating system abstraction API that enables an application to easily port the library to new platforms.
|
||||
|
||||
### 3. Reusability
|
||||
ECS has the potential for being a platform for the development of reusable, loosely coupled, plug and play features like input, physics and rendering. Flecs modules enable such features to be packaged in a loosely coupled way so that applications can simply import them, while guaranteeing a correct execution order. In addition, Flecs has features like time management that ensure a consistent baseline across modules.
|
||||
|
||||
### 4. Usability
|
||||
Flecs is designed first and foremost to be a framework that simplifies the development of games and simulations. Rather than just providing a vanilla ECS implementation, Flecs provides many features that are commonly found in game development frameworks such as hierarchies, prefabs and time management, all integrated seamlessly with the core ECS system.
|
||||
|
||||
### 5. Extensibility
|
||||
Flecs is used with other frameworks and game engines, and as such not all of its features are useful in each application. For that reason Flecs has a modular design, so that applications can easily remove features from the core that they do not need. Additionally, since many features are built on top of the ECS core, applications can easily extend or reimplement them.
|
||||
|
||||
### 6. Have fun!
|
||||
There are few things as satisfying as building games. If nothing else, Flecs has been built to enable creative visions both big and small. I'm having a lot of fun building Flecs, I hope you will have fun using it, and that your users will have fun playing your games :)
|
||||
|
||||
## API design
|
||||
|
||||
### Naming conventions
|
||||
|
||||
```c
|
||||
// Component names ('Position') use PascalCase
|
||||
typedef struct Position {
|
||||
float x;
|
||||
float y; // Component members ('y') use snake_case
|
||||
} Position;
|
||||
|
||||
typedef struct Velocity {
|
||||
float x;
|
||||
float y;
|
||||
} Velocity;
|
||||
|
||||
// System names ('Move') use PascalCase. API types use snake_case_t
|
||||
void Move(ecs_iter_t *it) {
|
||||
// Functions use snake_case
|
||||
Position *p = ecs_field(it, Position, 0);
|
||||
Velocity *v = ecs_field(it, Velocity, 1);
|
||||
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
p[i].x += v[i].x;
|
||||
p[i].y += v[i].y;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
ecs_world_t *world = ecs_init();
|
||||
|
||||
// Declarative function-style macros use SCREAMING_SNAKE_CASE
|
||||
ECS_COMPONENT(world, Position);
|
||||
ECS_COMPONENT(world, Velocity);
|
||||
|
||||
// Module names are PascalCase
|
||||
ECS_IMPORT(world, MyModule);
|
||||
|
||||
// Enumeration constants ('EcsOnUpdate') use PascalCase
|
||||
ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity);
|
||||
|
||||
// Function wrapper macros use snake_case
|
||||
ecs_entity_t e = ecs_new(world);
|
||||
|
||||
// Builtin entities use PascalCase
|
||||
ecs_add(world, EcsWorld, Position);
|
||||
|
||||
return ecs_fini(world);
|
||||
}
|
||||
```
|
||||
|
||||
### Idempotence
|
||||
Many operations in the Flecs API are idempotent, meaning that invoking an operation once has the same effect as invoking an operation multiple times with the same parameters. For example:
|
||||
|
||||
```c
|
||||
ecs_add(world, e, Position);
|
||||
```
|
||||
|
||||
Has the same effect as:
|
||||
|
||||
```c
|
||||
ecs_add(world, e, Position);
|
||||
ecs_add(world, e, Position);
|
||||
```
|
||||
|
||||
This simplifies application code as it can be written in a declarative style, where the only thing that matters is that after the operation has been invoked, the post condition of the operation is satisfied.
|
||||
|
||||
Some operations are idempotent but have side effects, like `ecs_set`:
|
||||
|
||||
```c
|
||||
ecs_set(world, e, Position, {10, 20});
|
||||
ecs_set(world, e, Position, {10, 20});
|
||||
```
|
||||
|
||||
The effect of invoking this operation once is the same as invoking the operation multiple times, but both invocations can invoke an OnSet observer which can introduce side effects.
|
||||
|
||||
All declarative macros (`ECS_COMPONENT`, `ECS_SYSTEM`, ...) are idempotent:
|
||||
|
||||
```c
|
||||
{
|
||||
ECS_COMPONENT(world, Position);
|
||||
}
|
||||
{
|
||||
ECS_COMPONENT(world, Position);
|
||||
}
|
||||
```
|
||||
|
||||
The second time the `ECS_COMPONENT` macro is evaluated, the first instance will be found and returned. Note that because these macros may declare variables, they cannot be defined twice in the same C scope.
|
||||
|
||||
### Error handling
|
||||
As a result of the idempotent design of many operations, the API has a very small error surface. There are essentially two conditions under which an operation is unable to fulfill its postcondition:
|
||||
|
||||
- The application provides invalid inputs to an operation
|
||||
- The operating system is unable to fulfill a request, like a failure to allocate memory
|
||||
|
||||
When either of those conditions occur, the library will throw an assertion in debug mode (the source is not compiled with `NDEBUG`). Except for errors caused by the OS, errors are almost always caused by the invocation of a single operation, which makes applications easy to debug.
|
||||
|
||||
This approach has several advantages. Application code does not need to check for errors. If an error occurs, the assertion will cause application execution to halt. As a result of this, application code is cleaner and more robust, as it is impossible to forget to handle an error condition.
|
||||
|
||||
### Memory ownership
|
||||
Most of the API is handle based, as many API constructs are implemented using entities. There are a few instances where an application will interface with memory managed by the framework, or when an application needs to provide memory it manages to the API. In these scenarios there are four rules:
|
||||
|
||||
- If an operation accepts a `const T*`, the application retains ownership of the memory
|
||||
- If an operation accepts a `T*`, ownership is transferred from application to framework
|
||||
- If an operation returns a `const T*`, the framework retains ownership of the memory
|
||||
- If an operation returns a `T*`, ownership is transferred from framework to application
|
||||
|
||||
The `ecs_get_name` operation is an example where the framework retains ownership:
|
||||
|
||||
```c
|
||||
const char *name = ecs_get_name(world, e);
|
||||
```
|
||||
|
||||
The `ecs_get_path` operation is an example where the ownership is transferred to the application:
|
||||
|
||||
```c
|
||||
char *path = ecs_get_path(world, e);
|
||||
```
|
||||
|
||||
Memory for which ownership has been transferred to the application will need to be freed by the application. This should be done by the `ecs_os_free` operation:
|
||||
|
||||
```c
|
||||
ecs_os_free(path);
|
||||
```
|
||||
|
||||
### Macros
|
||||
The C99 API heavily relies on function-style macros, probably more than you would see in other libraries. The number one reason for this is that an ECS framework needs to work with user-defined types, and C does not provide out of the box support for generics. A few strategies have been employed in the API to improve its overall ergonomics, type safety and readability. Let's start with a simple example:
|
||||
|
||||
```c
|
||||
typedef struct Position {
|
||||
float x;
|
||||
float y;
|
||||
} Position;
|
||||
|
||||
ECS_COMPONENT(world, Position);
|
||||
|
||||
ecs_entity_t e = ecs_new_w(world, Position);
|
||||
```
|
||||
|
||||
From a readability perspective this code looks fine as we can easily tell what is happening here. Though if we take a closer look, we can see that a typename is used where we expect an expression, and that is not possible in plain C. So what is going on?
|
||||
|
||||
Let's first remove the `ECS_COMPONENT` macro and replace it with equivalent code (details are omitted for brevity):
|
||||
|
||||
```c
|
||||
ecs_entity_t ecs_id(Position) = ecs_component_init(world, &(ecs_component_desc_t){
|
||||
.entity.name = "Position",
|
||||
.size = sizeof(Position),
|
||||
.alignment = ECS_ALIGNOF(Position)
|
||||
});
|
||||
```
|
||||
|
||||
The first line actually registers the component with Flecs, and captures its name and size. The result is stored in a variable with name `ecs_id(Position)`. Here, `ecs_id` is a macro that translates the typename of the component to a variable name. The actual name of the variable is:
|
||||
|
||||
```c
|
||||
FLECS__EPosition
|
||||
```
|
||||
|
||||
ECS operations that accept a typename, such as `ecs_get` will look for the `FLECS__E` variable:
|
||||
```c
|
||||
Position *p = ecs_get(world, e, Position);
|
||||
```
|
||||
|
||||
Translates into:
|
||||
|
||||
```c
|
||||
Position *p = (Position*)ecs_get_id(world, e, ecs_id(Position));
|
||||
```
|
||||
|
||||
As you can see, the `ecs_get` macro casts the result of the function to the correct type, so a compiler will throw a warning when an application tries to assign the result of the operation to a variable of the wrong type.
|
||||
|
||||
Similarly, `ecs_set` is a macro that ensures that anything we pass into it is of the right type:
|
||||
|
||||
```c
|
||||
ecs_set(world, e, Position, {10, 20});
|
||||
```
|
||||
|
||||
Translates into:
|
||||
|
||||
```c
|
||||
ecs_set_id
|
||||
(world, e, ecs_id(Position), sizeof(Position),
|
||||
&(Position){10, 20});
|
||||
```
|
||||
|
||||
In addition to casting the value to the right type and passing in the component, this macro also captures the size of the type, which saves Flecs from having to do a component data lookup.
|
||||
|
||||
Understanding how the macros work will go a long way in being able to write effective code in Flecs, and will lead to less surprises when debugging the code.
|
||||
|
||||
## Modules
|
||||
Modules allow an application to split up systems and components into separate decoupled units. The purpose of modules is to make it easier to organize systems and components for large projects. Additionally, modules also make it easier to split off functionality into separate compilation units.
|
||||
|
||||
A module consists out of a couple parts:
|
||||
|
||||
- The declaration of the components, tags, and systems of the module contents
|
||||
- An import function that loads the module contents for a world
|
||||
|
||||
The declaration of the module contents module is typically located in a separate module header file, and look like this for a module named "Vehicles":
|
||||
|
||||
```c
|
||||
typedef struct Car {
|
||||
float speed;
|
||||
} Car;
|
||||
|
||||
typedef struct Bus {
|
||||
float speed;
|
||||
} Bus;
|
||||
|
||||
typedef struct MotorCycle {
|
||||
float speed;
|
||||
} MotorCycle;
|
||||
|
||||
/* Components are declared with ECS_COMPONENT_DECLARE */
|
||||
extern ECS_COMPONENT_DECLARE(Car);
|
||||
extern ECS_COMPONENT_DECLARE(Bus);
|
||||
extern ECS_COMPONENT_DECLARE(MotorCycle);
|
||||
|
||||
/* Tags are declared with ECS_DECLARE */
|
||||
extern ECS_DECLARE(Moving);
|
||||
|
||||
/* Systems are declared with ECS_SYSTEM_DECLARE */
|
||||
extern ECS_SYSTEM_DECLARE(Move);
|
||||
|
||||
/* Forward declaration to the import function */
|
||||
/* The function name has to follow the convention: <ModuleName>Import */
|
||||
void VehiclesImport(ecs_world_t *world);
|
||||
```
|
||||
|
||||
The import function for this module is typically located in a module source file, and would look like this:
|
||||
|
||||
```c
|
||||
void VehiclesImport(ecs_world_t *world) {
|
||||
/* Define the module */
|
||||
ECS_MODULE(world, Vehicles);
|
||||
|
||||
/* Declare components, tags, systems, and assign them to the module with their respective _DEFINE macros */
|
||||
ECS_COMPONENT_DEFINE(world, Car);
|
||||
ECS_COMPONENT_DEFINE(world, Bus);
|
||||
ECS_COMPONENT_DEFINE(world, MotorCycle);
|
||||
ECS_TAG_DEFINE(world, Moving);
|
||||
ECS_SYSTEM_DEFINE(world, Move, EcsOnUpdate, Car, Moving);
|
||||
}
|
||||
```
|
||||
|
||||
After the module has been defined, it can be imported in an application like this:
|
||||
|
||||
```c
|
||||
ecs_world_t *world = ecs_init();
|
||||
|
||||
/* Import module, which invokes the module import function */
|
||||
ECS_IMPORT(world, Vehicles);
|
||||
|
||||
/* The module contents can now be used */
|
||||
ecs_entity_t e = ecs_new_w(world, Car);
|
||||
```
|
||||
|
||||
Module contents are namespaced, which means that the identifiers of the content of the module (components, tags, systems) are stored in the scope of the module. For the above example module, everything would be stored in the `vehicles` scope. To resolve the `Car` component by name, an application would have to do:
|
||||
|
||||
```c
|
||||
ecs_entity_t car_entity = ecs_lookup(world, "vehicles.Car");
|
||||
```
|
||||
|
||||
Note that even though the module name is specified with uppercase, the name is stored with lowercase. This is because the naming convention for modules in C is PascalCase, whereas the stored identifiers use snake_case. If a module name contains several uppercase letters, this will be translated to a nested module. For example, the C module name `MySimpleModule` will be translated to `my.simple.module`.
|
||||
|
||||
### Modules in C++
|
||||
A module in C++ is defined as a class where the module contents are defined in the constructor. The above Vehicles module would look like this in C++:
|
||||
|
||||
```cpp
|
||||
/* In C++ tags can be defined as empty structs */
|
||||
struct Moving { };
|
||||
|
||||
/* Module implementation */
|
||||
struct vehicles {
|
||||
vehicles(flecs::world& world) {
|
||||
world.component<Car>();
|
||||
world.component<Bus>();
|
||||
world.component<MotorCycle>();
|
||||
world.component<Moving>();
|
||||
|
||||
world.system<Car, Moving>("Move")
|
||||
.each([](flecs::entity e, Car& car, Moving) {
|
||||
/* System implementation */
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An application can import the module in C++ like this:
|
||||
|
||||
```cpp
|
||||
flecs::world world;
|
||||
world.import<vehicles>();
|
||||
```
|
||||
|
||||
## Deferred operations
|
||||
Applications can defer entity with the `ecs_defer_begin` and `ecs_defer_end` functions. This records all operations that happen inside the begin - end block, and executes them when `ecs_defer_end` is called. Deferred operations are useful when an application wants to make modifications to an entity while iterating, as doing this without deferring an operation could modify the underlying data structure. An example:
|
||||
|
||||
```c
|
||||
ecs_defer_begin(world);
|
||||
ecs_entity_t e = ecs_new(world);
|
||||
ecs_add(world, e, Position);
|
||||
ecs_set(world, e, Velocity, {1, 1});
|
||||
ecs_defer_end(world);
|
||||
```
|
||||
|
||||
The effects of these operations will not be visible until the `ecs_defer_end` operation.
|
||||
|
||||
There are a few things to keep in mind when deferring:
|
||||
- creating a new entity will always return a new id which increases the last used id counter of the world
|
||||
- `ecs_ensure` returns a pointer initialized with the current component value, and does not take into account deferred set or ensure operations
|
||||
- if an operation is called on an entity which was deleted while deferred, the operation will ignored by `ecs_defer_end`
|
||||
- if a child entity is created for a deleted parent while deferred, the child entity will be deleted by `ecs_defer_end`
|
||||
|
||||
@@ -0,0 +1,542 @@
|
||||
# Migration guide
|
||||
This guide will help migrating Flecs v3 code bases over to Flecs v4. While the guide attempts to be as complete as possible, v4 is a big release, so some things are inevitably missed. If you see something that you think belongs in the migration guide, feel free to create a PR!
|
||||
|
||||
Note that this is _not_ a comprehensive list of things that changed in v4. This document only intends to document the breaking changes between v3 and v4.
|
||||
|
||||
## Queries
|
||||
The three query implementations in v3 (`filter`, `query`, `rule`) have been merged into a single `query` API in v4. This means that any usage of `filter` and `rule` should now be replaced with `query`.
|
||||
|
||||
Additionally the following things have changed:
|
||||
|
||||
- **Query fields now start from 0 instead of 1(!)**
|
||||
- `ecs_term_id_t` is now called `ecs_term_ref_t`.
|
||||
- Term ref flags (such as `self`, `up`) are now applied with a bitwise OR mask to `ecs_term_ref_t::id`.
|
||||
- The `trav` field has been moved from `ecs_term_ref_t` to `ecs_term_t`.
|
||||
- When query traversal flags such as `up` are provided, the traversal relationship defaults to `ChildOf` (this used to be `IsA`).
|
||||
- If no traversal flags are provided, the default is still `self|up(IsA)` for inheritable components.
|
||||
- The `ecs_query_desc_t::instanced` member no longer exists. Instancing must be specified with a query flag (`.flags = EcsQueryIsInstanced`).
|
||||
- Stack-allocated query (filter) objects are no longer supported.
|
||||
- It is no longer possible to provide user-allocated term arrays. The max number of terms is now 32 and can be configured with `FLECS_TERM_COUNT_MAX`.
|
||||
- The `ecs_query_changed` function has been split up into a function that only accepts a query (`ecs_query_changed`) and one that only accepts an iterator (`ecs_iter_changed`).
|
||||
- The `ecs_query_skip` function has been renamed to `ecs_iter_skip`.
|
||||
- The `ecs_query_set_group` function has been renamed to `ecs_iter_set_group`.
|
||||
- The values in the `ecs_iter_t::columns` array have changed, and may change again in the near future. Applications should not directly depend on it.
|
||||
- The `EcsParent` convenience constant is gone, and can now be replaced with just `EcsUp`.
|
||||
- `ecs_query_desc_t::group_by_id` has been renamed to `ecs_query_desc_t::group_by`
|
||||
- `ecs_query_desc_t::group_by` has been renamed to `ecs_query_desc_t::group_by_callback`
|
||||
- `ecs_query_desc_t::order_by_component` has been renamed to `ecs_query_desc_t::order_by`
|
||||
- `ecs_query_desc_t::order_by` has been renamed to `ecs_query_desc_t::order_by_callback`
|
||||
- `ecs_query_desc_t::filter` no longer exists, its fields (where applicable) have been moved to `ecs_query_desc_t`.
|
||||
- The `ecs_query_next_table` and `ecs_query_populate` functions have been removed.
|
||||
- The `ecs_query_table_count` and `ecs_query_empty_table_count` functions have been replaced with `ecs_query_count`, which now returns a struct.
|
||||
- The `ecs_query_t` struct is now public, which means that many of the old accessor functions (like `ecs_query_get_ctx`, `ecs_query_get_binding_ctx`) are no longer necessary.
|
||||
- The subquery feature has been removed.
|
||||
- `ecs_query_fini()` won't be called automatically for uncached queries on world finalization.
|
||||
|
||||
A before/after example:
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
// v3
|
||||
ecs_filter_t *q = ecs_filter(world, {
|
||||
.terms = {
|
||||
{ .id = ecs_id(Position) }
|
||||
{ .id = ecs_id(Gravity), .src.id = Game, .src.flags = EcsSelf|EcsUp }
|
||||
}
|
||||
});
|
||||
|
||||
ecs_iter_t it = ecs_filter_iter(world, q);
|
||||
while (ecs_filter_next(&it)) {
|
||||
Position *p = ecs_field(&it, Position, 1);
|
||||
Gravity *g = ecs_field(&it, Gravity, 2);
|
||||
|
||||
for (int i = 0; i < it.count; i ++) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
ecs_filter_fini(q);
|
||||
```
|
||||
|
||||
```c
|
||||
// v4
|
||||
ecs_query_t *q = ecs_query(world, {
|
||||
.terms = {
|
||||
{ .id = ecs_id(Position) }
|
||||
{ .id = ecs_id(Gravity), .src.id = Game|EcsSelf|EcsUp }
|
||||
}
|
||||
});
|
||||
|
||||
ecs_iter_t it = ecs_query_iter(world, f);
|
||||
while (ecs_query_next(&it)) {
|
||||
Position *p = ecs_field(&it, Position, 0);
|
||||
Gravity *g = ecs_field(&it, Gravity, 1);
|
||||
|
||||
for (int i = 0; i < it.count; i ++) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
ecs_query_fini(q);
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
// v3
|
||||
auto q = world.filter_builder<Position, Gravity>()
|
||||
.term_at(2).src(Game).self().up()
|
||||
.build();
|
||||
|
||||
q.each([](Position& p, Gravity& v) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
```cpp
|
||||
// v4
|
||||
auto q = world.query_builder<Position, Gravity>()
|
||||
.term_at(1).src(Game).self().up()
|
||||
.build();
|
||||
|
||||
q.each([](Position& p, Gravity& v) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
### Caching
|
||||
In v3, a `query` was always cached. In v4, by default queries are created _uncached_. There are three conditions under which queries become cached:
|
||||
|
||||
- When a cache kind is configured that specifies caching
|
||||
- When the query is associated with an entity
|
||||
- When `order_by`, `group_by` or `cascade` is used
|
||||
|
||||
System queries are cached by default (they are always associated with the system entity). See the query manual for more details. The following example shows the difference between creating a cached query in v3 and v4:
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
// v3
|
||||
ecs_query_t *q = ecs_query(world, {
|
||||
.filter.terms = {
|
||||
{ .id = ecs_id(Position) },
|
||||
{ .id = ecs_id(Velocity) },
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
```c
|
||||
// v4
|
||||
|
||||
// Create cached query with cache kind
|
||||
ecs_query_t *q = ecs_query(world, {
|
||||
.terms = {
|
||||
{ .id = ecs_id(Position) },
|
||||
{ .id = ecs_id(Velocity) },
|
||||
},
|
||||
.cache_kind = EcsQueryCacheAuto // cache terms that are cacheable
|
||||
});
|
||||
|
||||
// Create cached query with associated entity
|
||||
ecs_query_t *q = ecs_query(world, {
|
||||
.entity = ecs_entity(world, { .name = "MyQuery" }),
|
||||
.terms = {
|
||||
{ .id = ecs_id(Position) },
|
||||
{ .id = ecs_id(Velocity) },
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
// v3
|
||||
auto q = world.query<Position, Velocity>();
|
||||
```
|
||||
|
||||
```cpp
|
||||
// v4
|
||||
|
||||
// Create cached query with cache kind
|
||||
auto q = world.query_builder<Position, Velocity>()
|
||||
.cache_kind(flecs::QueryCacheAuto)
|
||||
.build();
|
||||
|
||||
// Create cached query with associated entity
|
||||
auto q = world.query<Position, Velocity>("MyQuery");
|
||||
```
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
### Iter function
|
||||
In C++, the `iter` iteration function no longer exists, and the signature of the `run` function has changed. An example:
|
||||
|
||||
```cpp
|
||||
// v3
|
||||
auto q = world.query<Position>();
|
||||
|
||||
q.iter([](flecs::iter& it, Position *p) {
|
||||
for (auto i : it) {
|
||||
p[i].x ++;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
```cpp
|
||||
// v4
|
||||
auto q = world.query<Position>();
|
||||
|
||||
q.run([](flecs::iter& it) {
|
||||
while (it.next()) {
|
||||
auto p = it.field<Position>(0);
|
||||
for (auto i : it) {
|
||||
p[i].x ++;
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Query DSL
|
||||
The query DSL has remained mostly the same but there are a few small changes.
|
||||
|
||||
Traversal flags are specified differently in v4:
|
||||
|
||||
```c
|
||||
// v3
|
||||
Gravity(game:self|up(ChildOf))
|
||||
```
|
||||
```c
|
||||
// v4
|
||||
Gravity(game|self|up ChildOf)
|
||||
```
|
||||
|
||||
Component id flags are now specified with lower case:
|
||||
|
||||
```c
|
||||
// v3
|
||||
OVERRIDE | Position
|
||||
```
|
||||
```c
|
||||
// v4
|
||||
auto_override | Position
|
||||
```
|
||||
|
||||
In v4 the `$this` variable must be specified with a lower case:
|
||||
|
||||
```c
|
||||
// v3
|
||||
Position($This)
|
||||
```
|
||||
```c
|
||||
// v4
|
||||
Position($this)
|
||||
```
|
||||
|
||||
The `parent` traversal flag has been removed. Because `ChildOf` is now the default relationship when a traversal flag is specified, it can be replaced with just `up`:
|
||||
|
||||
```c
|
||||
// v3
|
||||
Position(parent)
|
||||
```
|
||||
```c
|
||||
// v4
|
||||
Position(up)
|
||||
```
|
||||
|
||||
The `$(Relationship)` notation was removed from the DSL in v4:
|
||||
```c
|
||||
// v3
|
||||
$(Movement)
|
||||
```
|
||||
```c
|
||||
// v4
|
||||
(Movement, $Movement)
|
||||
```
|
||||
|
||||
## Systems & Pipelines
|
||||
Systems have remained mostly the same. A list of the things that changed:
|
||||
|
||||
- `ecs_readonly_begin` got a new `multi_threaded` parameter (added in 3.2.12)
|
||||
- `no_readonly` has been renamed to `immediate`
|
||||
|
||||
## Observers
|
||||
Observers have remained mostly the same. A list of the things that changed:
|
||||
|
||||
- The `UnSet` event no longer exists. `OnRemove` can in v4 be used instead.
|
||||
- The `EcsIterable` component/iterable interface no longer exist. `yield_existing` is now only supported for builtin events.
|
||||
- The observer `run` callback now behaves like a regular system runner and no longer requires calling `ecs_observer_default_run_action`. To get the old behavior where `run` is called before the observer query is evaluated, specify the `EcsObserverBypassQuery` observer flag.
|
||||
|
||||
## Term iterators
|
||||
Term iterators have been removed from v4, and have been replaced with the easier to use `ecs_each` API. An example:
|
||||
|
||||
```c
|
||||
// v3
|
||||
ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t){ .id = ecs_id(Position) });
|
||||
while (ecs_term_next(&it)) {
|
||||
Position *p = ecs_field(&it, Position, 1);
|
||||
for (int i = 0; i < it.count; i ++) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```c
|
||||
// v4
|
||||
ecs_iter_t it = ecs_each(world, Position);
|
||||
while (ecs_each_next(&it)) {
|
||||
Position *p = ecs_field(&it, Position, 0);
|
||||
for (int i = 0; i < it.count; i ++) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Entity Names
|
||||
Entities can now be created with names that are numbers, e.g. `1000`. Lookups for numbers now result in a regular by name lookup for an entity with the number as name. To lookup an entity with a specific id using the lookup functions, use the `#` prefix. An example:
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
// v3
|
||||
ecs_entity_t e = ecs_lookup(world, "1000");
|
||||
```
|
||||
|
||||
```c
|
||||
// v4
|
||||
ecs_entity_t e = ecs_lookup(world, "#1000");
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
// v3
|
||||
flecs::entity e = world.lookup("1000");
|
||||
```
|
||||
|
||||
```cpp
|
||||
// v4
|
||||
flecs::entity e = world.lookup("#1000");
|
||||
```
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Additionally, entity name functions have changed:
|
||||
|
||||
- `ecs_get_fullpath` is now `ecs_get_path`
|
||||
- `ecs_get_path` is now `ecs_get_path_from`
|
||||
- `ecs_lookup_fullpath` is now `ecs_lookup`
|
||||
- `ecs_lookup_path` is now `ecs_lookup_from`
|
||||
|
||||
## ensure / get_mut
|
||||
These changes got introduced in 3.2.12, but are mentioned here as many users will likely see these changes first when upgrading to v4:
|
||||
|
||||
- `ensure` is renamed to `make_alive`
|
||||
- `ensure_id` is renamed to `make_alive_id`
|
||||
- the return type of `get_mut` in the C++ API is changed from a T* to a T&
|
||||
- `get_mut` is renamed to `ensure`
|
||||
- a new `get_mut` function is added that doesn't add the component
|
||||
|
||||
## ecs_new
|
||||
The following changes were made to the `new` family of functions in the C API:
|
||||
|
||||
- `ecs_new(world, T)` got renamed to `ecs_new_w`
|
||||
- `ecs_new_id` got renamed to `ecs_new`
|
||||
- `ecs_new_entity` no longer exists, use `ecs_entity(world, { .name = "EntityName" })` instead.
|
||||
|
||||
In v3, `ecs_new` took into account values set by `ecs_set_scope` and `ecs_set_with`. In v4 this is no longer the case, and the `ecs_new_w` function will only return the entity with the specified component. To get the old behavior that takes into account scope and with, use `ecs_entity_init`.
|
||||
|
||||
## Flecs Script
|
||||
The Flecs script syntax changed how components and entities are specified:
|
||||
|
||||
```c
|
||||
// v3
|
||||
ent {
|
||||
- Position{10, 20}
|
||||
child
|
||||
}
|
||||
```
|
||||
```c
|
||||
// v4
|
||||
ent {
|
||||
Position: {10, 20}
|
||||
child {}
|
||||
}
|
||||
```
|
||||
|
||||
For more details, see the [Flecs script manual](FlecsScript.md).
|
||||
|
||||
## Inheritance
|
||||
In v4, components no longer inherit by default when instantiating a prefab. Instead, components by default override, which is equivalent to how auto overriding worked in v3. To inherit a component, add the `(OnInstantiate, Inherit)` trait to the component.
|
||||
|
||||
The `EcsDontInherit` v3 trait is equivalent to the `(OnInstantiate, DontInherit)` v4 trait. The `EcsAlwaysOverride` v3 trait is now the default in v4. It can also be explicitly specified as `(OnInstantiate, Override)`.
|
||||
|
||||
An example:
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
// v3
|
||||
ecs_entity_t p = ecs_new_prefab(world, "SpaceShip");
|
||||
ecs_set(world, p, MaxSpeed, {100});
|
||||
ecs_set(world, p, Damage, {0});
|
||||
ecs_override(world, p, Damage);
|
||||
|
||||
ecs_entity_t i = ecs_new_w_pair(world, EcsIsA, p);
|
||||
```
|
||||
|
||||
```c
|
||||
// v4
|
||||
ecs_add_pair(world, ecs_id(MaxSpeed), EcsOnInstantiate, EcsInherit);
|
||||
|
||||
ecs_entity_t p = ecs_entity(world, {
|
||||
.name = "SpaceShip", .add = ecs_ids(EcsPrefab) });
|
||||
ecs_set(world, p, MaxSpeed, {100});
|
||||
ecs_set(world, p, Damage, {0});
|
||||
|
||||
ecs_entity_t i = ecs_new_w_pair(world, EcsIsA, p);
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
// v3
|
||||
flecs::entity p = world.prefab("SpaceShip")
|
||||
.set(MaxSpeed{100})
|
||||
.set_override(Damage{0});
|
||||
|
||||
flecs::entity i = world.entity().is_a(p);
|
||||
```
|
||||
|
||||
```cpp
|
||||
// v4
|
||||
world.component<MaxSpeed>().add(flecs::OnInstantiate, flecs::Inherit);
|
||||
|
||||
flecs::entity p = world.prefab("SpaceShip")
|
||||
.set(MaxSpeed{100})
|
||||
.set(Damage{0});
|
||||
|
||||
flecs::entity i = world.entity().is_a(p);
|
||||
```
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Additionally, the `override` operation has been renamed to `auto_override`.
|
||||
|
||||
## Component disabling
|
||||
In v4, components can only be disabled if they have the `CanToggle` trait. An example:
|
||||
|
||||
<div class="flecs-snippet-tabs">
|
||||
<ul>
|
||||
<li><b class="tab-title">C</b>
|
||||
|
||||
```c
|
||||
// v3
|
||||
ecs_entity_t e = ecs_new_w(world, Position);
|
||||
ecs_enable_component(world, e, Position, false);
|
||||
```
|
||||
|
||||
```c
|
||||
// v4
|
||||
ecs_add_id(world, ecs_id(Position), EcsCanToggle);
|
||||
|
||||
ecs_entity_t e = ecs_new_w(world, Position);
|
||||
ecs_enable_component(world, e, Position, false);
|
||||
```
|
||||
|
||||
</li>
|
||||
<li><b class="tab-title">C++</b>
|
||||
|
||||
```cpp
|
||||
// v3
|
||||
world.entity()
|
||||
.set(Position{10, 20})
|
||||
.disable<Position>();
|
||||
```
|
||||
|
||||
```cpp
|
||||
// v4
|
||||
world.component<Position>().add(flecs::CanToggle);
|
||||
|
||||
world.entity()
|
||||
.set(Position{10, 20})
|
||||
.disable<Position>();
|
||||
```
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
## Union relationships
|
||||
Union relationships have been replaced with `DontFragment, Exclusive` relationships. See the ComponentTraits manual for more information on `DontFragment` relationships.
|
||||
|
||||
```cpp
|
||||
// Old
|
||||
world.component<Position>().add(flecs::Union);
|
||||
```
|
||||
```cpp
|
||||
// New
|
||||
world.component<Position>().add(flecs::DontFragment).add(flecs::Exclusive);
|
||||
```
|
||||
|
||||
## Snapshots
|
||||
The snapshot feature has been removed from v4.
|
||||
|
||||
## Tree flattening
|
||||
The tree flattening feature (`ecs_flatten`) has been removed. It will be replaced eventually with a new implementation that addresses the shortcomings of the old feature.
|
||||
|
||||
## Addons
|
||||
The following addons have been removed/merged with other addons:
|
||||
|
||||
- `FLECS_META_C` (merged with `FLECS_META`)
|
||||
- `FLECS_PLECS` (renamed to `FLECS_SCRIPT`)
|
||||
- `FLECS_EXPR` (merged with `FLECS_SCRIPT`)
|
||||
- `FLECS_PARSER` (merged with `FLECS_SCRIPT`)
|
||||
- `FLECS_SNAPSHOT` (feature got removed)
|
||||
- `FLECS_RULES` (merged with queries)
|
||||
- `MONITOR` (merged with `STATS`)
|
||||
|
||||
## Misc
|
||||
|
||||
- The `EcsTag` trait has been renamed to `EcsPairIsTag`.
|
||||
- `DefaultChildComponent` is now a component (was a tag in v3).
|
||||
- The `ecs_set_automerge` functionality has been removed from v4.
|
||||
- The `ecs_async_stage_new` function has been renamed to `ecs_stage_new`.
|
||||
- `ecs_set` no longer returns a new entity if 0 is passed (use `ecs_insert` instead).
|
||||
- `ecs_set_entity_generation` has been renamed to `ecs_set_version`.
|
||||
- The `ecs_term_copy`/`ecs_term_move`/`ecs_term_fini` functions have been removed. Terms no longer exist by themselves, and term resources are now owned by the query object.
|
||||
- The `ecs_iter_poly` function has been removed. To iterate all entities in the world, now use `ecs_get_entities`.
|
||||
- `ecs_field_column` has been renamed to `ecs_field_column_index`.
|
||||
- `FLECS_TERM_DESC_MAX` has been renamed to `FLECS_TERM_COUNT_MAX`.
|
||||
- `ecs_app_desc_t::enable_monitor` has been renamed to `ecs_app_desc_t::enable_stats`.
|
||||
- `EcsMetaType` and `EcsMetaTypeSerialized` have been renamed to `EcsType` and `EcsTypeSerializer`, respectively.
|
||||
- `ecs_iter_t::terms` got removed and is now accessible through `ecs_iter_t::query::terms`.
|
||||
- `ecs_entity_desc_t::add` and `ecs_entity_desc_t::set` are no longer arrays but pointers to zero-terminated arrays.
|
||||
- `ecs_default_ctor` has been renamed to `flecs_default_ctor`.
|
||||
- `ecs_ensure` has been renamed to `ecs_make_alive`.
|
||||
- `binding_ctx` has been renamed in various structs to `callback_ctx`.
|
||||
- `ecs_pair_object` has been renamed to `ecs_pair_target`.
|
||||
- `ecs_get_context` and `ecs_set_context` have been renamed to `ecs_get_ctx` and `ecs_set_ctx`, respectively; and an `ecs_ctx_free_t` parameter has been added to `ecs_set_ctx`.
|
||||
- `ecs_get_stage_id` has been renamed to `ecs_stage_get_id`.
|
||||
@@ -0,0 +1,98 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600");
|
||||
|
||||
.github-corner svg {
|
||||
fill: var(--primary-color);
|
||||
color: var(--page-background-color);
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.github-corner svg {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
#projectnumber {
|
||||
margin-right: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.next_section_button {
|
||||
display: block;
|
||||
padding: var(--spacing-large) 0 var(--spacing-small) 0;
|
||||
color: var(--page-background-color);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.alter-theme-button:hover {
|
||||
background: var(--primary-dark-color);
|
||||
}
|
||||
|
||||
html.dark-mode .darkmode_inverted_image img, /* < doxygen 1.9.3 */
|
||||
html.dark-mode .darkmode_inverted_image object[type="image/svg+xml"] /* doxygen 1.9.3 */ {
|
||||
filter: brightness(87%) hue-rotate(180deg) invert();
|
||||
}
|
||||
|
||||
.bordered_image {
|
||||
border-radius: var(--border-radius-small);
|
||||
border: 1px solid var(--separator-color);
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
html.dark-mode .bordered_image img, /* < doxygen 1.9.3 */
|
||||
html.dark-mode .bordered_image object[type="image/svg+xml"] /* doxygen 1.9.3 */ {
|
||||
border-radius: var(--border-radius-small);
|
||||
}
|
||||
|
||||
html {
|
||||
--primary-color: rgb(66, 185, 131);
|
||||
--fragment-link: rgb(66, 185, 131);
|
||||
--fragment-keywordflow: rgb(188, 137, 189);
|
||||
--spacing-small: 8px;
|
||||
--content-maxwidth: 800px;
|
||||
--font-family: 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif;
|
||||
--font-family-monospace: 'Roboto Mono', Monaco, courier, monospace;
|
||||
--fragment-lineheight: 24px;
|
||||
}
|
||||
|
||||
html.dark-mode {
|
||||
--primary-color: #42b983;
|
||||
--page-background-color: hsl(210, 12%, 16%);
|
||||
--page-foreground-color: hsl(195, 15%, 85%);
|
||||
--side-nav-background: hsl(214, 10%, 13%);
|
||||
--separator-color: rgba(255,255,255,0.07);
|
||||
--tablehead-background: hsl(214, 10%, 13%);
|
||||
--code-background: hsl(214, 10%, 13%);
|
||||
--fragment-background: hsl(214, 10%, 13%);
|
||||
--fragment-link: rgb(66, 185, 131);
|
||||
--fragment-keywordflow: rgb(188, 137, 189);
|
||||
}
|
||||
|
||||
div.fragment {
|
||||
padding-top: 25px !important;
|
||||
padding-bottom: 25px !important;
|
||||
padding-left: 20px !important;
|
||||
border-radius: 0px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
code {
|
||||
border-radius: 0px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#projectlogo img {
|
||||
max-height: calc(var(--title-font-size) * 1.5) !important;
|
||||
}
|
||||
|
||||
html.light-mode #projectlogo img {
|
||||
content: url(logo_small_dark.png);
|
||||
}
|
||||
|
||||
#nav-tree a:hover {
|
||||
background: none !important;
|
||||
}
|
||||
#nav-tree a:hover > span.arrow {
|
||||
background: none !important;
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 - 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeDarkModeToggle extends HTMLElement {
|
||||
// SVG icons from https://fonts.google.com/icons
|
||||
// Licensed under the Apache 2.0 license:
|
||||
// https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
static lightModeIcon = `<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FCBF00"><rect fill="none" height="24" width="24"/><circle cx="12" cy="12" opacity=".3" r="3"/><path d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"/></svg>`
|
||||
static darkModeIcon = `<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FE9700"><rect fill="none" height="24" width="24"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27 C17.45,17.19,14.93,19,12,19c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z" opacity=".3"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/></svg>`
|
||||
static title = "Toggle Light/Dark Mode"
|
||||
|
||||
static prefersLightModeInDarkModeKey = "prefers-light-mode-in-dark-mode"
|
||||
static prefersDarkModeInLightModeKey = "prefers-dark-mode-in-light-mode"
|
||||
|
||||
static _staticConstructor = function() {
|
||||
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.userPreference)
|
||||
// Update the color scheme when the browsers preference changes
|
||||
// without user interaction on the website.
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
||||
DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged()
|
||||
})
|
||||
// Update the color scheme when the tab is made visible again.
|
||||
// It is possible that the appearance was changed in another tab
|
||||
// while this tab was in the background.
|
||||
document.addEventListener("visibilitychange", visibilityState => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged()
|
||||
}
|
||||
});
|
||||
}()
|
||||
|
||||
static init() {
|
||||
$(function() {
|
||||
$(document).ready(function() {
|
||||
const toggleButton = document.createElement('doxygen-awesome-dark-mode-toggle')
|
||||
toggleButton.title = DoxygenAwesomeDarkModeToggle.title
|
||||
toggleButton.updateIcon()
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
||||
toggleButton.updateIcon()
|
||||
})
|
||||
document.addEventListener("visibilitychange", visibilityState => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
toggleButton.updateIcon()
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function(){
|
||||
document.getElementById("MSearchBox").parentNode.appendChild(toggleButton)
|
||||
})
|
||||
$(window).resize(function(){
|
||||
document.getElementById("MSearchBox").parentNode.appendChild(toggleButton)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.onclick=this.toggleDarkMode
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` for dark-mode, `false` for light-mode system preference
|
||||
*/
|
||||
static get systemPreference() {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` for dark-mode, `false` for light-mode user preference
|
||||
*/
|
||||
static get userPreference() {
|
||||
return (!DoxygenAwesomeDarkModeToggle.systemPreference && localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)) ||
|
||||
(DoxygenAwesomeDarkModeToggle.systemPreference && !localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey))
|
||||
}
|
||||
|
||||
static set userPreference(userPreference) {
|
||||
DoxygenAwesomeDarkModeToggle.darkModeEnabled = userPreference
|
||||
if(!userPreference) {
|
||||
if(DoxygenAwesomeDarkModeToggle.systemPreference) {
|
||||
localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey, true)
|
||||
} else {
|
||||
localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)
|
||||
}
|
||||
} else {
|
||||
if(!DoxygenAwesomeDarkModeToggle.systemPreference) {
|
||||
localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey, true)
|
||||
} else {
|
||||
localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey)
|
||||
}
|
||||
}
|
||||
DoxygenAwesomeDarkModeToggle.onUserPreferenceChanged()
|
||||
}
|
||||
|
||||
static enableDarkMode(enable) {
|
||||
if(enable) {
|
||||
DoxygenAwesomeDarkModeToggle.darkModeEnabled = true
|
||||
document.documentElement.classList.add("dark-mode")
|
||||
document.documentElement.classList.remove("light-mode")
|
||||
} else {
|
||||
DoxygenAwesomeDarkModeToggle.darkModeEnabled = false
|
||||
document.documentElement.classList.remove("dark-mode")
|
||||
document.documentElement.classList.add("light-mode")
|
||||
}
|
||||
}
|
||||
|
||||
static onSystemPreferenceChanged() {
|
||||
DoxygenAwesomeDarkModeToggle.darkModeEnabled = DoxygenAwesomeDarkModeToggle.userPreference
|
||||
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled)
|
||||
}
|
||||
|
||||
static onUserPreferenceChanged() {
|
||||
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled)
|
||||
}
|
||||
|
||||
toggleDarkMode() {
|
||||
DoxygenAwesomeDarkModeToggle.userPreference = !DoxygenAwesomeDarkModeToggle.userPreference
|
||||
this.updateIcon()
|
||||
}
|
||||
|
||||
updateIcon() {
|
||||
if(DoxygenAwesomeDarkModeToggle.darkModeEnabled) {
|
||||
this.innerHTML = DoxygenAwesomeDarkModeToggle.darkModeIcon
|
||||
} else {
|
||||
this.innerHTML = DoxygenAwesomeDarkModeToggle.lightModeIcon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("doxygen-awesome-dark-mode-toggle", DoxygenAwesomeDarkModeToggle);
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 - 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeFragmentCopyButton extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.onclick=this.copyContent
|
||||
}
|
||||
static title = "Copy to clipboard"
|
||||
static copyIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>`
|
||||
static successIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>`
|
||||
static successDuration = 980
|
||||
static init() {
|
||||
$(function() {
|
||||
$(document).ready(function() {
|
||||
if(navigator.clipboard) {
|
||||
const fragments = document.getElementsByClassName("fragment")
|
||||
for(const fragment of fragments) {
|
||||
const fragmentWrapper = document.createElement("div")
|
||||
fragmentWrapper.className = "doxygen-awesome-fragment-wrapper"
|
||||
const fragmentCopyButton = document.createElement("doxygen-awesome-fragment-copy-button")
|
||||
fragmentCopyButton.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon
|
||||
fragmentCopyButton.title = DoxygenAwesomeFragmentCopyButton.title
|
||||
|
||||
fragment.parentNode.replaceChild(fragmentWrapper, fragment)
|
||||
fragmentWrapper.appendChild(fragment)
|
||||
fragmentWrapper.appendChild(fragmentCopyButton)
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
copyContent() {
|
||||
const content = this.previousSibling.cloneNode(true)
|
||||
// filter out line number from file listings
|
||||
content.querySelectorAll(".lineno, .ttc").forEach((node) => {
|
||||
node.remove()
|
||||
})
|
||||
let textContent = content.textContent
|
||||
// remove trailing newlines that appear in file listings
|
||||
let numberOfTrailingNewlines = 0
|
||||
while(textContent.charAt(textContent.length - (numberOfTrailingNewlines + 1)) == '\n') {
|
||||
numberOfTrailingNewlines++;
|
||||
}
|
||||
textContent = textContent.substring(0, textContent.length - numberOfTrailingNewlines)
|
||||
navigator.clipboard.writeText(textContent);
|
||||
this.classList.add("success")
|
||||
this.innerHTML = DoxygenAwesomeFragmentCopyButton.successIcon
|
||||
window.setTimeout(() => {
|
||||
this.classList.remove("success")
|
||||
this.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon
|
||||
}, DoxygenAwesomeFragmentCopyButton.successDuration);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("doxygen-awesome-fragment-copy-button", DoxygenAwesomeFragmentCopyButton)
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 - 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeInteractiveToc {
|
||||
static topOffset = 38
|
||||
static hideMobileMenu = true
|
||||
static headers = []
|
||||
|
||||
static init() {
|
||||
window.addEventListener("load", () => {
|
||||
let toc = document.querySelector(".contents > .toc")
|
||||
if(toc) {
|
||||
toc.classList.add("interactive")
|
||||
if(!DoxygenAwesomeInteractiveToc.hideMobileMenu) {
|
||||
toc.classList.add("open")
|
||||
}
|
||||
document.querySelector(".contents > .toc > h3")?.addEventListener("click", () => {
|
||||
if(toc.classList.contains("open")) {
|
||||
toc.classList.remove("open")
|
||||
} else {
|
||||
toc.classList.add("open")
|
||||
}
|
||||
})
|
||||
|
||||
document.querySelectorAll(".contents > .toc > ul a").forEach((node) => {
|
||||
let id = node.getAttribute("href").substring(1)
|
||||
DoxygenAwesomeInteractiveToc.headers.push({
|
||||
node: node,
|
||||
headerNode: document.getElementById(id)
|
||||
})
|
||||
|
||||
document.getElementById("doc-content")?.addEventListener("scroll", () => {
|
||||
DoxygenAwesomeInteractiveToc.update()
|
||||
})
|
||||
})
|
||||
DoxygenAwesomeInteractiveToc.update()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static update() {
|
||||
let active = DoxygenAwesomeInteractiveToc.headers[0]?.node
|
||||
DoxygenAwesomeInteractiveToc.headers.forEach((header) => {
|
||||
let position = header.headerNode.getBoundingClientRect().top
|
||||
header.node.classList.remove("active")
|
||||
header.node.classList.remove("aboveActive")
|
||||
if(position < DoxygenAwesomeInteractiveToc.topOffset) {
|
||||
active = header.node
|
||||
active?.classList.add("aboveActive")
|
||||
}
|
||||
})
|
||||
active?.classList.add("active")
|
||||
active?.classList.remove("aboveActive")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 - 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeParagraphLink {
|
||||
// Icon from https://fonts.google.com/icons
|
||||
// Licensed under the Apache 2.0 license:
|
||||
// https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
static icon = `<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M17 7h-4v2h4c1.65 0 3 1.35 3 3s-1.35 3-3 3h-4v2h4c2.76 0 5-2.24 5-5s-2.24-5-5-5zm-6 8H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-2zm-3-4h8v2H8z"/></svg>`
|
||||
static title = "Permanent Link"
|
||||
static init() {
|
||||
$(function() {
|
||||
$(document).ready(function() {
|
||||
document.querySelectorAll(".contents a.anchor[id], .contents .groupheader > a[id]").forEach((node) => {
|
||||
let anchorlink = document.createElement("a")
|
||||
anchorlink.setAttribute("href", `#${node.getAttribute("id")}`)
|
||||
anchorlink.setAttribute("title", DoxygenAwesomeParagraphLink.title)
|
||||
anchorlink.classList.add("anchorlink")
|
||||
node.classList.add("anchor")
|
||||
anchorlink.innerHTML = DoxygenAwesomeParagraphLink.icon
|
||||
node.parentElement.appendChild(anchorlink)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 - 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
|
||||
#MSearchBox {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px);
|
||||
}
|
||||
|
||||
#MSearchField {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 - 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
html {
|
||||
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
|
||||
* Make sure it is wide enough to contain the page title (logo + title + version)
|
||||
*/
|
||||
--side-nav-fixed-width: 335px;
|
||||
--menu-display: none;
|
||||
|
||||
--top-height: 120px;
|
||||
--toc-sticky-top: -25px;
|
||||
--toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 25px);
|
||||
}
|
||||
|
||||
#projectname {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
html {
|
||||
--searchbar-background: var(--page-background-color);
|
||||
}
|
||||
|
||||
#side-nav {
|
||||
min-width: var(--side-nav-fixed-width);
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
top: var(--top-height);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
#nav-tree, #side-nav {
|
||||
height: calc(100vh - var(--top-height)) !important;
|
||||
}
|
||||
|
||||
#nav-tree {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#top {
|
||||
display: block;
|
||||
border-bottom: none;
|
||||
height: var(--top-height);
|
||||
margin-bottom: calc(0px - var(--top-height));
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
overflow: hidden;
|
||||
background: var(--side-nav-background);
|
||||
}
|
||||
#main-nav {
|
||||
float: left;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.ui-resizable-handle {
|
||||
cursor: default;
|
||||
width: 1px !important;
|
||||
background: var(--separator-color);
|
||||
box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color);
|
||||
}
|
||||
|
||||
#nav-path {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: var(--side-nav-fixed-width);
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#doc-content {
|
||||
height: calc(100vh - 31px) !important;
|
||||
padding-bottom: calc(3 * var(--spacing-large));
|
||||
padding-top: calc(var(--top-height) - 80px);
|
||||
box-sizing: border-box;
|
||||
margin-left: var(--side-nav-fixed-width) !important;
|
||||
}
|
||||
|
||||
#MSearchBox {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)));
|
||||
}
|
||||
|
||||
#MSearchField {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px);
|
||||
}
|
||||
|
||||
#MSearchResultsWindow {
|
||||
left: var(--spacing-medium) !important;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeTabs {
|
||||
|
||||
static init() {
|
||||
window.addEventListener("load", () => {
|
||||
document.querySelectorAll(".tabbed:not(:empty)").forEach((tabbed, tabbedIndex) => {
|
||||
let tabLinkList = []
|
||||
tabbed.querySelectorAll("li").forEach((tab, tabIndex) => {
|
||||
tab.id = "tab_" + tabbedIndex + "_" + tabIndex
|
||||
let header = tab.querySelector(".tab-title")
|
||||
let tabLink = document.createElement("button")
|
||||
tabLink.classList.add("tab-button")
|
||||
tabLink.appendChild(header)
|
||||
header.title = header.textContent
|
||||
tabLink.addEventListener("click", () => {
|
||||
tabbed.querySelectorAll("li").forEach((tab) => {
|
||||
tab.classList.remove("selected")
|
||||
})
|
||||
tabLinkList.forEach((tabLink) => {
|
||||
tabLink.classList.remove("active")
|
||||
})
|
||||
tab.classList.add("selected")
|
||||
tabLink.classList.add("active")
|
||||
})
|
||||
tabLinkList.push(tabLink)
|
||||
if(tabIndex == 0) {
|
||||
tab.classList.add("selected")
|
||||
tabLink.classList.add("active")
|
||||
}
|
||||
})
|
||||
let tabsOverview = document.createElement("div")
|
||||
tabsOverview.classList.add("tabs-overview")
|
||||
let tabsOverviewContainer = document.createElement("div")
|
||||
tabsOverviewContainer.classList.add("tabs-overview-container")
|
||||
tabLinkList.forEach((tabLink) => {
|
||||
tabsOverview.appendChild(tabLink)
|
||||
})
|
||||
tabsOverviewContainer.appendChild(tabsOverview)
|
||||
tabbed.before(tabsOverviewContainer)
|
||||
|
||||
function resize() {
|
||||
let maxTabHeight = 0
|
||||
tabbed.querySelectorAll("li").forEach((tab, tabIndex) => {
|
||||
let visibility = tab.style.display
|
||||
tab.style.display = "block"
|
||||
maxTabHeight = Math.max(tab.offsetHeight, maxTabHeight)
|
||||
tab.style.display = visibility
|
||||
})
|
||||
tabbed.style.height = `${maxTabHeight + 10}px`
|
||||
}
|
||||
|
||||
resize()
|
||||
new ResizeObserver(resize).observe(tabbed)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
static resize(tabbed) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
.flecs-snippet-tabs > ul {
|
||||
padding-inline-start: 0px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-tab {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-tab.selected {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-tab p.startli:first-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-tab .doxygen-awesome-fragment-wrapper:first-of-type .fragment {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-overview-container {
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-overview {
|
||||
border-bottom: 1px solid var(--separator-color);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.flecs-snippet-tabs-overview-container {
|
||||
margin: 0 calc(0px - var(--spacing-large));
|
||||
}
|
||||
.flecs-snippet-tabs-overview {
|
||||
padding: 0 var(--spacing-large)
|
||||
}
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-overview button.tab-button {
|
||||
color: var(--page-foreground-color);
|
||||
margin: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: calc(var(--spacing-large) / 2) 0;
|
||||
display: inline-block;
|
||||
font-size: var(--page-font-size);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 0 0 var(--separator-color);
|
||||
position: relative;
|
||||
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-overview button.tab-button .tab-title::before {
|
||||
display: block;
|
||||
content: attr(title);
|
||||
font-weight: 600;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-overview button.tab-button .tab-title {
|
||||
float: left;
|
||||
white-space: nowrap;
|
||||
font-weight: normal;
|
||||
padding: calc(var(--spacing-large) / 2) var(--spacing-large);
|
||||
border-radius: var(--border-radius-medium);
|
||||
transition: background-color var(--animation-duration) ease-in-out, font-weight var(--animation-duration) ease-in-out;
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-overview button.tab-button:not(:last-child) .tab-title {
|
||||
box-shadow: 8px 0 0 -7px var(--separator-color);
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-overview button.tab-button:hover .tab-title {
|
||||
background: var(--separator-color);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-overview button.tab-button.active .tab-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-overview button.tab-button::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 0;
|
||||
width: 0%;
|
||||
margin: 0 auto;
|
||||
border-radius: var(--border-radius-small) var(--border-radius-small) 0 0;
|
||||
background-color: var(--primary-color);
|
||||
transition: width var(--animation-duration) ease-in-out, height var(--animation-duration) ease-in-out;
|
||||
}
|
||||
|
||||
.flecs-snippet-tabs-overview button.tab-button.active::after {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
height: 3px;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// Heavily modified version of DoxygenAwesomeTabs
|
||||
// https://github.com/jothepro/doxygen-awesome-css
|
||||
class FlecsSnippetTabs {
|
||||
static init() {
|
||||
window.addEventListener("load", () => {
|
||||
let changeTab = (tabName) => {
|
||||
// Save active tab to local storage
|
||||
let activeTabHistory = JSON.parse(localStorage.getItem("activeTabHistory"));
|
||||
if (activeTabHistory) {
|
||||
// Remove if already exists and wasn't the last one
|
||||
let index = activeTabHistory.indexOf(tabName);
|
||||
if (index < activeTabHistory.length - 1) {
|
||||
if (index > -1) {
|
||||
activeTabHistory.splice(index, 1);
|
||||
}
|
||||
activeTabHistory.push(tabName);
|
||||
localStorage.setItem("activeTabHistory", JSON.stringify(activeTabHistory));
|
||||
}
|
||||
} else {
|
||||
// Create if doesn't exist
|
||||
localStorage.setItem("activeTabHistory", JSON.stringify([tabName]));
|
||||
}
|
||||
|
||||
// TODO: Don't switch tabs if this tabbed doesn't have the tab
|
||||
document.querySelectorAll(".flecs-snippet-tabs:not(:empty)").forEach((tabbed, tabbedIndex) => {
|
||||
// Find the last active tab that exists in this group of tabs
|
||||
let lastActiveIndex = -1;
|
||||
let tabButtonId = "tab_button_" + tabbedIndex + "_0"; // Default to first tab
|
||||
tabbed.querySelectorAll(".tab-button").forEach((tabLink) => {
|
||||
let index = activeTabHistory.indexOf(tabLink.textContent);
|
||||
if (index > -1 && index > lastActiveIndex) {
|
||||
lastActiveIndex = index;
|
||||
tabButtonId = tabLink.id;
|
||||
}
|
||||
});
|
||||
|
||||
// Update active tab link
|
||||
tabbed.querySelectorAll(".tab-button").forEach((tabLink) => {
|
||||
if (tabLink.id == tabButtonId) {
|
||||
tabLink.classList.add("active");
|
||||
} else {
|
||||
tabLink.classList.remove("active");
|
||||
}
|
||||
});
|
||||
|
||||
let tabId = tabButtonId.replace("tab_button_", "tab_");
|
||||
|
||||
// Update active tab
|
||||
tabbed.querySelectorAll(".flecs-snippet-tabs:not(:empty) > ul > li").forEach((tab, tabIndex) => {
|
||||
if (tab.id == tabId) {
|
||||
tab.classList.add("selected");
|
||||
} else {
|
||||
tab.classList.remove("selected");
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
document.querySelectorAll(".flecs-snippet-tabs:not(:empty)").forEach((tabbed, tabbedIndex) => {
|
||||
let tabsOverview = document.createElement("div");
|
||||
tabsOverview.classList.add("flecs-snippet-tabs-overview");
|
||||
|
||||
tabbed.querySelectorAll(".flecs-snippet-tabs:not(:empty) > ul > li").forEach((tab, tabIndex) => {
|
||||
tab.classList.add("flecs-snippet-tabs-tab");
|
||||
tab.id = "tab_" + tabbedIndex + "_" + tabIndex;
|
||||
|
||||
let header = tab.querySelector(".tab-title");
|
||||
if (!header) {
|
||||
console.error("Tabbed element doesn't have a header", tab);
|
||||
return;
|
||||
}
|
||||
header.title = header.textContent;
|
||||
|
||||
let tabLink = document.createElement("button");
|
||||
tabLink.id = "tab_button_" + tabbedIndex + "_" + tabIndex;
|
||||
tabLink.classList.add("tab-button");
|
||||
tabLink.appendChild(header);
|
||||
tabLink.addEventListener("click", () => changeTab(header.textContent));
|
||||
|
||||
tabsOverview.appendChild(tabLink);
|
||||
|
||||
if (tabIndex == 0) {
|
||||
tab.classList.add("selected");
|
||||
tabLink.classList.add("active");
|
||||
}
|
||||
});
|
||||
|
||||
let tabsOverviewContainer = document.createElement("div");
|
||||
tabsOverviewContainer.classList.add("flecs-snippet-tabs-overview-container");
|
||||
tabsOverviewContainer.appendChild(tabsOverview);
|
||||
|
||||
tabbed.prepend(tabsOverviewContainer);
|
||||
});
|
||||
|
||||
// Restore active tab from local storage
|
||||
let activeTabHistory = JSON.parse(localStorage.getItem("activeTabHistory"));
|
||||
if (activeTabHistory) {
|
||||
changeTab(activeTabHistory[activeTabHistory.length - 1]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<!-- HTML footer for doxygen 1.9.5-->
|
||||
<!-- start footer part -->
|
||||
<!--BEGIN GENERATE_TREEVIEW-->
|
||||
|
||||
<!--END GENERATE_TREEVIEW-->
|
||||
<!--BEGIN !GENERATE_TREEVIEW-->
|
||||
<!--END !GENERATE_TREEVIEW-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,114 @@
|
||||
<!-- HTML header for doxygen 1.9.5-->
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="$langISO">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=11"/>
|
||||
<meta name="generator" content="Doxygen $doxygenversion"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
|
||||
<!-- BEGIN opengraph metadata -->
|
||||
<meta property="og:title" content="$projectname: $title" />
|
||||
<meta property="og:image" content="https://www.flecs.dev/flecs/logo.png" />
|
||||
<meta property="og:description" content="$projectbrief" />
|
||||
<meta property="og:url" content="https://www.flecs.dev/" />
|
||||
<!-- END opengraph metadata -->
|
||||
|
||||
<!-- BEGIN twitter metadata -->
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:image" content="https://www.flecs.dev/flecs/logo.png" />
|
||||
<meta name="twitter:image:src" content="https://www.flecs.dev/flecs/logo.png" />
|
||||
<meta name="twitter:title" content="$projectname: $title" />
|
||||
<meta name="twitter:description" content="$projectbrief" />
|
||||
<!-- END twitter metadata -->
|
||||
|
||||
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
|
||||
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<!--BEGIN DISABLE_INDEX-->
|
||||
<!--BEGIN FULL_SIDEBAR-->
|
||||
<script type="text/javascript">var page_layout=1;</script>
|
||||
<!--END FULL_SIDEBAR-->
|
||||
<!--END DISABLE_INDEX-->
|
||||
<script type="text/javascript" src="$relpath^jquery.js"></script>
|
||||
<script type="text/javascript" src="$relpath^dynsections.js"></script>
|
||||
$treeview
|
||||
$search
|
||||
$mathjax
|
||||
$darkmode
|
||||
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
|
||||
$extrastylesheet
|
||||
<link rel="icon" href="logo_small.png" />
|
||||
<script type="text/javascript" src="$relpath^doxygen-awesome-darkmode-toggle.js"></script>
|
||||
<script type="text/javascript" src="$relpath^doxygen-awesome-fragment-copy-button.js"></script>
|
||||
<script type="text/javascript" src="$relpath^doxygen-awesome-paragraph-link.js"></script>
|
||||
<script type="text/javascript" src="$relpath^doxygen-awesome-interactive-toc.js"></script>
|
||||
<script type="text/javascript" src="$relpath^doxygen-awesome-tabs.js"></script>
|
||||
<script type="text/javascript" src="$relpath^flecs-snippet-tabs.js"></script>
|
||||
<script type="text/javascript">
|
||||
DoxygenAwesomeDarkModeToggle.init()
|
||||
DoxygenAwesomeFragmentCopyButton.init()
|
||||
DoxygenAwesomeParagraphLink.init()
|
||||
DoxygenAwesomeInteractiveToc.init()
|
||||
DoxygenAwesomeTabs.init()
|
||||
FlecsSnippetTabs.init()
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- https://tholman.com/github-corners/ -->
|
||||
<a href="https://github.com/SanderMertens/flecs" class="github-corner" title="View source on GitHub" target="_blank">
|
||||
<svg viewBox="0 0 250 250" width="40" height="40" style="position: absolute; top: 0; border: 0; right: 0; z-index: 99;" aria-hidden="true">
|
||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
|
||||
<style>
|
||||
.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}
|
||||
</style>
|
||||
|
||||
<!--BEGIN DISABLE_INDEX-->
|
||||
<!--BEGIN FULL_SIDEBAR-->
|
||||
<div id="side-nav" class="ui-resizable side-nav-resizable"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
<!--END FULL_SIDEBAR-->
|
||||
<!--END DISABLE_INDEX-->
|
||||
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
|
||||
<!--BEGIN TITLEAREA-->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr id="projectrow">
|
||||
<!--BEGIN PROJECT_LOGO-->
|
||||
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
|
||||
<!--END PROJECT_LOGO-->
|
||||
<!--BEGIN PROJECT_NAME-->
|
||||
<td id="projectalign">
|
||||
<div id="projectname">$projectname<!--BEGIN PROJECT_NUMBER--><span id="projectnumber"> $projectnumber</span><!--END PROJECT_NUMBER-->
|
||||
</div>
|
||||
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
|
||||
</td>
|
||||
<!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME-->
|
||||
<!--BEGIN PROJECT_BRIEF-->
|
||||
<td>
|
||||
<div id="projectbrief">$projectbrief</div>
|
||||
</td>
|
||||
<!--END PROJECT_BRIEF-->
|
||||
<!--END !PROJECT_NAME-->
|
||||
<!--BEGIN DISABLE_INDEX-->
|
||||
<!--BEGIN SEARCHENGINE-->
|
||||
<!--BEGIN !FULL_SIDEBAR-->
|
||||
<td>$searchbox</td>
|
||||
<!--END !FULL_SIDEBAR-->
|
||||
<!--END SEARCHENGINE-->
|
||||
<!--END DISABLE_INDEX-->
|
||||
</tr>
|
||||
<!--BEGIN SEARCHENGINE-->
|
||||
<!--BEGIN FULL_SIDEBAR-->
|
||||
<tr><td colspan="2">$searchbox</td></tr>
|
||||
<!--END FULL_SIDEBAR-->
|
||||
<!--END SEARCHENGINE-->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--END TITLEAREA-->
|
||||
<!-- end header part -->
|
||||
@@ -0,0 +1,80 @@
|
||||
/*! `javascript` grammar compiled for Highlight.js 11.9.0 */
|
||||
(()=>{var e=(()=>{"use strict"
|
||||
;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],r=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],c=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","global"],i=[].concat(r,t,s)
|
||||
;return o=>{const l=o.regex,b=e,d={begin:/<[A-Za-z0-9\\._:-]+/,
|
||||
end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{
|
||||
const a=e[0].length+e.index,t=e.input[a]
|
||||
;if("<"===t||","===t)return void n.ignoreMatch();let s
|
||||
;">"===t&&(((e,{after:n})=>{const a="</"+e[0].slice(1)
|
||||
;return-1!==e.input.indexOf(a,n)})(e,{after:a})||n.ignoreMatch())
|
||||
;const r=e.input.substring(a)
|
||||
;((s=r.match(/^\s*=/))||(s=r.match(/^\s+extends\s+/))&&0===s.index)&&n.ignoreMatch()
|
||||
}},g={$pattern:e,keyword:n,literal:a,built_in:i,"variable.language":c
|
||||
},u="[0-9](_?[0-9])*",m=`\\.(${u})`,E="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",A={
|
||||
className:"number",variants:[{
|
||||
begin:`(\\b(${E})((${m})|\\.)?|(${m}))[eE][+-]?(${u})\\b`},{
|
||||
begin:`\\b(${E})\\b((${m})\\b|\\.)?|(${m})\\b`},{
|
||||
begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{
|
||||
begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{
|
||||
begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{
|
||||
begin:"\\b0[0-7]+n?\\b"}],relevance:0},y={className:"subst",begin:"\\$\\{",
|
||||
end:"\\}",keywords:g,contains:[]},h={begin:"html`",end:"",starts:{end:"`",
|
||||
returnEnd:!1,contains:[o.BACKSLASH_ESCAPE,y],subLanguage:"xml"}},N={
|
||||
begin:"css`",end:"",starts:{end:"`",returnEnd:!1,
|
||||
contains:[o.BACKSLASH_ESCAPE,y],subLanguage:"css"}},_={begin:"gql`",end:"",
|
||||
starts:{end:"`",returnEnd:!1,contains:[o.BACKSLASH_ESCAPE,y],
|
||||
subLanguage:"graphql"}},f={className:"string",begin:"`",end:"`",
|
||||
contains:[o.BACKSLASH_ESCAPE,y]},v={className:"comment",
|
||||
variants:[o.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{
|
||||
begin:"(?=@[A-Za-z]+)",relevance:0,contains:[{className:"doctag",
|
||||
begin:"@[A-Za-z]+"},{className:"type",begin:"\\{",end:"\\}",excludeEnd:!0,
|
||||
excludeBegin:!0,relevance:0},{className:"variable",begin:b+"(?=\\s*(-)|$)",
|
||||
endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]
|
||||
}),o.C_BLOCK_COMMENT_MODE,o.C_LINE_COMMENT_MODE]
|
||||
},p=[o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,h,N,_,f,{match:/\$\d+/},A]
|
||||
;y.contains=p.concat({begin:/\{/,end:/\}/,keywords:g,contains:["self"].concat(p)
|
||||
});const S=[].concat(v,y.contains),w=S.concat([{begin:/\(/,end:/\)/,keywords:g,
|
||||
contains:["self"].concat(S)}]),R={className:"params",begin:/\(/,end:/\)/,
|
||||
excludeBegin:!0,excludeEnd:!0,keywords:g,contains:w},O={variants:[{
|
||||
match:[/class/,/\s+/,b,/\s+/,/extends/,/\s+/,l.concat(b,"(",l.concat(/\./,b),")*")],
|
||||
scope:{1:"keyword",3:"title.class",5:"keyword",7:"title.class.inherited"}},{
|
||||
match:[/class/,/\s+/,b],scope:{1:"keyword",3:"title.class"}}]},k={relevance:0,
|
||||
match:l.either(/\bJSON/,/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/),
|
||||
className:"title.class",keywords:{_:[...t,...s]}},I={variants:[{
|
||||
match:[/function/,/\s+/,b,/(?=\s*\()/]},{match:[/function/,/\s*(?=\()/]}],
|
||||
className:{1:"keyword",3:"title.function"},label:"func.def",contains:[R],
|
||||
illegal:/%/},x={
|
||||
match:l.concat(/\b/,(T=[...r,"super","import"],l.concat("(?!",T.join("|"),")")),b,l.lookahead(/\(/)),
|
||||
className:"title.function",relevance:0};var T;const C={
|
||||
begin:l.concat(/\./,l.lookahead(l.concat(b,/(?![0-9A-Za-z$_(])/))),end:b,
|
||||
excludeBegin:!0,keywords:"prototype",className:"property",relevance:0},M={
|
||||
match:[/get|set/,/\s+/,b,/(?=\()/],className:{1:"keyword",3:"title.function"},
|
||||
contains:[{begin:/\(\)/},R]
|
||||
},B="(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+o.UNDERSCORE_IDENT_RE+")\\s*=>",$={
|
||||
match:[/const|var|let/,/\s+/,b,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(B)],
|
||||
keywords:"async",className:{1:"keyword",3:"title.function"},contains:[R]}
|
||||
;return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{
|
||||
PARAMS_CONTAINS:w,CLASS_REFERENCE:k},illegal:/#(?![$_A-z])/,
|
||||
contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{
|
||||
label:"use_strict",className:"meta",relevance:10,
|
||||
begin:/^\s*['"]use (strict|asm)['"]/
|
||||
},o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,h,N,_,f,v,{match:/\$\d+/},A,k,{
|
||||
className:"attr",begin:b+l.lookahead(":"),relevance:0},$,{
|
||||
begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",
|
||||
keywords:"return throw case",relevance:0,contains:[v,o.REGEXP_MODE,{
|
||||
className:"function",begin:B,returnBegin:!0,end:"\\s*=>",contains:[{
|
||||
className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{
|
||||
className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,
|
||||
excludeEnd:!0,keywords:g,contains:w}]}]},{begin:/,/,relevance:0},{match:/\s+/,
|
||||
relevance:0},{variants:[{begin:"<>",end:"</>"},{
|
||||
match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:d.begin,
|
||||
"on:begin":d.isTrulyOpeningTag,end:d.end}],subLanguage:"xml",contains:[{
|
||||
begin:d.begin,end:d.end,skip:!0,contains:["self"]}]}]},I,{
|
||||
beginKeywords:"while if switch catch for"},{
|
||||
begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",
|
||||
returnBegin:!0,label:"func.def",contains:[R,o.inherit(o.TITLE_MODE,{begin:b,
|
||||
className:"title.function"})]},{match:/\.\.\./,relevance:0},C,{match:"\\$"+b,
|
||||
relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"},
|
||||
contains:[R]},x,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,
|
||||
className:"variable.constant"},O,M,{match:/\$[(.]/}]}}})()
|
||||
;hljs.registerLanguage("javascript",e)})();
|
||||
|
After Width: | Height: | Size: 231 KiB |
|
After Width: | Height: | Size: 980 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 299 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 6.0 KiB |