Mercurial > molko
changeset 65:80a913d25aa9
core: implement script, closes #2465 @2h
author | David Demelier <markand@malikania.fr> |
---|---|
date | Mon, 27 Jan 2020 12:24:20 +0100 |
parents | da9b7462ab92 |
children | 9435a53adab4 |
files | .hgignore Makefile src/adventure/main.c src/core/message.c src/core/script.c src/core/script.h src/core/util.c src/core/util.h tests/test-script.c |
diffstat | 9 files changed, 526 insertions(+), 3 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Thu Jan 23 20:44:01 2020 +0100 +++ b/.hgignore Mon Jan 27 12:24:20 2020 +0100 @@ -16,6 +16,7 @@ ^tests/test-color(\.exe)?$ ^tests/test-error(\.exe)?$ ^tests/test-map(\.exe)?$ +^tests/test-script(\.exe)?$ ^tools/molko-map(\.exe)?$ # doxygen stuff.
--- a/Makefile Thu Jan 23 20:44:01 2020 +0100 +++ b/Makefile Mon Jan 27 12:24:20 2020 +0100 @@ -36,6 +36,7 @@ src/core/map_state.c \ src/core/message.c \ src/core/painter.c \ + src/core/script.c \ src/core/sprite.c \ src/core/sys.c \ src/core/texture.c \ @@ -62,7 +63,8 @@ TESTS= tests/test-color.c \ tests/test-error.c \ - tests/test-map.c + tests/test-map.c \ + tests/test-script.c TESTS_INCS= -I extern/libgreatest -I src/core ${SDL_CFLAGS} TESTS_LIBS= ${LIB} ${SDL_LDFLAGS} ${LDFLAGS} TESTS_OBJS= ${TESTS:.c=}
--- a/src/adventure/main.c Thu Jan 23 20:44:01 2020 +0100 +++ b/src/adventure/main.c Mon Jan 27 12:24:20 2020 +0100 @@ -16,6 +16,10 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + #include "clock.h" #include "error.h" #include "event.h" @@ -31,6 +35,7 @@ #include "sys.h" #include "util.h" #include "window.h" +#include "script.h" #define WINDOW_WIDTH 1280 #define WINDOW_HEIGHT 720 @@ -88,7 +93,8 @@ return; //game_handle(&ev); - message_handle(&msg, &ev); + if (msg.state) + message_handle(&msg, &ev); } if (msg.state)
--- a/src/core/message.c Thu Jan 23 20:44:01 2020 +0100 +++ b/src/core/message.c Mon Jan 27 12:24:20 2020 +0100 @@ -26,7 +26,7 @@ #include "texture.h" #include "window.h" -#define MESSAGE_SPEED 150 /* Time delay for animations */ +#define MESSAGE_SPEED 200 /* Time delay for animations */ #define MESSAGE_TIMEOUT 5000 /* Time for auto-closing */ static unsigned int
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/script.c Mon Jan 27 12:24:20 2020 +0100 @@ -0,0 +1,152 @@ +/* + * script.c -- convenient sequence of actions + * + * Copyright (c) 2020 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "script.h" +#include "util.h" + +static void +script_action_finish(struct action *a) +{ + assert(a); + + script_finish(a->data); + free(a->data); +} + +static void +script_action_handle(struct action *a, const union event *ev) +{ + assert(a); + assert(ev); + + script_handle(a->data, ev); +} + +static bool +script_action_update(struct action *a, unsigned int ticks) +{ + assert(a); + + return script_update(a->data, ticks); +} + +static void +script_action_draw(struct action *a) +{ + assert(a); + + script_draw(a->data); +} + +void +script_init(struct script *s) +{ + assert(s); + + memset(s, 0, sizeof (struct script)); + s->tail = &s->head; +} + +void +script_start(struct script *s) +{ + assert(s); + + s->iter = s->head; +} + +void +script_append(struct script *s, const struct action *a) +{ + assert(s); + assert(a); + assert(a->update); + + struct script_action *iter = ecalloc(1, sizeof (struct script_action)); + + memcpy(&iter->action, a, sizeof (struct action)); + *s->tail = iter; + s->tail = &iter->next; +} + +void +script_handle(struct script *s, const union event *ev) +{ + assert(s); + assert(ev); + + if (s->iter) + s->iter->action.handle(&s->iter->action, ev); +} + +bool +script_update(struct script *s, unsigned int ticks) +{ + assert(s); + + if (s->iter && s->iter->action.update(&s->iter->action, ticks)) { + if (s->iter->action.finish) + s->iter->action.finish(&s->iter->action); + + s->iter = s->iter->next; + } + + return s->iter == NULL; +} + +void +script_draw(struct script *s) +{ + assert(s); + + if (s->iter && s->iter->action.draw) + s->iter->action.draw(&s->iter->action); +} + +void +script_action(const struct script *s, struct action *action) +{ + assert(s); + assert(action); + + memset(action, 0, sizeof (struct action)); + action->data = ememdup(s, sizeof (struct script)); + action->handle = script_action_handle; + action->update = script_action_update; + action->draw = script_action_draw; + action->finish = script_action_finish; +} + +void +script_finish(struct script *s) +{ + assert(s); + + struct script_action *iter, *next; + + for (iter = s->head; iter; iter = next) { + next = iter->next; + free(iter); + } + + memset(s, 0, sizeof (struct script)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/script.h Mon Jan 27 12:24:20 2020 +0100 @@ -0,0 +1,154 @@ +/* + * script.h -- convenient sequence of actions + * + * Copyright (c) 2020 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MOLKO_SCRIPT_H +#define MOLKO_SCRIPT_H + +/** + * \file script.h + * \brief Convenient sequence of actions. + * + * Those routines wrap individual actions into a sequence of actions into an + * action itself. + * + * This is convenient for scenarios where you need to specify several + * sequential actions that neet to wait before continuing. + * + * In a nutshell, to write a scenario you should: + * + * 1. Create a script with see script_init, + * 2. Create one or more actions and append with script_append, + * 3. Start the action using script_start, + * 4. Put the script into the game using game_add_action. + */ + +#include <stdbool.h> + +#include "action.h" + +union event; + +/** + * \brief Single-linked list of actions. + */ +struct script_action { + struct action action; + struct script_action *next; +}; + +/** + * \brief Sequence of actions and state holder. + */ +struct script { + struct script_action *iter; /*!< (RO) Current action */ + struct script_action *head; /*!< (RO) Beginning */ + struct script_action **tail; /*!< (RO) Pointer to add to tail */ +}; + +/** + * Initialize a script. + * + * This is mandatory before using any functions, do not zero-initialize the + * structure yourself. + * + * \pre s != NULL + * \param s the script + */ +void +script_init(struct script *s); + +/** + * Call this function before putting the script in the game. + * + * \pre s != NULL + * \param s the script + */ +void +script_start(struct script *s); + +/** + * Append a new action to the script. + * + * The action is copied into the script and does not need to be allocated on + * the heap. + * + * The action can be empty but must have at least update member set. + * + * \pre s != NULL + * \pre a != NULL && a->update + * \param s the script + * \param a the action to copy + */ +void +script_append(struct script *s, const struct action *a); + +/** + * Handle the event into the current action. + * + * \pre s != NULL + * \pre ev != NULL + * \param s the script + * \param ev the event + * \note You usually don't need to call this yourself. + */ +void +script_handle(struct script *s, const union event *ev); + +/** + * Update the current action. + * + * \pre s != NULL + * \param s the script + * \param ticks the number of milliseconds since last frame + * \note You usually don't need to call this yourself. + */ +bool +script_update(struct script *s, unsigned int ticks); + +/** + * Draw the current action. + * + * \pre s != NULL + * \param s the script + * \note You usually don't need to call this yourself. + */ +void +script_draw(struct script *s); + +/** + * Create an action from the script for use into the game. + * + * \pre s != NULL + * \pre dst != NULL + * \param s the script + * \param dst the action to build with the script + */ +void +script_action(const struct script *s, struct action *dst); + +/** + * Destroy all the actions into the script. + * + * \pre s != NULL + * \param s the script + * \note You usually don't need to call this yourself. + */ +void +script_finish(struct script *s); + +#endif /* !MOLKO_SCRIPT_H */
--- a/src/core/util.c Thu Jan 23 20:44:01 2020 +0100 +++ b/src/core/util.c Mon Jan 27 12:24:20 2020 +0100 @@ -47,6 +47,17 @@ return mem; } +void * +ememdup(const void *ptr, size_t size) +{ + void *mem; + + if (!(mem = malloc(size))) + error_fatalf("%s\n", strerror(errno)); + + return memcpy(mem, ptr, size); +} + void delay(unsigned int ms) {
--- a/src/core/util.h Thu Jan 23 20:44:01 2020 +0100 +++ b/src/core/util.h Mon Jan 27 12:24:20 2020 +0100 @@ -51,6 +51,18 @@ ecalloc(size_t n, size_t size); /** + * Copy the region specified by ptr. + * + * \pre ptr != NULL + * \param ptr the pointer + * \param size the size of the memory to copy + * \return a pointer + * \post returned pointer will never be NULL + */ +void * +ememdup(const void *ptr, size_t size); + +/** * Put the thread to sleep for a given amount of milliseconds. * * \param ms the number of milliseconds to wait
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-script.c Mon Jan 27 12:24:20 2020 +0100 @@ -0,0 +1,185 @@ +/* + * test-script.c -- test script action + * + * Copyright (c) 2020 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <greatest.h> + +#include <script.h> + +static bool +truth(struct action *a, unsigned int ticks) +{ + (void)a; + (void)ticks; + + return true; +} + +static bool +nope(struct action *a, unsigned int ticks) +{ + (void)a; + (void)ticks; + + return false; +} + +TEST +append(void) +{ + int ref1 = 0, ref2 = 0; + struct script sc; + + script_init(&sc); + + /* ref1 */ + script_append(&sc, &(struct action) { + .update = truth, + .data = &ref1 + }); + + ASSERT(sc.head); + ASSERT_EQ(sc.head->action.data, &ref1); + + /* ref2 */ + script_append(&sc, &(struct action) { + .update = truth, + .data = &ref2 + }); + + ASSERT(sc.head); + ASSERT_EQ(sc.head->action.data, &ref1); + ASSERT_EQ(sc.head->next->action.data, &ref2); + + script_finish(&sc); + + PASS(); +} + +TEST +finish(void) +{ + struct script sc; + + script_init(&sc); + script_append(&sc, &(struct action) { + .update = truth, + }); + script_append(&sc, &(struct action) { + .update = truth, + }); + script_finish(&sc); + + ASSERT(!sc.head); + PASS(); +} + +SUITE(manipulation) +{ + RUN_TEST(append); + RUN_TEST(finish); +} + +TEST +advance(void) +{ + int ref1 = 0, ref2 = 0; + struct script sc; + + script_init(&sc); + + script_append(&sc, &(struct action) { + .update = truth, + .data = &ref1 + }); + script_append(&sc, &(struct action) { + .update = truth, + .data = &ref2 + }); + + /* Start -> 1 */ + script_start(&sc); + + ASSERT(sc.iter); + ASSERT_EQ(sc.iter->action.data, &ref1); + + /* Update -> 2 */ + script_update(&sc, 0); + + ASSERT(sc.iter); + ASSERT_EQ(sc.iter->action.data, &ref2); + + /* Update -> end */ + script_update(&sc, 0); + + ASSERT(!sc.iter); + + script_finish(&sc); + + PASS(); +} + +TEST +dont_advance(void) +{ + int ref1 = 0, ref2 = 0; + struct script sc; + + script_init(&sc); + + script_append(&sc, &(struct action) { + .update = nope, + .data = &ref1 + }); + script_append(&sc, &(struct action) { + .update = truth, + .data = &ref2 + }); + + /* Start -> 1 */ + script_start(&sc); + + ASSERT(sc.iter); + ASSERT_EQ(sc.iter->action.data, &ref1); + + /* Update -> 1 */ + script_update(&sc, 0); + + ASSERT(sc.iter); + ASSERT_EQ(sc.iter->action.data, &ref1); + + script_finish(&sc); + + PASS(); +} + +SUITE(usage) +{ + RUN_TEST(advance); + RUN_TEST(dont_advance); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + RUN_SUITE(manipulation); + RUN_SUITE(usage); + GREATEST_MAIN_END(); +}