Skip to content

Commit

Permalink
Modernize Event structure and add a unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
fwsGonzo committed Jan 14, 2024
1 parent 98b39fc commit d4e4a0f
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 20 deletions.
36 changes: 21 additions & 15 deletions engine/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ int main()
/* The event_loop function can be resumed later, and can execute work
that has been preemptively handed to it from other machines. */
auto events = Script("events", "scripts/gameplay.elf", debug);
/* A VM function call. The function must be public (listed in the symbols
* file). */
/* A VM function call. The function is looked up in the symbol table
of the program binary. Without an entry in the table, we cannot
know the address of the function, even if it exists in the code. */
assert(events.address_of("event_loop") != 0x0);
events.call("event_loop");

/* Create the gameplay machine by cloning 'events' (same binary, but new instance) */
Expand Down Expand Up @@ -65,20 +67,21 @@ int main()
timers_loop(
[&]
{
/* This guy should run each engine tick instead. We are passing
the maximum number of instructions that we allow it to use.
This can work well as a substitute for time spent, provided
each complex system call increments the counter sufficiently. */
/* This should run each engine tick instead. We are passing
the maximum number of instructions that we allow it to use. */
events.resume(5'000);
});

/* Pass some non-trivial parameters to a VM function call. */
/* Create an event that is callable. */
struct C
{
char c = 'C';
};
Event<void(const std::string&, const C&, const std::string&)> my_event(gameplay, "cpp_function");

/* Pass some non-trivial parameters to the function call. */
my_event.call("Hello", C {}, "World");

gameplay.call("cpp_function", "Hello", C {}, "World");

strf::to(stdout)("...\n");

Expand All @@ -94,8 +97,11 @@ int main()
EmbeddedString<32> name;
bool alive = false;

Event onDeath;
Event onAction;
// Since this is shared between host and Script,
// we do not use Event here, as it would contain
// host pointers, which we don't want to share.
Script::gaddr_t onDeath = 0x0;
Script::gaddr_t onAction = 0x0;

static Script::gaddr_t address(size_t i)
{
Expand Down Expand Up @@ -130,21 +136,21 @@ int main()
auto& obj = guest_objs.at(0);
obj.alive = true;
obj.name = "myobject";
obj.onDeath = Event(gameplay, "myobject_death");
obj.onDeath = gameplay.address_of("myobject_death");

/* Simulate object dying */
strf::to(stdout)(
"Calling '", obj.onDeath.function(), "' in '",
obj.onDeath.script().name(), "' for object at 0x",
"Calling '", gameplay.symbol_name(obj.onDeath), "' in '",
gameplay.name(), "' for object at 0x",
strf::hex(guest_objs.address(0)), "\n");
assert(obj.alive == true);
obj.onDeath.call(guest_objs.address(0));
gameplay.call(obj.onDeath, guest_objs.address(0));
assert(obj.alive == false);

/* Guest-allocated objects can be moved */
auto other_guest_objs = std::move(guest_objs);
other_guest_objs.at(0).alive = true;
other_guest_objs.at(0).onDeath.call(other_guest_objs.address(0));
gameplay.call(other_guest_objs.at(0).onDeath, other_guest_objs.address(0));
assert(other_guest_objs.at(0).alive == false);

strf::to(stdout)("...\n");
Expand Down
31 changes: 26 additions & 5 deletions engine/src/script/event.hpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
#pragma once
#include "script.hpp"

/// @brief Create a wrapper for a function call, matching the
/// function type F into a given Script instance, for the given function.
/// @tparam F A function type, eg. void(int)
template <typename F>
struct Event
{
Event() = default;
Event(Script&, const std::string& func);
Event(Script&, Script::gaddr_t address);

template <typename... Args> long call(Args&&... args);
template <typename... Args> auto call(Args&&... args);

bool is_callable() const noexcept
{
Expand Down Expand Up @@ -41,16 +46,32 @@ struct Event
Script::gaddr_t m_addr = 0;
};

inline Event::Event(Script& script, const std::string& func)
template <typename F>
inline Event<F>::Event(Script& script, const std::string& func)
: m_script(&script), m_addr(script.address_of(func))
{
}

template <typename... Args> inline long Event::call(Args&&... args)
template <typename F>
inline Event<F>::Event(Script& script, Script::gaddr_t address)
: m_script(&script), m_addr(address)
{
}

template <typename F>
template <typename... Args> inline auto Event<F>::call(Args&&... args)
{
static_assert(std::is_invocable_v<F, Args...>);
using Ret = decltype((F*){}(args...));

if (is_callable())
{
return script().call(address(), std::forward<Args>(args)...);
script().call(address(), std::forward<Args>(args)...);
if constexpr (std::is_same_v<void, Ret>)
return;
else
return script().machine().template return_value<Ret>();
}
return -1;
if constexpr (!std::is_same_v<void, Ret>)
return Ret(-1);
}
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ endfunction()

add_unit_test(basic basic.cpp)
add_unit_test(evloop event_loop.cpp)
add_unit_test(events events.cpp)
76 changes: 76 additions & 0 deletions tests/events.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include "codebuilder.hpp"
#include <script/event.hpp>

TEST_CASE("Simple events", "[Events]")
{
const auto program = build_and_load(R"M(
#include <api.h>
int main() {}
extern "C" unsigned MyFunc() {
return 0xDEADBEEF;
}
extern "C" int IntFunc(int arg) {
return arg * 2;
}
extern "C" int StrFunc(const char *arg1, int arg2) {
assert(strcmp(arg1, "Hello World!") == 0);
assert(arg2 == 123);
return arg2 * 2;
}
struct Data {
char arg1[32];
int arg2;
};
extern "C" int DataFunc(Data& data) {
assert(strcmp(data.arg1, "Hello World!") == 0);
assert(data.arg2 == 123);
return data.arg2 * 2;
}
extern "C" int WriteDataFunc(Data& data) {
strcpy(data.arg1, "Hello World!");
data.arg2 = 123;
return data.arg2 * 2;
}
)M");

Script script {program, "MyScript", "/tmp/myscript"};

/* Function that returns a value */
Event<unsigned()> ev0(script, "MyFunc");
REQUIRE(ev0.call() == 0xDEADBEEF);

/* Function that takes an integer */
Event<int(int)> ev1(script, "IntFunc");
REQUIRE(ev1.call(123) == 246);

/* Function that takes a string and an integer */
Event<int(const std::string&, int)> ev2(script, "StrFunc");
REQUIRE(ev2.call("Hello World!", 123) == 246);

/* Function that takes a zero-terminated string and an integer */
Event<int(const char* str, int)> ev3(script, "StrFunc");
REQUIRE(ev3.call("Hello World!", 123) == 246);

/* Function that takes a struct */
struct Data {
char arg1[32];
int arg2;
} data {.arg1 = "Hello World!", .arg2 = 123};
Event<int(const Data&)> ev4(script, "DataFunc");

REQUIRE(ev4.call(data) == 246);
REQUIRE(ev4.call(Data{"Hello World!", 123}) == 246);

/* Function that modifies a struct, which we will read back */
auto obj = script.guest_alloc<Data>();
Event<int(Script::gaddr_t)> ev5(script, "WriteDataFunc");

/* Call function with the address of our shared object */
REQUIRE(ev5.call(obj.address(0)) == 246);

/* Verify that the script correctly modified the struct */
REQUIRE(std::string(obj.at(0).arg1) == "Hello World!");
REQUIRE(obj.at(0).arg2 == 123);
}

0 comments on commit d4e4a0f

Please sign in to comment.