139 lines
4.7 KiB
C
139 lines
4.7 KiB
C
|
|
/**
|
||
|
|
* @file hoststruct.c
|
||
|
|
* @brief Sharing a game's own C structures with a script.
|
||
|
|
*
|
||
|
|
* This is the code in docs/15-structures.md's "Sharing a structure with a host"
|
||
|
|
* section, kept here so it is compiled and run by every build rather than
|
||
|
|
* rotting in a document.
|
||
|
|
*
|
||
|
|
* **The thing worth noticing is that there is no marshalling step.** The host
|
||
|
|
* describes its struct once and binds an instance; the script then reads and
|
||
|
|
* writes the game's real memory. `FOE@.HP# = FOE@.HP# - 10` decrements
|
||
|
|
* `GOBLIN.hp` in place.
|
||
|
|
*
|
||
|
|
* The second thing worth noticing is that the language's own distinction --
|
||
|
|
* assignment copies, `POINT` shares -- turns out to be exactly the distinction
|
||
|
|
* a host needs, so there is only one API and which one a script used is visible
|
||
|
|
* in the listing.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <stdbool.h>
|
||
|
|
#include <stddef.h>
|
||
|
|
#include <stdint.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <akerror.h>
|
||
|
|
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/host.h>
|
||
|
|
#include <akbasic/runtime.h>
|
||
|
|
#include <akbasic/sink.h>
|
||
|
|
|
||
|
|
/* ------------------------------------------------------------ the game --- */
|
||
|
|
|
||
|
|
/** @brief The game's own struct. Nothing about it is akbasic's business. */
|
||
|
|
typedef struct
|
||
|
|
{
|
||
|
|
char name[32];
|
||
|
|
int32_t hp;
|
||
|
|
float x;
|
||
|
|
float y;
|
||
|
|
bool hostile;
|
||
|
|
} game_Enemy;
|
||
|
|
|
||
|
|
static game_Enemy GOBLIN = { "GOBLIN", 30, 10.0f, 20.0f, true };
|
||
|
|
static game_Enemy TROLL = { "TROLL", 80, 50.0f, 60.0f, true };
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The description akbasic needs: one row per field, with the BASIC name and its
|
||
|
|
* suffix, the C representation, and where it sits. AKBASIC_HOST_FIELD takes the
|
||
|
|
* offset and the width from the member itself, so the two cannot drift apart --
|
||
|
|
* writing them out by hand is two chances to name the wrong member and no way to
|
||
|
|
* notice.
|
||
|
|
*/
|
||
|
|
static const akbasic_HostField ENEMY_FIELDS[] = {
|
||
|
|
/* struct member BASIC name C representation */
|
||
|
|
AKBASIC_HOST_FIELD( game_Enemy, name, "NAME$", AKBASIC_HOSTFIELD_CSTRING ),
|
||
|
|
AKBASIC_HOST_FIELD( game_Enemy, hp, "HP#", AKBASIC_HOSTFIELD_INT32 ),
|
||
|
|
AKBASIC_HOST_FIELD( game_Enemy, x, "X%", AKBASIC_HOSTFIELD_FLOAT ),
|
||
|
|
AKBASIC_HOST_FIELD( game_Enemy, y, "Y%", AKBASIC_HOSTFIELD_FLOAT ),
|
||
|
|
AKBASIC_HOST_FIELD( game_Enemy, hostile, "HOSTILE#", AKBASIC_HOSTFIELD_BOOL )
|
||
|
|
};
|
||
|
|
|
||
|
|
static const akbasic_HostType ENEMY_TYPE = {
|
||
|
|
"ENEMY", sizeof(game_Enemy), ENEMY_FIELDS, 5
|
||
|
|
};
|
||
|
|
|
||
|
|
/* ------------------------------------------------------ the interpreter --- */
|
||
|
|
|
||
|
|
static akbasic_Runtime SCRIPT;
|
||
|
|
static akbasic_TextSink SINK;
|
||
|
|
static akbasic_StdioSink SINKSTATE;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* A script written against one name. The host points that name at each enemy in
|
||
|
|
* turn, which is what akbasic_host_rebind() is for -- one script, one binding,
|
||
|
|
* however many enemies.
|
||
|
|
*/
|
||
|
|
static const char *PROGRAM =
|
||
|
|
"10 PRINT FOE@.NAME$ + \" HAS \" + FOE@.HP#\n"
|
||
|
|
"20 FOE@.HP# = FOE@.HP# - 10\n"
|
||
|
|
"30 PRINT \" AFTER THE HIT: \" + FOE@.HP#\n";
|
||
|
|
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *run_against(game_Enemy *enemy)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
PASS(errctx, akbasic_host_rebind(&SCRIPT, "FOE@", enemy));
|
||
|
|
PASS(errctx, akbasic_runtime_start(&SCRIPT, AKBASIC_MODE_RUN));
|
||
|
|
PASS(errctx, akbasic_runtime_run(&SCRIPT, 0));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
int rc = 0;
|
||
|
|
|
||
|
|
ATTEMPT {
|
||
|
|
CATCH(errctx, akbasic_error_register());
|
||
|
|
CATCH(errctx, akbasic_sink_init_stdio(&SINK, &SINKSTATE, stdout, NULL));
|
||
|
|
CATCH(errctx, akbasic_runtime_init(&SCRIPT, &SINK));
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Register the type, then bind an instance. The descriptor is borrowed
|
||
|
|
* rather than copied, which is why it is `static const` -- it has to
|
||
|
|
* outlive the runtime.
|
||
|
|
*/
|
||
|
|
CATCH(errctx, akbasic_host_register_type(&SCRIPT, &ENEMY_TYPE));
|
||
|
|
CATCH(errctx, akbasic_host_bind(&SCRIPT, "FOE@", "ENEMY", &GOBLIN));
|
||
|
|
CATCH(errctx, akbasic_runtime_load(&SCRIPT, PROGRAM));
|
||
|
|
|
||
|
|
CATCH(errctx, run_against(&GOBLIN));
|
||
|
|
CATCH(errctx, run_against(&TROLL));
|
||
|
|
|
||
|
|
/*
|
||
|
|
* And the game's own structs have changed. No marshalling step, no copy
|
||
|
|
* to write back -- the script was reading and writing these all along.
|
||
|
|
*/
|
||
|
|
printf("host sees GOBLIN.hp = %d\n", GOBLIN.hp);
|
||
|
|
printf("host sees TROLL.hp = %d\n", TROLL.hp);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Unbind before the storage would go away. A binding is the only pointer
|
||
|
|
* this interpreter holds that it did not allocate, so this is the one
|
||
|
|
* lifetime a host has to think about -- and after it, a script reading
|
||
|
|
* the name is refused by name rather than reading freed memory.
|
||
|
|
*/
|
||
|
|
CATCH(errctx, akbasic_host_unbind(&SCRIPT, "FOE@"));
|
||
|
|
} CLEANUP {
|
||
|
|
} PROCESS(errctx) {
|
||
|
|
} HANDLE_DEFAULT(errctx) {
|
||
|
|
LOG_ERROR_WITH_MESSAGE(errctx, "host structure example failed");
|
||
|
|
rc = 1;
|
||
|
|
} FINISH_NORETURN(errctx);
|
||
|
|
|
||
|
|
return rc;
|
||
|
|
}
|