I have been trying to get an app working on my pebble for a while now and I finally got it to work. It's a sports counter that keeps track of the score for both teams and also has a built in stop watch in the middle. Everything works as it should but if the stopwatch runs for 42 seconds then the font for the counters become very small. I have no idea whats going on.
When I was looking around someone said it may be a memory leak, but this was not similar to my problem.
#include <pebble.h>
#define COUNTER_FONT_49 RESOURCE_ID_MACHINE_GUN_49
#define STOPWATCH_FONT_24 RESOURCE_ID_SPORTS_WORLD_24
#define HOME_AWAY_FONT_18 FONT_KEY_GOTHIC_18_BOLD
//---Counter Constants---
#define COUNTER_START 0
#define COUNTER_MAX 9999
#define COUNTER_MIN -9999
#define MAX_DIGITS 4
//---Interface Variables---
Window* window;
static Layer* layer;
static GFont counterFont;
static GFont stopwatchFont;
static TextLayer* teamAScore_layer;
static TextLayer* teamBScore_layer;
static TextLayer* big_time_layer;
static TextLayer* seconds_time_layer;
static TextLayer* home_away_layer;
//---Counter Variables---
char teamA_counter_text[MAX_DIGITS + 2 /* sign & \0 */];
char teamB_counter_text[MAX_DIGITS + 2 /* sign & \0 */];
int teamACounter;
int teamBCounter;
int singleClickIncrement;
int longClickIncrement;
int doubleClickIncrement;
//The Time
static double elapsed_time = 0;
static bool started = false;
static AppTimer* update_timer = NULL;
static double start_time = 0;
static double pause_time = 0;
time_t time_seconds();
void stop_stopwatch();
void start_stopwatch();
void handle_timer(void* data);
void update_stopwatch();
double float_time_ms() {
time_t seconds;
uint16_t milliseconds;
time_ms(&seconds, &milliseconds);
return (double)seconds + ((double)milliseconds / 1000.0);
}
void stop_stopwatch() {
started = false;
pause_time = float_time_ms();
if(update_timer != NULL) {
app_timer_cancel(update_timer);
update_timer = NULL;
}
}
void start_stopwatch() {
started = true;
if(start_time == 0) {
start_time = float_time_ms();
} else if(pause_time != 0) {
double interval = float_time_ms() - pause_time;
start_time += interval;
}
update_timer = app_timer_register(100, handle_timer, NULL);
}
void update_stopwatch() {
static char big_time[] = "00:00";
static char deciseconds_time[] = ".0";
static char seconds_time[] = ":00";
// Now convert to hours/minutes/seconds.
int tenths = (int)(elapsed_time * 10) % 10;
int seconds = (int)elapsed_time % 60;
int minutes = (int)elapsed_time / 60 % 60;
int hours = (int)elapsed_time / 3600;
// We can't fit three digit hours, so stop timing here.
if(hours > 99) {
stop_stopwatch();
return;
}
if(hours < 1) {
snprintf(big_time, 6, "%02d:%02d", minutes, seconds);
snprintf(deciseconds_time, 3, ".%d", tenths);
} else {
snprintf(big_time, 6, "%02d:%02d", hours, minutes);
snprintf(seconds_time, 4, ":%02d", seconds);
}
// Now draw the strings.
text_layer_set_text(big_time_layer, big_time);
text_layer_set_text(seconds_time_layer, hours < 1 ? deciseconds_time : seconds_time);
}
void select_click_long_handler(ClickRecognizerRef recognizer, Window *window) { //pressed SELECT LONG
bool is_running = started;
stop_stopwatch();
start_time = 0;
elapsed_time = 0;
if(is_running) start_stopwatch();
update_stopwatch();
}
static void select_click_handler(ClickRecognizerRef recognizer, void *context) { //pressed SELECT
if(started) {
stop_stopwatch();
} else {
start_stopwatch();
}
}
void handle_timer(void* data) {
if(started) {
double now = float_time_ms();
elapsed_time = now - start_time;
update_timer = app_timer_register(100, handle_timer, NULL);
}
update_stopwatch();
}
static int increment_value(int team_score, const int increment){
if(team_score + increment <= COUNTER_MAX && team_score + increment >= COUNTER_MIN)
team_score = team_score + increment;
return team_score;
}
static void up_multi_click_handler(ClickRecognizerRef recognizer, void *context) { //pressed UP MULTI
teamACounter = increment_value(teamACounter, doubleClickIncrement);
layer_mark_dirty(layer);
}
static void up_click_long_handler(ClickRecognizerRef recognizer, void *context) { //pressed UP LONG
teamACounter = increment_value(teamACounter, longClickIncrement);
layer_mark_dirty(layer);
}
static void up_click_handler(ClickRecognizerRef recognizer, void *context) { //pressed UP
teamACounter = increment_value(teamACounter, singleClickIncrement);
layer_mark_dirty(layer);
}
static void down_multi_click_handler(ClickRecognizerRef recognizer, void *context) { //pressed DOWN MULTI
teamBCounter = increment_value(teamBCounter, doubleClickIncrement);
layer_mark_dirty(layer);
}
static void down_click_long_handler(ClickRecognizerRef recognizer, void *context) { //pressed DOWN LONG
teamBCounter = increment_value(teamBCounter, longClickIncrement);
layer_mark_dirty(layer);
}
static void down_click_handler(ClickRecognizerRef recognizer, void *context) { //pressed DOWN
teamBCounter = increment_value(teamBCounter, singleClickIncrement);
layer_mark_dirty(layer);
}
static void click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
window_long_click_subscribe(BUTTON_ID_SELECT, 700, (ClickHandler) select_click_long_handler, NULL);
window_single_click_subscribe(BUTTON_ID_UP, up_click_handler);
window_long_click_subscribe(BUTTON_ID_UP, 700, (ClickHandler)up_click_long_handler, NULL);
window_multi_click_subscribe(BUTTON_ID_UP, 2, 10, 0, true, up_multi_click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler);
window_long_click_subscribe(BUTTON_ID_DOWN, 700, (ClickHandler)down_click_long_handler, NULL);
window_multi_click_subscribe(BUTTON_ID_DOWN, 2, 10, 0, true, down_multi_click_handler);
}
static void update_layer(Layer *layer, GContext *ctx){
GRect bounds = layer_get_frame(layer);
counterFont = fonts_load_custom_font(resource_get_handle(COUNTER_FONT_49));
graphics_context_set_text_color(ctx, GColorBlack);
snprintf(teamA_counter_text, (MAX_DIGITS + 2)*sizeof(char), "%d", teamACounter);
graphics_draw_text(ctx,
teamA_counter_text,
counterFont,
GRect(0, 0, bounds.size.w - 10, 60),
GTextOverflowModeWordWrap,
GTextAlignmentCenter,
NULL);
snprintf(teamB_counter_text, (MAX_DIGITS + 2)*sizeof(char), "%d", teamBCounter);
graphics_draw_text(ctx,
teamB_counter_text,
counterFont,
GRect(0, 95, bounds.size.w - 10, 60),
GTextOverflowModeWordWrap,
GTextAlignmentCenter,
NULL);
}
static void window_load(Window *window) {
// Get the root layer
Layer *window_layer = window_get_root_layer(window);
// Get the bounds of the window for sizing the text layer
GRect bounds = layer_get_bounds(window_layer);
layer = layer_create(bounds);
layer_set_update_proc(layer, update_layer);
layer_add_child(window_layer, layer);
}
static void window_unload(Window *window) {
// Destroy TextLayer
text_layer_destroy(teamAScore_layer);
text_layer_destroy(teamBScore_layer);
text_layer_destroy(big_time_layer);
text_layer_destroy(seconds_time_layer);
}
void handle_init(void) {
window = window_create();
window_set_click_config_provider(window, click_config_provider);
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
//Set the counters start
teamACounter = COUNTER_START;
teamBCounter = COUNTER_START;
//---------------------------------------------TEMPERARY INCREMENT
singleClickIncrement = 1;
doubleClickIncrement = 2;
longClickIncrement = 3;
Layer *root_layer = window_get_root_layer(window);
stopwatchFont = fonts_load_custom_font(resource_get_handle(STOPWATCH_FONT_24));
//-----Display Stop Watch-----
big_time_layer = text_layer_create(GRect(0, 65, 86, 35));
text_layer_set_text_alignment(big_time_layer, GTextAlignmentRight);
text_layer_set_background_color(big_time_layer, GColorClear);
text_layer_set_text(big_time_layer, "00:00");
text_layer_set_font(big_time_layer, stopwatchFont);
layer_add_child(root_layer, (Layer*)big_time_layer);
seconds_time_layer = text_layer_create(GRect(86, 65, 49, 35));
text_layer_set_text(seconds_time_layer, ".0");
text_layer_set_background_color(seconds_time_layer, GColorClear);
text_layer_set_font(seconds_time_layer, stopwatchFont);
layer_add_child(root_layer, (Layer*)seconds_time_layer);
//-----Display Home and Away-----
home_away_layer = text_layer_create(GRect(124, 0, 20, 152));
text_layer_set_text(home_away_layer, "\n H\n\n\n\n\n A");
text_layer_set_background_color(home_away_layer, GColorBlack);
text_layer_set_text_color(home_away_layer, GColorWhite);
text_layer_set_font(home_away_layer, fonts_get_system_font(HOME_AWAY_FONT_18));
layer_add_child(root_layer, (Layer*)home_away_layer);
const bool animated = true;
window_stack_push(window, animated);
}
void handle_deinit(void) {
window_destroy(window);
}
int main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
You're loading the custom font into memory each time the update_layer function is called. Eventually the app runs out of memory and can't load the font any more, which is why it uses the fallback (really small) font.
You should just load the custom font once, in your init function.
Related
Can someone help me to increase the length of the snake and make it follow the head ?
I tried to develop a simple snake game without videos or help and did almost great but cannot figure out how to increase the size of the snake and move it properly.
I'd be very thankful
#include <SDL2/SDL.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#define LOG(x) printf("%s\n", x)
#define LOG_ERROR(x) fprintf(stderr, "Error: %s\n", x)
#define LOG_SDL_ERROR(x) fprintf(stderr, "%s: %s\n", x, SDL_GetError())
#define global_variable static
#define internal_function static
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define HEAD_SIZE 30
#define APPLE_SIZE 25
#define MAX_LENGHT 10
typedef const char* string;
typedef float realNum;
typedef struct Snake
{
SDL_Rect body[MAX_LENGHT];
int xSpeed;
int ySpeed;
int site;
realNum xPos;
realNum yPos;
} Snake;
typedef struct Apple
{
int xPos;
int yPos;
int size;
} Apple;
void checkColissions(Apple *apple, Snake *snake );
bool initGame(void);
void update(realNum);
void handleEvent(SDL_Event);
void renderGame(void);
void gameOver(void);
void shutdownGame(void);
Snake makeSnake(void);
void renderSnake(Snake *snake);
void updateSnake(Snake *, realNum);
Apple makeApple();
void renderApple(Apple *);
global_variable bool Running;
global_variable SDL_Window *g_pWindow;
global_variable SDL_Renderer *g_pRenderer;
Snake *g_pSnake = NULL;
Apple *g_pApple = NULL;
int
main(int argc, char const *argv[])
{
srand((unsigned int)time(NULL));
atexit(shutdownGame);
if (!initGame())
{
LOG_ERROR("Failed Initialization");
exit(1);
}
else
{
Running = true;
int fps = 330;
int desiredDelta = 1000/fps;
SDL_Event event;
while (Running)
{
renderGame();
update(desiredDelta);
handleEvent(event);
SDL_Delay(rand() % 30);
}
}
return (EXIT_SUCCESS);
}
bool
initGame()
{
if (SDL_Init(SDL_INIT_VIDEO) != 0)
{
LOG_SDL_ERROR("Failed initialization: ");
return (false);
}
else
{
g_pWindow =
SDL_CreateWindow("Snake", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
g_pRenderer =
SDL_CreateRenderer(g_pWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
g_pSnake = (Snake*)malloc(sizeof(Snake));
g_pApple = (Apple*)malloc(sizeof(Apple));
*g_pSnake = makeSnake();
*g_pApple = makeApple();
if ((g_pApple || g_pSnake || g_pRenderer || g_pWindow) == NULL)
{
return (false);
}
g_pWindow == NULL ? LOG_ERROR("Window failed") : LOG("Succes Window");
g_pRenderer == NULL ? LOG_ERROR("Renderer failed") : LOG("Succes Renderer");
g_pApple == NULL ? LOG_ERROR("Apple failed") : LOG("Succes Apple");
g_pSnake == NULL ? LOG_ERROR("Snake failed") : LOG("Succes Snake");
LOG("Game initialized");
}
return true;
}
void
renderGame()
{
SDL_SetRenderDrawColor(g_pRenderer, 0, 80, 0, 255);
SDL_RenderClear(g_pRenderer);
renderSnake(g_pSnake);
renderApple(g_pApple);
SDL_RenderPresent(g_pRenderer);
}
void update(realNum elapsed)
{
updateSnake(g_pSnake, elapsed);
checkColissions(g_pApple, g_pSnake);
}
void
handleEvent(SDL_Event event)
{
SDL_PollEvent(&event);
switch (event.type)
{
case SDL_QUIT:
{
Running = false;
} break;
default:
break;
}
}
void
shutdownGame()
{
if (g_pWindow)
{
SDL_DestroyWindow(g_pWindow);
}
if (g_pRenderer)
{
SDL_DestroyRenderer(g_pRenderer);
}
if (g_pApple)
{
free(g_pApple);
}
if (g_pSnake)
{
free(g_pSnake);
}
SDL_Quit();
LOG("Game shutdowned");
}
Snake makeSnake(void)
{
const int speed = 0;
Snake snake = {
.xSpeed = speed,
.ySpeed = speed,
.xPos = SCREEN_WIDTH / 2,
.yPos = SCREEN_HEIGHT / 2,
.site = HEAD_SIZE
};
SDL_Rect SnakeRect = {
.h = snake.site,
.w = snake.site,
.x = snake.xPos,
.y = snake.yPos
};
snake.body[0] = SnakeRect;
return (snake);
}
void renderSnake(Snake *snake)
{
for (int i = 0; i < MAX_LENGHT; i++)
{
SDL_SetRenderDrawColor(g_pRenderer, 0, 0, 100, 255);
SDL_RenderFillRect(g_pRenderer, &snake->body[i]);
}
}
void updateSnake(Snake *snake, realNum elapsed)
{
const Uint8 *keyboardState = SDL_GetKeyboardState(NULL);
for (int i = 0; i < MAX_LENGHT; i++)
{
/* code */
snake->body[i].y += snake->ySpeed * elapsed;
snake->body[i].x += snake->xSpeed * elapsed;
}
/* code */
/* code */
/* code */
if (keyboardState[SDL_SCANCODE_W])
{
snake->ySpeed = -1;
snake->xSpeed = 0;
}
if (keyboardState[SDL_SCANCODE_D])
{
snake->ySpeed = 0;
snake->xSpeed = 1;
}
if (keyboardState[SDL_SCANCODE_A])
{
snake->ySpeed = 0;
snake->xSpeed = -1;
}
if (keyboardState[SDL_SCANCODE_S])
{
snake->ySpeed = 1;
snake->xSpeed = 0;
}
if (snake->body->x < 0 - HEAD_SIZE)
{
snake->body->x = SCREEN_WIDTH + HEAD_SIZE;
}
if (snake->body->x > SCREEN_WIDTH + HEAD_SIZE)
{
snake->body->x = 0 - HEAD_SIZE;
}
if (snake->body->y < 0 - HEAD_SIZE)
{
snake->body->y = SCREEN_HEIGHT + HEAD_SIZE;
}
if (snake->body->y > SCREEN_HEIGHT + HEAD_SIZE)
{
snake->body->y = 0 - HEAD_SIZE;
}
}
void
checkColissions(Apple *apple, Snake *snake )
{
SDL_Rect appleRect = {
.h = APPLE_SIZE,
.w = APPLE_SIZE,
.x = apple->xPos,
.y = apple->yPos
};
if (SDL_HasIntersection(&appleRect, snake->body))
{
*g_pApple = makeApple();
};
}
Can someone help me to increase the length of the snake and make it follow the head ?
Snake is defined by an array of coordinates, not just one (x,y). You need some kind of circular buffer, that can change capacity. Or you can just use an array, and then change every element of the array with every snake's move. And then you would realize that circular buffer is a really neat idea.
Once you have your buffer, be it an array or circular buffer, the question becomes how to move and enlarge the snake in it. It is simple. To move the snake you add one coordinate to the head, and remove one from the tail of the buffer. When the snake should become bigger you just don't remove the last element in the buffer. Add to the head, but don't remove the tail. This is how circular buffer would be handled. In case of a plain array moving means overwriting everything, and enlarging means adding to the beginning, and moving everything else by one place.
I have ported it, but it doesn't work . It compiled successfully but the board doesn't work and the app named nRF Mesh can't find the Unprovisioned device (the board), if I delete these codes about porting nimble then the board works I use a project like this https://github.com/InfiniTimeOrg/InfiniTime (for reference)
Here is the function I call in my main() to init nimble:
void nimble_port_init(void) {
void os_msys_init(void);
void ble_store_ram_init(void);
ble_npl_eventq_init(&g_eventq_dflt);
os_msys_init();
ble_hs_init();
ble_store_ram_init();
int res;
res = hal_timer_init(5, NULL);
ASSERT(res == 0);
res = os_cputime_init(32768);
ASSERT(res == 0);
ble_ll_init();
ble_hci_ram_init();
ble_svc_gap_init ();
ble_svc_gatt_init();
bt_mesh_register_gatt();
nimble_port_freertos_init(BleHost);
}
Here is some functions about porting and ble mesh:
(1) about controller/host task and mesh_init and provision
void
nimble_port_freertos_init(TaskFunction_t host_task_fn)
{
#if NIMBLE_CFG_CONTROLLER
/*
* Create task where NimBLE LL will run. This one is required as LL has its
* own event queue and should have highest priority. The task function is
* provided by NimBLE and in case of FreeRTOS it does not need to be wrapped
* since it has compatible prototype.
*/
xTaskCreate(nimble_port_ll_task_func, "ll", configMINIMAL_STACK_SIZE + 400,
NULL, configMAX_PRIORITIES - 1, &ll_task_h);
#endif
/*
* Create task where NimBLE host will run. It is not strictly necessary to
* have separate task for NimBLE host, but since something needs to handle
* default queue it is just easier to make separate task which does this.
*/
xTaskCreate(host_task_fn, "ble", configMINIMAL_STACK_SIZE + 400,
NULL, tskIDLE_PRIORITY + 1, &host_task_h);
}
void
mesh_initialized(TaskFunction_t mesh_task_fn)
{
xTaskCreate(mesh_task_fn,"mesh",configMINIMAL_STACK_SIZE + 400, NULL,
tskIDLE_PRIORITY + 1, &mesh_task_h);
}
void BleHost(void*) {
Int_Pub();
InitPrepare();
nimble_port_run();
}
void Int_Pub(void)
{
bt_mesh_pub_msg_health_pub = NET_BUF_SIMPLE(1 + 3 +0);
bt_mesh_pub_msg_gen_data_pub_srv = NET_BUF_SIMPLE(2 + 2);
bt_mesh_pub_msg_gen_data_pub_cli = NET_BUF_SIMPLE(2 + 2);
health_pub.msg = bt_mesh_pub_msg_health_pub;
gen_data_pub_srv.msg = bt_mesh_pub_msg_gen_data_pub_srv;
gen_data_pub_cli.msg = bt_mesh_pub_msg_gen_data_pub_cli;
}
void InitPrepare(void)
{
health_pub_init();
//os_mempool_module_init();
ble_hs_cfg.reset_cb = blemesh_on_reset;
ble_hs_cfg.sync_cb = blemesh_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* ble_svc_gap_init();
ble_svc_gatt_init(); */
}
void nimble_port_run(void)
{
struct ble_npl_event *ev;
while (1) {
ev = ble_npl_eventq_get(&g_eventq_dflt, BLE_NPL_TIME_FOREVER);
ble_npl_event_run(ev);
}
}
static void blemesh_on_sync(void)
{
int err;
ble_addr_t addr;
NRF_LOG_INFO("Bluetooth initialized\n");
err = ble_hs_id_gen_rnd(1,&addr);
assert(err == 0);
err = ble_hs_id_set_rnd(addr.val);
assert(err == 0);
err = bt_mesh_init(addr.type,&prov,&comp);
//err = bt_mesh_init(0,&prov,&comp);
if(err)
{
NRF_LOG_INFO("Initializing mesh failed (err %d)\n", err);
return;
}
bt_mesh_prov_enable(BT_MESH_PROV_ADV | BT_MESH_PROV_GATT);
if(IS_ENABLED(CONFIG_SETTINGS))
{
settings_load();
}
if(bt_mesh_is_provisioned()){
NRF_LOG_INFO("Mesh network restored from flash\n");
}
mesh_initialized(Ble_Mesh_Adv_Task);
NRF_LOG_INFO("Mesh initialized\n");
}
(2)some interrupt handlers and ...
void RADIO_IRQHandler(void) {
((void (*)(void)) radio_isr_addr)();
}
void RNG_IRQHandler(void) {
((void (*)(void)) rng_isr_addr)();
}
void RTC0_IRQHandler(void) {
((void (*)(void)) rtc0_isr_addr)();
}
void WDT_IRQHandler(void) {
nrf_wdt_event_clear(NRF_WDT_EVENT_TIMEOUT);
}
void npl_freertos_hw_set_isr(int irqn, void (*addr)(void)) {
switch (irqn) {
case RADIO_IRQn:
radio_isr_addr = addr;
break;
case RNG_IRQn:
rng_isr_addr = addr;
break;
case RTC0_IRQn:
rtc0_isr_addr = addr;
break;
}
}
uint32_t npl_freertos_hw_enter_critical(void) {
uint32_t ctx = __get_PRIMASK();
__disable_irq();
return (ctx & 0x01);
}
void npl_freertos_hw_exit_critical(uint32_t ctx) {
if (!ctx) {
__enable_irq();
}
}
extern struct ble_npl_eventq g_eventq_dflt;
I was working in a University C project (first sem), in which our group wanted to make a simple pong game, we decided to use raylib, as it seemed easy. But here is the problem, in the following code:
void UpdatePad(Pad* pad)
{
int height = GetScreenHeight();
if (IsKeyDown(pad->Scheme.DownButton)) {
printf("Down = %d\n", pad->Scheme.DownButton);
pad->Position.y += GetFrameTime() * pad->Speed;
if ( pad->Position.y + pad->Size.y/2 > height ) {
pad->Position.y = height - pad->Size.y /2;
}
}
if (IsKeyDown(pad->Scheme.UpButton)) {
printf("Up = %d\n", pad->Scheme.UpButton);
pad->Position.y -= GetFrameTime() * pad -> Speed;
if (pad->Position.y - pad->Size.y/2 < 0 ) {
pad->Position.y = pad->Size.y /2;
}
}
}
The function IsKeyDown of raylib always returns true, whatever I do. Even if I replace pad->Scheme.DownButton to KEY_UP or something, it always returns true and executes the code block, Is there any solution for this?
Full script is:
#include <raylib.h>
#include <stdio.h>
#include "pad.h"
#include "main.h"
void DrawPad(Pad* pad);
void UpdatePad(Pad* pad);
void Update();
void DrawUpdate();
void Loop();
int main()
{
int screenWidth = 800;
int screenHeight = 450;
InitWindow(screenWidth, screenHeight, "Table Tennis");
SetTargetFPS(12);
Loop();
return 0;
}
void Loop()
{
while (!WindowShouldClose()) {
DrawUpdate();
}
}
void UpdatePad(Pad* pad)
{
int height = GetScreenHeight();
if (IsKeyDown(pad->Scheme.DownButton)) {
printf("Down = %d\n", pad->Scheme.DownButton);
pad->Position.y += GetFrameTime() * pad->Speed;
if ( pad->Position.y + pad->Size.y/2 > height ) {
pad->Position.y = height - pad->Size.y /2;
}
}
if (IsKeyDown(pad->Scheme.UpButton)) {
printf("Up = %d\n", pad->Scheme.UpButton);
pad->Position.y -= GetFrameTime() * pad -> Speed;
if (pad->Position.y - pad->Size.y/2 < 0 ) {
pad->Position.y = pad->Size.y /2;
}
}
}
void DrawPad(Pad* pad)
{
DrawRectangle(pad->Position.x, pad->Position.y - (pad->Size.y /2), pad->Size.x, pad->Size.y, WHITE);
}
void DrawUpdate()
{
const char* scoreLeft = TextFormat("%d", 10);
int scoreSizeLeft = MeasureText(scoreLeft, 20);
InputScheme Input = { .DownButton = KEY_S, .UpButton = KEY_W };
Vector2 paddySize = { .x = 5, .y = 50 };
Vector2 paddyPos = { .x = GetScreenWidth() - paddySize.x , .y = GetScreenHeight() - paddySize.y };
Pad pad = { .Size = paddySize, .Speed = 50, .Scheme = Input , .Position = paddyPos };
Vector2 from = {.x = (GetScreenWidth() / (float) 2), .y = 5};
Vector2 to = { .x = (GetScreenWidth() / (float) 2), .y = ( GetScreenHeight() - (float) 5 ) };
UpdatePad(&pad);
BeginDrawing();
ClearBackground(BLACK);
DrawLineEx(from, to, 2, LIGHTGRAY);
DrawText(scoreLeft, (GetScreenWidth()/2) - 10 -scoreSizeLeft, 10, 20, LIGHTGRAY);
DrawPad(&pad);
EndDrawing();
}
The pad is:-
#include <raylib.h>
typedef struct {
int UpButton;
int DownButton;
} InputScheme;
typedef struct {
InputScheme Scheme;
int Score;
float Speed;
Vector2 Position;
Vector2 Size;
} Pad;
I'm trying to create a libretro core. It will be a standalone game, so I'm setting RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME to true. The documentation suggests that retro_get_memory_* can be used to have data saved without needing to explicitly query RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
The save directory should be used to
store SRAM, memory cards, high scores, etc, if the libretro core
cannot use the regular memory interface (retro_get_memory_data()).
How should the core trigger the saving of data using this interface? Or am I misunderstanding the documentation?
I would expect the frontend to call retro_get_memory_{data,size}, read from the exposed buffer when stopping the core, persist the data to disk, and write it back to the exposed buffer the next time the core starts. Instead I observe:
If I don't provide a content file, the frontend never calls retro_get_memory_{data,size}.
If I provide a content file (which is unused), the frontend calls retro_get_memory_{data,size} after retro_load_game but doesn't write to disk.
Note that this question is about save files (automatically persisted data, usually capturing the player's progress), not save states (snapshots of the game state triggered by the user) which are implemented by the *serialize* methods.
Here is a simple example to reproduce the issue (based on this sample):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libretro.h"
static unsigned char c = 0;
void* retro_get_memory_data(unsigned id) {
fprintf(stderr, "retro_get_memory_data(%d)\n", id);
return (id == RETRO_MEMORY_SAVE_RAM) ? &c : NULL;
}
size_t retro_get_memory_size(unsigned id) {
fprintf(stderr, "retro_get_memory_size(%d)\n", id);
return (id == RETRO_MEMORY_SAVE_RAM) ? 1 : 0;
}
#define WIDTH 320
#define HEIGHT 240
static uint32_t* frame_buf;
void retro_init(void) { frame_buf = calloc(WIDTH * HEIGHT, sizeof(uint32_t)); }
void retro_deinit(void) {
free(frame_buf);
frame_buf = NULL;
}
unsigned retro_api_version(void) { return RETRO_API_VERSION; }
void retro_get_system_info(struct retro_system_info* info) {
memset(info, 0, sizeof(*info));
info->library_name = "SaveTest";
info->library_version = "v1";
info->need_fullpath = false;
info->valid_extensions = NULL; // Anything is fine, we don't care.
}
static retro_video_refresh_t video_cb;
static retro_environment_t environ_cb;
static retro_input_poll_t input_poll_cb;
static retro_input_state_t input_state_cb;
void retro_set_input_poll(retro_input_poll_t cb) { input_poll_cb = cb; }
void retro_set_input_state(retro_input_state_t cb) { input_state_cb = cb; }
void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; }
void retro_set_environment(retro_environment_t cb) {
environ_cb = cb;
bool no_content = true;
cb(RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME, &no_content);
}
void retro_get_system_av_info(struct retro_system_av_info* info) {
float aspect = (float)WIDTH / HEIGHT;
info->timing = (struct retro_system_timing){
.fps = 60.0,
.sample_rate = 0.0,
};
info->geometry = (struct retro_game_geometry){
.base_width = WIDTH,
.base_height = HEIGHT,
.max_width = WIDTH,
.max_height = HEIGHT,
.aspect_ratio = aspect,
};
}
unsigned retro_get_region(void) { return RETRO_REGION_NTSC; }
bool retro_load_game(const struct retro_game_info* info) {
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) {
fprintf(stderr, "XRGB8888 is not supported.\n");
return false;
}
(void)info;
return true;
}
bool button(unsigned id) {
return input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, id);
}
void retro_run(void) {
input_poll_cb();
if (button(RETRO_DEVICE_ID_JOYPAD_LEFT) && c > 0) --c;
if (button(RETRO_DEVICE_ID_JOYPAD_RIGHT) && c < 255) ++c;
uint32_t color = (255 - c) | (c << 8);
uint32_t* buf = frame_buf;
for (unsigned i = WIDTH * HEIGHT; i > 0; --i) {
*buf = color;
++buf;
}
video_cb(frame_buf, WIDTH, HEIGHT, WIDTH * sizeof(uint32_t));
}
void retro_unload_game(void) {}
size_t retro_serialize_size(void) { return 1; }
bool retro_serialize(void* data, size_t size) {
fprintf(stderr, "serialize(%p, %lu) <= %u\n", data, size, c);
*(char*)data = c;
return true;
}
bool retro_unserialize(const void* data, size_t size) {
c = *(char*)data;
fprintf(stderr, "unserialize(%p, %lu) => %u\n", data, size, c);
return true;
}
void retro_set_controller_port_device(unsigned port, unsigned device) {}
void retro_set_audio_sample(retro_audio_sample_t cb) {}
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) {}
void retro_reset(void) {}
bool retro_load_game_special(unsigned type, const struct retro_game_info* info,
size_t num) {
return false;
}
void retro_cheat_reset(void) {}
void retro_cheat_set(unsigned index, bool enabled, const char* code) {}
It is undocumented but intended that auto save does not trigger for a core without content.
Moreover auto save was not triggering for cores which had content but could support no content. This was unintended and recently fixed.
Reference: https://github.com/libretro/RetroArch/issues/9300
So I wrote a toy program for fun, and at the I moment I finished debugging thinking I finally got everything right, the last check with valgrind gave me 2 errors for not freeing 2 blocks of memory. But the error message really does not make sense to me.
==7419== 80 bytes in 1 blocks are definitely lost in loss record 1 of 2
==7419== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7419== by 0x400C77: mj_Malloc (mj.c:19)
==7419== by 0x401761: main (choco.c:93)
==7419==
==7419== 80 bytes in 1 blocks are definitely lost in loss record 2 of 2
==7419== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7419== by 0x400C77: mj_Malloc (mj.c:19)
==7419== by 0x401776: main (choco.c:94)
==7419==
==7419== LEAK SUMMARY:
==7419== definitely lost: 160 bytes in 2 blocks
Line 94 and 93 in main is
mj_Thread *chocolateMakers = mj_Malloc(nMakers * sizeof *chocolateMakers);
mj_Thread *chocolateEaters = mj_Malloc(nEaters * sizeof *chocolateEaters);
which is freed by
mj_Free(chocolateEaters);
mj_Free(chocolateMakers);
mj_Malloc and mj_Free are simple wrappers for error checking. (mj_Free for consistency)
void *mj_Malloc(size_t size) {
void *p = malloc(size);
if (p == NULL) {
mj_Error("heap allocation failed");
}
return p;
}
void mj_Free(void *p) {
free(p);
}
You can see the whole code below if you want.
choco.c
#include "../mj.c"
typedef struct {
int n;
mj_BlockingQueue orderQueue;
mj_BlockingQueue deliveryQueue;
} *ChocolateArgument;
ChocolateArgument ChocolateArgumentCreate(int n, mj_BlockingQueue orderQueue, mj_BlockingQueue deliveryQueue) {
ChocolateArgument this = mj_Malloc(sizeof *this);
this->n = n;
this->orderQueue = orderQueue;
this->deliveryQueue = deliveryQueue;
return this;
}
int MakeChocolates(void *data) {
ChocolateArgument argument = (ChocolateArgument)data;
while (true) {
if (mj_BlockingQueueOut(argument->orderQueue) != NULL) {
printf("chocolate maker %i going home\n", argument->n);
break;
}
int milli = mj_RandomInt(1, 1000);
mj_Sleep(milli);
printf("new chocolate (maker %i, %.3f seconds)\n", argument->n, (double)milli / 1000.0);
int *pMakerNumber = mj_Malloc(sizeof *pMakerNumber);
*pMakerNumber = argument->n;
mj_BlockingQueueIn(argument->deliveryQueue, pMakerNumber);
}
mj_Free(data);
return EXIT_SUCCESS;
}
void HireChocolateMakers(mj_Thread **pMakers, int nMakers, mj_BlockingQueue orderQueue, mj_BlockingQueue deliveryQueue) {
*pMakers = mj_Malloc(nMakers * sizeof **pMakers);
for (int i = 0; i < nMakers; i += 1) {
ChocolateArgument argument = ChocolateArgumentCreate(i + 1, orderQueue, deliveryQueue);
(*pMakers)[i] = mj_ThreadCreate(MakeChocolates, argument);
}
printf("%i chocolate makers hired\n", nMakers);
}
int EatChocolates(void *data) {
ChocolateArgument argument = (ChocolateArgument)data;
int nOrders = mj_RandomInt(1, 10);
for (int i = 0; i < nOrders; i += 1) {
mj_BlockingQueueIn(argument->orderQueue, NULL);
}
printf("chocolate eater %i ordered %i chocolates\n", argument->n, nOrders);
for (int i = 1; i <= nOrders; i += 1) {
int *pMakerNumber = mj_BlockingQueueOut(argument->deliveryQueue);
printf("maker %i -> eater %i (%i / %i)\n", *pMakerNumber, argument->n, i, nOrders);
free(pMakerNumber);
}
printf("chocolate eater %i is satisfied\n", argument->n);
mj_Free(data);
return EXIT_SUCCESS;
}
void OrderChocolates(mj_Thread **pEaters, int nEaters, mj_BlockingQueue orderQueue, mj_BlockingQueue deliveryQueue) {
*pEaters = mj_Malloc(nEaters * sizeof **pEaters);
for (int i = 0; i < nEaters; i += 1) {
ChocolateArgument argument = ChocolateArgumentCreate(i + 1, orderQueue, deliveryQueue);
(*pEaters)[i] = mj_ThreadCreate(EatChocolates, argument);
}
}
void GoHome(mj_Thread *eaters, int nEaters, mj_Thread *makers, int nMakers, mj_BlockingQueue orderQueue) {
for (int i = 0; i < nEaters; i += 1) {
mj_ThreadWait(eaters[i]);
mj_ThreadDelete(eaters[i]);
}
printf("all chocolate eaters are satisfied\n");
for (int i = 0; i < nMakers; i += 1) {
mj_BlockingQueueIn(orderQueue, NULL + 1);
}
for (int i = 0; i < nMakers; i += 1) {
mj_ThreadWait(makers[i]);
mj_ThreadDelete(makers[i]);
}
}
int main(int argc, char **argv) {
if (argc != 3) {
mj_Error("not enough arguments");
}
int nMakers = atoi(argv[1]);
int nEaters = atoi(argv[2]);
mj_RandomSeed();
mj_BlockingQueue orderQueue = mj_BlockingQueueCreate();
mj_BlockingQueue deliveryQueue = mj_BlockingQueueCreate();
mj_Thread *chocolateMakers = mj_Malloc(nMakers * sizeof *chocolateMakers);
mj_Thread *chocolateEaters = mj_Malloc(nEaters * sizeof *chocolateEaters);
HireChocolateMakers(&chocolateMakers, nMakers, orderQueue, deliveryQueue);
OrderChocolates(&chocolateEaters, nEaters, orderQueue, deliveryQueue);
GoHome(chocolateEaters, nEaters, chocolateMakers, nMakers, orderQueue);
mj_BlockingQueueDelete(orderQueue);
mj_BlockingQueueDelete(deliveryQueue);
mj_Free(chocolateEaters);
mj_Free(chocolateMakers);
return 0;
}
mj.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <time.h>
#include <pthread.h>
#include <semaphore.h>
void mj_Error(char *errorMessage) {
fprintf(stderr, "%s\n", errorMessage);
exit(EXIT_FAILURE);
}
void *mj_Malloc(size_t size) {
void *p = malloc(size);
if (p == NULL) {
mj_Error("heap allocation failed");
}
return p;
}
void mj_Free(void *p) {
free(p);
}
typedef struct mj_QueueElement {
void *data;
struct mj_QueueElement *next;
} *mj_QueueElement;
mj_QueueElement mj_QueueElementCreate(void) {
mj_QueueElement this = mj_Malloc(sizeof *this);
return this;
}
void mj_QueueElementDelete(mj_QueueElement this) {
mj_Free(this);
}
typedef struct {
mj_QueueElement first;
mj_QueueElement last;
} *mj_Queue;
mj_Queue mj_QueueCreate(void) {
mj_Queue this = mj_Malloc(sizeof *this);
this->first = mj_QueueElementCreate();
this->last = this->first;
return this;
}
void mj_QueueDelete(mj_Queue this) {
mj_QueueElementDelete(this->first);
mj_Free(this);
}
void mj_QueueIn(mj_Queue this, void *data) {
this->last->data = data;
this->last->next = mj_QueueElementCreate();
this->last = this->last->next;
}
void *mj_QueueOut(mj_Queue this) {
mj_QueueElement temp = this->first;
void *data = temp->data;
this->first = this->first->next;
mj_QueueElementDelete(temp);
return data;
}
typedef pthread_mutex_t *mj_Mutex;
mj_Mutex mj_MutexCreate(void) {
mj_Mutex this = mj_Malloc(sizeof *this);
pthread_mutex_init(this, NULL);
return this;
}
void mj_MutexDelete(mj_Mutex this) {
pthread_mutex_destroy(this);
mj_Free(this);
}
void mj_MutexLock(mj_Mutex this) {
pthread_mutex_lock(this);
}
void mj_MutexUnlock(mj_Mutex this) {
pthread_mutex_unlock(this);
}
typedef sem_t *mj_Semaphore;
mj_Semaphore mj_SemaphoreCreate(int n) {
mj_Semaphore this = mj_Malloc(sizeof *this);
sem_init(this, 0, n);
return this;
}
void mj_SemaphoreDelete(mj_Semaphore this) {
sem_destroy(this);
mj_Free(this);
}
void mj_SemaphoreUp(mj_Semaphore this) {
sem_post(this);
}
void mj_SemaphoreDown(mj_Semaphore this) {
sem_wait(this);
}
typedef struct {
mj_Queue queue;
mj_Mutex inLock;
mj_Mutex outLock;
mj_Semaphore emptyBlocker;
} *mj_BlockingQueue;
mj_BlockingQueue mj_BlockingQueueCreate(void) {
mj_BlockingQueue this = mj_Malloc(sizeof *this);
this->queue = mj_QueueCreate();
this->inLock = mj_MutexCreate();
this->outLock = mj_MutexCreate();
this->emptyBlocker = mj_SemaphoreCreate(0);
return this;
}
void mj_BlockingQueueDelete(mj_BlockingQueue this) {
mj_QueueDelete(this->queue);
mj_MutexDelete(this->inLock);
mj_MutexDelete(this->outLock);
mj_SemaphoreDelete(this->emptyBlocker);
mj_Free(this);
}
void mj_BlockingQueueIn(mj_BlockingQueue this, void *data) {
mj_MutexLock(this->inLock);
mj_QueueIn(this->queue, data);
mj_SemaphoreUp(this->emptyBlocker);
mj_MutexUnlock(this->inLock);
}
void *mj_BlockingQueueOut(mj_BlockingQueue this) {
mj_MutexLock(this->outLock);
mj_SemaphoreDown(this->emptyBlocker);
void *data = mj_QueueOut(this->queue);
mj_MutexUnlock(this->outLock);
return data;
}
typedef pthread_t *mj_Thread;
typedef struct {
int (*function)(void *);
void *argument;
} *mj_ThreadInfo;
mj_ThreadInfo mj_ThreadInfoCreate(int (*function)(void *), void *argument) {
mj_ThreadInfo this = mj_Malloc(sizeof *this);
this->function = function;
this->argument = argument;
return this;
}
void *mj_ThreadFunction(void *data) {
mj_ThreadInfo info = (mj_ThreadInfo)data;
info->function(info->argument);
mj_Free(data);
return NULL;
}
mj_Thread mj_ThreadCreate(int (*function)(void *), void *argument) {
mj_Thread this = mj_Malloc(sizeof *this);
mj_ThreadInfo info = mj_ThreadInfoCreate(function, argument);
if (pthread_create(this, NULL, mj_ThreadFunction, info) != 0) {
mj_Error("failed to create thread");
}
return this;
}
void mj_ThreadDelete(mj_Thread this) {
mj_Free(this);
}
void mj_ThreadWait(mj_Thread this) {
pthread_join(*this, NULL);
}
void mj_Sleep(int milli) {
struct timespec time;
time.tv_sec = milli / 1000;
time.tv_nsec = (milli % 1000) * 1000000;
nanosleep(&time, NULL);
}
uint64_t mj_RandomInt_s;
uint64_t mj_RandomInt_s2;
void mj_RandomSeed(void) {
srand((unsigned)time(NULL));
mj_RandomInt_s = rand() * rand();
mj_RandomInt_s2 = rand() * rand() * rand();
}
int mj_RandomInt(int from, int to) {
if (from > to) {
mj_Error("invalid arguments");
}
uint64_t x = mj_RandomInt_s;
uint64_t y = mj_RandomInt_s2;
mj_RandomInt_s = y;
x ^= x << 23;
x ^= x >> 17;
x ^= y ^ (y >> 26);
mj_RandomInt_s2 = x;
return (int)((x + y) % (uint64_t)(to - from + 1)) + from;
}
You're allocating chocolateMakers twice (line 93 first and line 36 then) and chocolateEaters twice too (line 94 first and line 62 then). In both cases, you're overwriting the pointer resulting of the first allocation with the one resulting of the second allocation. When you free the allocated memory, you're doing it only once, with the pointers of the second allocations. The pointers of the first allocation are lost, the memory allocated is never freed.