Skip to content

Commit

Permalink
keira: add Lua garbage collection
Browse files Browse the repository at this point in the history
keira: add buzzer sounds to asteroids
lib: allocate images in PSRAM
buzzer: fix deadlocks
  • Loading branch information
and3rson committed Mar 11, 2024
1 parent f6182f9 commit 155709a
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 14 deletions.
64 changes: 64 additions & 0 deletions firmware/keira/sdcard/asteroids/asteroids.lua
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,67 @@ end
PRESS_START = resources.load_image(ROOT .. "press_start.bmp")
YOU_ARE_DEAD = resources.load_image(ROOT .. "game_over.bmp")

-- Buzzer sound for shooting.
-- First element of each item is frequency, second is note size.
SHOOT_SOUND = {
{880, 8},
{784, 8},
{698, 8},
{659, 8},
{587, 8},
{523, 8},
{440, 8},
{392, 8},
{349, 8},
{330, 8},
{294, 8},
{262, 8},
}

BOOM_SOUND = {
{ 440, 8 },
{ 392, 8 },
{ 349, 8 },
{ 330, 8 },
{ 294, 8 },
{ 262, 8 },
{ 220, 8 },
{ 196, 8 },
{ 175, 8 },
{ 165, 8 },
{ 147, 8 },
{ 131, 8 },
{ 123, 8 },
{ 110, 8 },
{ 98, 8 },
{ 88, 8 },
}

-- Low-pitch (100-200 Hz) noise.
DEATH_SOUND = {
{ 200, 8 },
{ 100, 8 },
{ 150, 8 },
{ 200, 8 },
{ 100, 8 },
{ 150, 8 },
{ 100, 8 },
{ 150, 8 },
{ 100, 8 },
{ 150, 8 },
{ 50, 8 },
{ 100, 8 },
{ 50, 8 },
{ 100, 8 },
{ 50, 8 },
{ 100, 8 },
{ 50, 8 },
{ 100, 8 },
{ 50, 8 },
{ 100, 8 },
{ 50, 8 },
}

-------------------------------------------------------------------------------
-- Ігрові класи
-------------------------------------------------------------------------------
Expand Down Expand Up @@ -397,6 +458,7 @@ function lilka.update(delta)
bullet.speed_x = ship.speed_x / 2 + display.width * dir_x
bullet.speed_y = ship.speed_y / 2 + display.width * dir_y
table.insert(bullets, bullet)
buzzer.play_melody(SHOOT_SOUND, 400)
end

-- Вихід з гри
Expand Down Expand Up @@ -443,6 +505,7 @@ function lilka.update(delta)
-- Якщо куля влучила в астероїд, то вони обидвоє вмирають
asteroid:kill()
bullet:kill()
buzzer.play_melody(BOOM_SOUND, 400)
end
end
if math.dist(asteroid.x, asteroid.y, ship.x, ship.y) < (asteroid.sprite.width / 3 + ship.width / 2) then
Expand All @@ -452,6 +515,7 @@ function lilka.update(delta)
asteroid:kill()
ship:kill()
game_state = STATES.GAME_OVER
buzzer.play_melody(DEATH_SOUND, 400)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion firmware/keira/src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ App::App(const char* name, uint16_t x, uint16_t y, uint16_t w, uint16_t h) : nam

void App::start() {
Serial.println("Starting app " + String(name));
xTaskCreate(_run, name, 32768, this, 1, &taskHandle);
xTaskCreate(_run, name, 8192, this, 1, &taskHandle);
}

