Skip to content
Merged

Dev #18

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Build

on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]

jobs:
build:
name: Build (${{ matrix.cxx }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- cc: gcc
cxx: g++
- cc: clang
cxx: clang++
steps:
- uses: actions/checkout@v4

- name: Configure
run: >
cmake -S . -B build
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_C_COMPILER=${{ matrix.cc }}
-DCMAKE_CXX_COMPILER=${{ matrix.cxx }}

- name: Build
run: cmake --build build --parallel
56 changes: 56 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Unit tests

on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]

jobs:
unit-tests:
name: Unit tests (${{ matrix.cxx }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- cc: gcc
cxx: g++
- cc: clang
cxx: clang++
steps:
- uses: actions/checkout@v4

- name: Configure
run: >
cmake -S . -B build
-DBUILD_TESTS=ON
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_C_COMPILER=${{ matrix.cc }}
-DCMAKE_CXX_COMPILER=${{ matrix.cxx }}

- name: Build
run: cmake --build build --parallel --target kronkworld_tests

- name: Run unit tests
# kronklab's runner always exits with 0, even when tests fail or
# crash, so the exit code can't be trusted: parse the final report.
run: |
set -o pipefail
./build/kronkworld_tests | tee test-output.txt

report=$(sed 's/\x1b\[[0-9;]*m//g' test-output.txt | grep '^\[REPORT\] total')
echo "$report"

total=$(sed -E 's/.*total \[([0-9]+)\].*/\1/' <<< "$report")
failed=$(sed -E 's/.*failed \[([0-9]+)\].*/\1/' <<< "$report")
crashed=$(sed -E 's/.*crashed \[([0-9]+)\].*/\1/' <<< "$report")

if [ -z "$report" ] || [ "${total:-0}" -eq 0 ]; then
echo "::error::No test report found or no test was run"
exit 1
fi
if [ "$failed" -ne 0 ] || [ "$crashed" -ne 0 ]; then
echo "::error::$failed test(s) failed, $crashed test(s) crashed"
exit 1
fi
6 changes: 0 additions & 6 deletions example/snake/src/Snake.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,8 @@
#include <SFML/Window/Event.hpp>
#include <SFML/Window/Keyboard.hpp>
#include <SFML/Window/VideoMode.hpp>
#include <chrono>
#include <cstddef>
#include <iostream>
#include <memory>
#include "../../../include/kronkworld/Kronkworld.hpp"
#include <SFML/Graphics/RenderWindow.hpp>
#include "SFML/Graphics.hpp"
#include <utility>
#include "systems/StartupSystems.hpp"
#include "systems/UpdateSystems.hpp"
#include "systems/RenderSystems.hpp"
Expand Down
5 changes: 3 additions & 2 deletions include/kronkworld/component/Component.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "ComponentError.hpp"
#include "kronkworld/entity/Entity.hpp"
#include <array>
#include <atomic>
#include <cstdint>
#include <memory>
#include <utility>
Expand Down Expand Up @@ -106,12 +107,12 @@ namespace kw
template<typename C>
Component id(void) const
{
static Component id = m_id++;
static const Component id = m_id.fetch_add(1);
return id;
}

private:
inline static Component m_id;
inline static std::atomic<Component> m_id{0};
std::array<std::unique_ptr<IComponentBox>, MAX_COMPONENTS> m_componentBoxs;
};

Expand Down
9 changes: 9 additions & 0 deletions include/kronkworld/kronkflow/Scheduler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ namespace kw
}
}

Scheduler(Scheduler& other) = delete;
Scheduler(Scheduler&& other) = delete;

~Scheduler()
{
kfScheduler_destroy(sch);
Expand All @@ -52,6 +55,12 @@ namespace kw
return kfScheduler_addTask(sch, opt, delay, interval);
}

// true if the task was found (its clearer has run or will run)
bool remove(kfTaskID id)
{
return kfScheduler_removeTask(sch, id) != 0;
}

private:
kfScheduler* sch;
};
Expand Down
17 changes: 14 additions & 3 deletions include/kronkworld/ressource/RessourceManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "../entity/Entity.hpp"
#include "../component/Component.hpp"
#include <array>
#include <atomic>
#include <cstddef>
#include <memory>
#include <utility>
Expand All @@ -20,13 +21,24 @@
namespace kw
{

namespace detail
{
// One counter for the whole process: a resource type has the same id
// in every World, and two threads can register types at the same time.
inline std::atomic<size_t>& resourceIdCounter(void) noexcept
{
static std::atomic<size_t> counter{0};
return counter;
}
}

class ResourceManager
{
public:
template<typename R>
size_t id(void) noexcept
static size_t id(void) noexcept
{
static size_t id = m_id++;
static const size_t id = detail::resourceIdCounter().fetch_add(1);
return id;
}

Expand Down Expand Up @@ -71,7 +83,6 @@ namespace kw

private:
std::array<std::unique_ptr<IResource>, MAX_RESOURCES> m_resources;
size_t m_id = 0;
};

}
Expand Down
2 changes: 1 addition & 1 deletion include/kronkworld/system/ISystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace kw
bool isDone(void) const { return m_isDone; }

