Skip to content

Commit 208ac2f

Browse files
committed
Add a frame event loop example
1 parent 7b4c208 commit 208ac2f

File tree

11 files changed

+86
-17
lines changed

11 files changed

+86
-17
lines changed

engine/libtcc1.a

Lines changed: 0 additions & 1 deletion
This file was deleted.

engine/scripts/src/level2.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ PUBLIC(void start())
77
print("Hello from Level 2!\n");
88
int value = gameplay_allowed_function(123);
99
print("Back in Level2! Got result value = ", value, "\n");
10+
11+
// Create a simple event loop
12+
while (true) {
13+
// Wait for a frame to complete
14+
struct {
15+
int frame = 0;
16+
} frame_data;
17+
Game::wait(frame_data);
18+
19+
// Do some work here, like updating game state, rendering, etc.
20+
print("Frame ", frame_data.frame, " in Level2!\n");
21+
}
1022
}
1123

1224
int main() {}

engine/src/main.cpp

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,33 @@ int main()
6767
}
6868

6969
strf::to(stdout)("...\n");
70-
/* Ordinarily a game engine has a physics loop that ticks regularly,
71-
but we don't in this example. Instead we will just sleep until
72-
the next available timer. And resume the event loop in between. */
73-
extern void timers_loop(std::function<void()>);
74-
timers_loop(
75-
[&]
76-
{
77-
/* This should run each engine tick instead. We are passing
78-
the maximum number of instructions that we allow it to use. */
79-
events.resume(5'000);
80-
});
70+
71+
/* Let's create a simple frame loop that depends on the guest
72+
waiting at a certain point in the code, with a pointer argument
73+
to frame data that is updated each frame. */
74+
struct FrameData
75+
{
76+
int frame = 0;
77+
};
78+
/* When the guest pauses, the pointer to the frame data is the first argument (A0). */
79+
FrameData* frame_data = level2.machine().memory.memarray<FrameData> (level2.machine().sysarg(0), 1);
80+
/* Simulate 10 frames of gameplay. */
81+
for (int i = 0; i < 10; ++i)
82+
{
83+
if (level2.getWaitingState() == 0) {
84+
strf::to(stderr)("Level2 did not call Game::wait()!?\n");
85+
return 1;
86+
}
87+
/* Set the data for the current frame.
88+
This is accessible to the guest program when it resumes. */
89+
frame_data->frame = i;
90+
/* Resume the level2 program, which unpauses the guest and continues
91+
execution until it loops around to the Game::wait() call again. */
92+
if (!level2.resume(5'000)) {
93+
strf::to(stderr)("Failed to resume level2!\n");
94+
return 1;
95+
}
96+
}
8197

8298
/* Create an event that is callable. */
8399
struct C

engine/src/script/script.hpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,25 @@ struct Script
255255
/// @return A wrapper managing the program-hosted objects. Can be moved.
256256
template <typename T> GuestObjects<T> guest_alloc(size_t n = 1);
257257

258+
/// @brief Set the waiting state of the script.
259+
/// This can be used to signal that the script is waiting in an event loop,
260+
/// such as waiting for a frame to complete, or waiting for an event to occur.
261+
/// @param state A user-defined state value, such as 0 for no wait, 1 for waiting for a frame, etc.
262+
void setWaitingState(uint8_t state) noexcept
263+
{
264+
m_waiting_state = state;
265+
}
266+
267+
/// @brief Get the current waiting state of the script.
268+
/// This can be used to check if the script is waiting in an event loop,
269+
/// such as waiting for a frame to complete, or waiting for an event to occur.
270+
/// @return The current waiting state value, such as 0 for no wait, 1 for waiting for a frame, etc.
271+
/// @note The waiting state is user-defined and can be any value.
272+
uint8_t getWaitingState() const noexcept
273+
{
274+
return m_waiting_state;
275+
}
276+
258277
/// @brief Start debugging with GDB right now. Only works on Linux.
259278
/// @param message Message to print inside GDB.
260279
/// @param one_up Always go one stack frame up? This can be used to leave a wrapper function.
@@ -322,6 +341,7 @@ struct Script
322341
bool m_stdout = true;
323342
bool m_last_newline = true;
324343
int m_budget_overruns = 0;
344+
uint8_t m_waiting_state = 0;
325345
Script* m_remote_script = nullptr;
326346
/// @brief Functions accessible when remote access is *strict*
327347
std::unordered_set<gaddr_t> m_remote_access;

engine/src/script/script_syscalls.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,10 @@ APICALL(api_machine_hash)
8585
machine.set_result(script(machine).hash());
8686
}
8787

88-
APICALL(api_each_frame)
88+
APICALL(api_frame_wait)
8989
{
90-
machine.set_result(-1);
90+
script(machine).setWaitingState(1);
91+
machine.stop(); // Pause here
9192
}
9293

9394
APICALL(api_game_setting)
@@ -170,7 +171,7 @@ void Script::setup_syscall_interface()
170171
{ECALL_DYNCALL, api_dyncall},
171172
{ECALL_DYNARGS, api_dyncall_args},
172173
{ECALL_MACHINE_HASH, api_machine_hash},
173-
{ECALL_EACH_FRAME, api_each_frame},
174+
{ECALL_FRAME_WAIT, api_frame_wait},
174175

175176
{ECALL_GAME_SETTING, api_game_setting},
176177
{ECALL_GAME_EXIT, api_game_exit},

ext/libriscv

programs/micro/api/api.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ namespace api
2525
static uint32_t current_machine();
2626

2727
static std::optional<intptr_t> setting(std::string_view);
28+
29+
/// @brief Enter a waiting state, and unpausing when the next frame is ready.
30+
/// @tparam T
31+
/// @param data The frame data received from the host every frame.
32+
template <typename T>
33+
static void wait(T& data);
2834
};
2935

3036
/** GUI **/

programs/micro/api/api_impl.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ inline bool Game::is_debugging()
117117
return sys_is_debug();
118118
}
119119

120+
template <typename T>
121+
inline void Game::wait(T& data)
122+
{
123+
sys_frame_wait(&data);
124+
}
125+
126+
120127
/** Timers **/
121128

122129
using timer_callback = void (*)(int, void*);

programs/micro/api/syscalls.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#define ECALL_DYNCALL (GAME_API_BASE+4)
1010
#define ECALL_DYNARGS (GAME_API_BASE+5)
1111
#define ECALL_MACHINE_HASH (GAME_API_BASE+6)
12-
#define ECALL_EACH_FRAME (GAME_API_BASE+7)
12+
#define ECALL_FRAME_WAIT (GAME_API_BASE+7)
1313
#define ECALL_GAME_SETTING (GAME_API_BASE+8)
1414
#define ECALL_GAME_EXIT (GAME_API_BASE+9)
1515

programs/micro/libc/engine.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,10 @@ asm(".global sys_write\n"
2828
" li a7, " STRINGIFY(ECALL_WRITE) "\n"
2929
" ecall\n"
3030
" ret\n");
31+
32+
asm(".global sys_frame_wait\n"
33+
"sys_frame_wait:\n"
34+
" li a7, " STRINGIFY(ECALL_FRAME_WAIT) "\n"
35+
" ecall\n"
36+
" ret\n");
37+

0 commit comments

Comments
 (0)