void App::_run(void* data) {
Expand Down
66 changes: 66 additions & 0 deletions firmware/keira/src/apps/lua/lualilka_buzzer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include <csetjmp>

#include "lualilka_buzzer.h"

extern jmp_buf stopjmp;

int lualilka_buzzer_play(lua_State* L) {
// Takes 1 or 2 args: frequency or frequency and duration
int n = lua_gettop(L);
if (n < 1 || n > 2) {
return luaL_error(L, "Очікується 1 або 2 аргументи, отримано %d", n);
}
int freq = luaL_checkinteger(L, 1);
if (n == 1) {
lilka::buzzer.play(freq);
} else {
int duration = luaL_checkinteger(L, 2);
lilka::buzzer.play(freq, duration);
}
return 0;
}

int lualilka_buzzer_playMelody(lua_State* L) {
// Takes 2 arg: table of tones and tempo. Each tone is a table of 2 elements: frequency and duration
// Convert them to array of arrays
int n = lua_gettop(L);
if (n != 2) {
return luaL_error(L, "Очікується 2 аргументи, отримано %d", n);
}
luaL_checktype(L, 1, LUA_TTABLE);
luaL_checktype(L, 2, LUA_TNUMBER);
int size = luaL_len(L, 1);
int tempo = luaL_checkinteger(L, 2);
lilka::Tone melody[size];
for (int i = 0; i < size; i++) {
lua_geti(L, 1, i + 1);
luaL_checktype(L, -1, LUA_TTABLE);
lua_rawgeti(L, -1, 1);
lua_rawgeti(L, -2, 2);
melody[i].frequency = luaL_checkinteger(L, -2);
melody[i].size = luaL_checkinteger(L, -1);
lua_pop(L, 3);
}
lua_pop(L, 2);
lilka::buzzer.playMelody(melody, size, tempo);
return 0;
}

int lualilka_buzzer_stop(lua_State* L) {
lilka::buzzer.stop();
return 0;
}

static const luaL_Reg lualilka_buzzer[] = {
{"play", lualilka_buzzer_play},
{"play_melody", lualilka_buzzer_playMelody},
{"stop", lualilka_buzzer_stop},
{NULL, NULL},
};

int lualilka_buzzer_register(lua_State* L) {
// Create global "buzzer" table that contains all buzzer functions
luaL_newlib(L, lualilka_buzzer);
lua_setglobal(L, "buzzer");
return 0;
}
6 changes: 6 additions & 0 deletions firmware/keira/src/apps/lua/lualilka_buzzer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#pragma once

#include <lua.hpp>
#include <lilka.h>

int lualilka_buzzer_register(lua_State* L);
1 change: 1 addition & 0 deletions firmware/keira/src/apps/lua/lualilka_display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Arduino_GFX* getDrawable(lua_State* L) {
lua_getfield(L, LUA_REGISTRYINDEX, "app");
const App* app = static_cast<App*>(lua_touserdata(L, -1));
lua_pop(L, 1);
return app->canvas;
}

Expand Down
14 changes: 10 additions & 4 deletions firmware/keira/src/apps/lua/luarunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "lualilka_geometry.h"
#include "lualilka_gpio.h"
#include "lualilka_util.h"
#include "lualilka_buzzer.h"
#include "lualilka_state.h"

jmp_buf stopjmp;
Expand All @@ -30,7 +31,7 @@ bool pushLilka(lua_State* L) {

bool callInit(lua_State* L) {
// Calls the "init" function of the "lilka" table.
// Returns false if the function doesn't exist and pops "lilka" from the stack.
// Returns false if the function doesn't exist. Keeps "lilka" on the stack.
lua_getfield(L, -1, "init");
if (!lua_isfunction(L, -1)) {
lua_pop(L, 1);
Expand All @@ -53,7 +54,7 @@ bool callUpdate(lua_State* L, uint32_t delta) {
// Returns false if the function doesn't exist and pops "lilka" from the stack.
lua_getfield(L, -1, "update");
if (!lua_isfunction(L, -1)) {
lua_pop(L, 1);
lua_pop(L, 2);
return false;
}
lua_pushnumber(L, ((float)delta) / 1000.0);
Expand All @@ -75,7 +76,7 @@ bool callDraw(lua_State* L) {
// Returns false if the function doesn't exist and pops "lilka" from the stack.
lua_getfield(L, -1, "draw");
if (!lua_isfunction(L, -1)) {
lua_pop(L, 1);
lua_pop(L, 2);
return false;
}
int retCode = lua_pcall(L, 0, 0, 0);
Expand Down Expand Up @@ -133,6 +134,7 @@ void AbstractLuaRunnerApp::luaSetup(const char* dir) {
lualilka_geometry_register(L);
lualilka_gpio_register(L);
lualilka_util_register(L);
lualilka_buzzer_register(L);

// lilka::serial_log("lua: init canvas");
// lilka::Canvas* canvas = new lilka::Canvas();
Expand Down Expand Up @@ -224,9 +226,12 @@ int AbstractLuaRunnerApp::execute() {
lua_pop(L, 1);
queueDraw();

lua_gc(L, LUA_GCCOLLECT, 0); // TODO: Use LUA_GCSTEP?

// display.renderCanvas(canvas);

// Calculate time spent in update
// Calculate time spent in update & gargage collection
// TODO: Split time spent in update, time spent in draw, time spent in GC?
uint32_t elapsed = millis() - now;
// If we're too fast, delay to keep 30 FPS
if (elapsed < perfectDelta) {
Expand Down Expand Up @@ -467,6 +472,7 @@ void LuaReplApp::run() {
}

int retCode = luaL_loadstring(L, input.c_str()) || execute();
lua_gc(L, LUA_GCCOLLECT, 0); // TODO: Use LUA_GCSTEP?

if (retCode) {
const char* err = lua_tostring(L, -1);
Expand Down
26 changes: 18 additions & 8 deletions sdk/lib/lilka/src/lilka/buzzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ void Buzzer::begin() {
serial_err("Buzzer is not supported on this board");
return;
#else
_stop();
pinMode(LILKA_BUZZER, OUTPUT);
#endif
}
Expand Down Expand Up @@ -55,17 +56,24 @@ void Buzzer::playMelody(const Tone* melody, uint32_t length, uint32_t tempo) {
xSemaphoreTake(buzzerMutex, portMAX_DELAY);
_stop();
currentMelody = static_cast<Tone*>(realloc(currentMelody, length * sizeof(Tone)));
// delete[] currentMelody;
// currentMelody = new Tone[length];
memcpy(currentMelody, melody, length * sizeof(Tone));
currentMelodyLength = length;
currentMelodyTempo = tempo;
xTaskCreate(melodyTask, "melodyTask", 2048, this, 1, &melodyTaskHandle);
// Serial.println("Melody task starting, length: " + String(length) + ", tempo: " + String(tempo) + ", first note frequency: " + String(melody[0].frequency));
if (xTaskCreate(melodyTask, "melodyTask", 2048, this, 1, &melodyTaskHandle) != pdPASS) {
serial_err("Failed to create melody task (not enough memory?)");
}
xSemaphoreGive(buzzerMutex);
#endif
}

void Buzzer::melodyTask(void* arg) {
Buzzer* buzzer = static_cast<Buzzer*>(arg);
// Serial.println("Melody task started");
for (uint32_t i = 0; i < buzzer->currentMelodyLength; i++) {
// Serial.println("Playing note " + String(i) + " with frequency " + String(buzzer->currentMelody[i].frequency));
xSemaphoreTake(buzzer->buzzerMutex, portMAX_DELAY);
Tone currentTone = buzzer->currentMelody[i];
if (currentTone.size == 0) {
Expand All @@ -82,11 +90,9 @@ void Buzzer::melodyTask(void* arg) {
}
xSemaphoreGive(buzzer->buzzerMutex);
vTaskDelay(duration / portTICK_PERIOD_MS);
if (i == buzzer->currentMelodyLength - 1) {
noTone(LILKA_BUZZER);
}
}
buzzer->stop();
noTone(LILKA_BUZZER);
vTaskSuspend(NULL);
}

void Buzzer::stop() {
Expand All @@ -100,11 +106,15 @@ void Buzzer::stop() {
}

void Buzzer::_stop() {
if (melodyTaskHandle != NULL) {
vTaskDelete(melodyTaskHandle);
// TODO: This is not thread-safe
noTone(LILKA_BUZZER);
TaskHandle_t handle = melodyTaskHandle;
if (handle != NULL) {
melodyTaskHandle = NULL;
// This can be called from within the task,
// so we postpone the deletion of the task till the end of the function to avoid a deadlock
vTaskDelete(handle);
}
noTone(LILKA_BUZZER);
}

void Buzzer::playDoom() {
Expand Down
3 changes: 2 additions & 1 deletion sdk/lib/lilka/src/lilka/display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ int16_t Canvas::y() {

Image::Image(uint32_t width, uint32_t height, int32_t transparentColor) :
width(width), height(height), transparentColor(transparentColor) {
pixels = new uint16_t[width * height];
// Allocate pixels in PSRAM
pixels = static_cast<uint16_t*>(ps_malloc(width * height * sizeof(uint16_t)));
}

Image::~Image() {
Expand Down

0 comments on commit 155709a

Please sign in to comment.