private:
bool m_isDone;
bool m_isDone = false;
};

}
Expand Down
77 changes: 58 additions & 19 deletions include/kronkworld/system/System.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <cstddef>
#include <cstdint>
#include <memory>
#include <new>
#include <utility>
#include <vector>
#include "ISystem.hpp"
Expand All @@ -22,6 +23,23 @@ namespace kw

typedef uint32_t StageId;

// A World owns two schedulers, each one with its own tick counter (so
// "delay" and "interval" count ticks of the schedule the system is in):
// - Fixed: run as many times as needed to catch up with a fixed timestep
// - Frame: run once per rendered frame
// kw doesn't decide when they run: the caller does, with runOnce(schedule).
enum class Schedule : uint8_t { Fixed, Frame };

// Identifies a system that was added, to remove it later
struct SystemHandle {

Schedule schedule = Schedule::Fixed;
kfTaskID id = 0; // 0: no system

explicit operator bool() const { return id != 0; }

};

// static constexpr StageId Startup = 0;

// typedef size_t RunPolicy;
Expand All @@ -40,7 +58,7 @@ namespace kw
class SystemManager
{
public:
SystemManager() : m_scheduler(128) {}
SystemManager() : m_fixed(128), m_frame(128) {}

// void addUpdate(std::unique_ptr<ISystem> system)
// {
Expand All @@ -52,17 +70,20 @@ namespace kw
// m_renderSystems.push_back(std::move(system));
// }

void addSystem(
// Systems of a same stage, due on the same tick, run in the order
// they were added. A system that returns false is not run again.
SystemHandle addSystem(
Schedule schedule,
StageId stage,
std::unique_ptr<ISystem> system,
size_t delay = 1,
size_t interval = 0,
size_t interval = 1,
const RWMask& mask = RWMask(0, 0)
)
{
ISystem* rawSystem = system.release();

m_scheduler.pushTask((kfTaskOpt){
auto id = scheduler(schedule).pushTask((kfTaskOpt){
[](void *ctx, void *arg) -> int {
auto task = static_cast<ISystem *>(arg);
auto ret = task->handle(*static_cast<World *>(ctx));
Expand All @@ -74,29 +95,47 @@ namespace kw
stage,
(kfRWMasks){mask.read_mask, mask.write_mask}},
delay, interval);
if (id == 0) {
delete rawSystem;
throw std::bad_alloc();
}
return SystemHandle{schedule, id};
}

void runOnce(World& world)
// Same as above, in the Fixed schedule
SystemHandle addSystem(
StageId stage,
std::unique_ptr<ISystem> system,
size_t delay = 1,
size_t interval = 1,
const RWMask& mask = RWMask(0, 0)
)
{
// for (auto& ls : m_logicSystems) {
// ls->handle(world);
// }
// for (auto& rs : m_renderSystems) {
// rs->handle(world);
// }
m_scheduler.tick(static_cast<void *>(&world));
// std::erase_if(m_systems, [](const auto& system) {
// return system->isDone();
// });
return addSystem(Schedule::Fixed, stage, std::move(system), delay, interval, mask);
}

// Can be called from a system, including on itself (it then finishes
// its current run and is not run again).
// Returns false if it already ended, or if the handle is empty.
bool removeSystem(const SystemHandle& handle)
{
return handle && scheduler(handle.schedule).remove(handle.id);
}

void runOnce(World& world, Schedule schedule)
{
scheduler(schedule).tick(static_cast<void *>(&world));
}

private:

// std::vector<std::unique_ptr<ISystem>> m_logicSystems;
// std::vector<std::unique_ptr<ISystem>> m_renderSystems;
Scheduler& scheduler(Schedule schedule)
{
return schedule == Schedule::Fixed ? m_fixed : m_frame;
}

// std::vector<std::unique_ptr<ISystem>> m_systems;
Scheduler m_scheduler;
Scheduler m_fixed;
Scheduler m_frame;
};

}
Expand Down
Loading
Loading