diff options
author | Paul Gilbert | 2019-09-02 20:57:19 -0700 |
---|---|---|
committer | Paul Gilbert | 2019-09-25 20:13:26 -0700 |
commit | 60c860c6a6c37b95ab8265d658cbcc144d51153b (patch) | |
tree | e21dac197fcd374bb8d348873c91230141e1fddf /engines/glk/adrift/scmemos.cpp | |
parent | c893c5a60b8298aa99ae1a47dea51c6f04c419bc (diff) | |
download | scummvm-rg350-60c860c6a6c37b95ab8265d658cbcc144d51153b.tar.gz scummvm-rg350-60c860c6a6c37b95ab8265d658cbcc144d51153b.tar.bz2 scummvm-rg350-60c860c6a6c37b95ab8265d658cbcc144d51153b.zip |
GLK: ADRIFT: Skeleton sub-engine commit
Diffstat (limited to 'engines/glk/adrift/scmemos.cpp')
-rw-r--r-- | engines/glk/adrift/scmemos.cpp | 622 |
1 files changed, 622 insertions, 0 deletions
diff --git a/engines/glk/adrift/scmemos.cpp b/engines/glk/adrift/scmemos.cpp new file mode 100644 index 0000000000..4809b2078f --- /dev/null +++ b/engines/glk/adrift/scmemos.cpp @@ -0,0 +1,622 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/adrift/scare.h" +#include "glk/adrift/scprotos.h" + +namespace Glk { +namespace Adrift { + +/* Assorted definitions and constants. */ +static const sc_uint MEMENTO_MAGIC = 0x9fd33d1d; +enum { MEMO_ALLOCATION_BLOCK = 32 }; + +/* + * Game memo structure, saves a serialized game. Allocation is preserved so + * that structures can be reused without requiring reallocation. + */ +typedef struct sc_memo_s +{ + sc_byte *serialized_game; + sc_int allocation; + sc_int length; +} sc_memo_t; +typedef sc_memo_t *sc_memoref_t; + +/* + * Game command history structure, similar to a memo. Saves a player input + * command to create a history, reusing allocation where possible. + */ +typedef struct sc_history_s +{ + sc_char *command; + sc_int sequence; + sc_int timestamp; + sc_int turns; + sc_int allocation; + sc_int length; +} sc_history_t; +typedef sc_history_t *sc_historyref_t; + +/* + * Memo set structure. This reserves space for a predetermined number of + * serialized games, and an indicator cursor showing where additions are + * placed. The structure is a ring, with old elements being overwritten by + * newer arrivals. Also tacked onto this structure is a set of strings + * used to hold a command history that operates in a somewhat csh-like way, + * also a ring with limited capacity. + */ +enum { MEMO_UNDO_TABLE_SIZE = 16, MEMO_HISTORY_TABLE_SIZE = 64 }; +typedef struct sc_memo_set_s +{ + sc_uint magic; + sc_memo_t memo[MEMO_UNDO_TABLE_SIZE]; + sc_int memo_cursor; + + sc_history_t history[MEMO_HISTORY_TABLE_SIZE]; + sc_int history_count; + sc_int current_history; + sc_bool is_at_start; +} sc_memo_set_t; + + +/* + * memo_is_valid() + * + * Return TRUE if pointer is a valid memo set, FALSE otherwise. + */ +static sc_bool +memo_is_valid (sc_memo_setref_t memento) +{ + return memento && memento->magic == MEMENTO_MAGIC; +} + + +/* + * memo_round_up() + * + * Round up an allocation in bytes to the next allocation block. + */ +static sc_int +memo_round_up (sc_int allocation) +{ + sc_int extended; + + extended = allocation + MEMO_ALLOCATION_BLOCK - 1; + return (extended / MEMO_ALLOCATION_BLOCK) * MEMO_ALLOCATION_BLOCK; +} + + +/* + * memo_create() + * + * Create and return a new set of memos. + */ +sc_memo_setref_t +memo_create (void) +{ + sc_memo_setref_t memento; + + /* Create and initialize a clean set of memos. */ + memento = (sc_memo_setref_t)sc_malloc (sizeof (*memento)); + memento->magic = MEMENTO_MAGIC; + + memset (memento->memo, 0, sizeof (memento->memo)); + memento->memo_cursor = 0; + + memset (memento->history, 0, sizeof (memento->history)); + memento->history_count = 0; + memento->current_history = 0; + memento->is_at_start = FALSE; + + return memento; +} + + +/* + * memo_destroy() + * + * Destroy a memo set, and free its heap memory. + */ +void +memo_destroy (sc_memo_setref_t memento) +{ + sc_int index_; + assert (memo_is_valid (memento)); + + /* Free the content of any used memo and any used history. */ + for (index_ = 0; index_ < MEMO_UNDO_TABLE_SIZE; index_++) + { + sc_memoref_t memo; + + memo = memento->memo + index_; + sc_free (memo->serialized_game); + } + for (index_ = 0; index_ < MEMO_HISTORY_TABLE_SIZE; index_++) + { + sc_historyref_t history; + + history = memento->history + index_; + sc_free (history->command); + } + + /* Poison and free the memo set itself. */ + memset (memento, 0xaa, sizeof (*memento)); + sc_free (memento); +} + + +/* + * memo_save_game_callback() + * + * Callback function for game serialization. Appends the data passed in to + * that already stored in the memo. + */ +static void +memo_save_game_callback (void *opaque, const sc_byte *buffer, sc_int length) +{ + sc_memoref_t memo = (sc_memoref_t)opaque; + sc_int required; + assert (opaque && buffer && length > 0); + + /* + * If necessary, increase the allocation for this memo. Serialized games + * tend to grow slightly as the game progresses, so we add a bit of extra + * to the actual allocation. + */ + required = memo->length + length; + if (required > memo->allocation) + { + required = memo_round_up (required + 2 * MEMO_ALLOCATION_BLOCK); + memo->serialized_game = (sc_byte *)sc_realloc (memo->serialized_game, required); + memo->allocation = required; + } + + /* Add this block of data to the buffer. */ + memcpy (memo->serialized_game + memo->length, buffer, length); + memo->length += length; +} + + +/* + * memo_save_game() + * + * Store a game in the next memo slot. + */ +void +memo_save_game (sc_memo_setref_t memento, sc_gameref_t game) +{ + sc_memoref_t memo; + assert (memo_is_valid (memento)); + + /* + * If the current slot is in use, we can re-use its allocation. Saved + * games will tend to be of roughly equal sizes, so it's worth doing. + */ + memo = memento->memo + memento->memo_cursor; + memo->length = 0; + + /* Serialize the given game into this memo. */ + ser_save_game (game, memo_save_game_callback, memo); + + /* + * If serialization worked (failure would be a surprise), advance the + * current memo cursor. + */ + if (memo->length > 0) + { + memento->memo_cursor++; + memento->memo_cursor %= MEMO_UNDO_TABLE_SIZE; + } + else + sc_error ("memo_save_game: warning: game save failed\n"); +} + + +/* + * memo_load_game_callback() + * + * Callback function for game deserialization. Returns data from the memo + * until it's drained. + */ +static sc_int +memo_load_game_callback (void *opaque, sc_byte *buffer, sc_int length) +{ + sc_memoref_t memo = (sc_memoref_t)opaque; + sc_int bytes; + assert (opaque && buffer && length > 0); + + /* Send back either all the bytes, or as many as the buffer allows. */ + bytes = (memo->length < length) ? memo->length : length; + + /* Read and remove the first block of data (or all if less than length). */ + memcpy (buffer, memo->serialized_game, bytes); + memmove (memo->serialized_game, + memo->serialized_game + bytes, memo->length - bytes); + memo->length -= bytes; + + /* Return the count of bytes placed in the buffer. */ + return bytes; +} + + +/* + * memo_load_game() + * + * Restore a game from the last memo slot used, if possible. + */ +sc_bool +memo_load_game (sc_memo_setref_t memento, sc_gameref_t game) +{ + sc_int cursor; + sc_memoref_t memo; + assert (memo_is_valid (memento)); + + /* Look back one from the current memo cursor. */ + cursor = (memento->memo_cursor == 0) + ? MEMO_UNDO_TABLE_SIZE - 1 : memento->memo_cursor - 1; + memo = memento->memo + cursor; + + /* If this slot is not empty, restore the serialized game held in it. */ + if (memo->length > 0) + { + sc_bool status; + + /* + * Deserialize the given game from this memo; failure would be somewhat + * of a surprise here. + */ + status = ser_load_game (game, memo_load_game_callback, memo); + if (!status) + sc_error ("memo_load_game: warning: game load failed\n"); + + /* + * This should have drained the memo of all data, but to be sure that + * there's no chance of trying to restore from this slot again, we'll + * force it anyway. + */ + if (memo->length > 0) + { + sc_error ("memo_load_game: warning: data remains after loading\n"); + memo->length = 0; + } + + /* Regress current memo, and return TRUE if we restored a memo. */ + memento->memo_cursor = cursor; + return status; + } + + /* There are no more memos to restore. */ + return FALSE; +} + + +/* + * memo_is_load_available() + * + * Returns TRUE if a memo restore is likely to succeed if called, FALSE + * otherwise. + */ +sc_bool +memo_is_load_available (sc_memo_setref_t memento) +{ + sc_int cursor; + sc_memoref_t memo; + assert (memo_is_valid (memento)); + + /* + * Look back one from the current memo cursor. Return TRUE if this slot + * contains a serialized game. + */ + cursor = (memento->memo_cursor == 0) + ? MEMO_UNDO_TABLE_SIZE - 1 : memento->memo_cursor - 1; + memo = memento->memo + cursor; + return memo->length > 0; +} + + +/* + * memo_clear_games() + * + * Forget the memos of saved games. + */ +void +memo_clear_games (sc_memo_setref_t memento) +{ + sc_int index_; + assert (memo_is_valid (memento)); + + /* Deallocate every entry. */ + for (index_ = 0; index_ < MEMO_UNDO_TABLE_SIZE; index_++) + { + sc_memoref_t memo; + + memo = memento->memo + index_; + sc_free (memo->serialized_game); + } + + /* Reset all entries and the cursor. */ + memset (memento->memo, 0, sizeof (memento->memo)); + memento->memo_cursor = 0; +} + + +/* + * memo_save_command() + * + * Store a player command in the command history, evicting any least recently + * used item if necessary. + */ +void +memo_save_command (sc_memo_setref_t memento, + const sc_char *command, sc_int timestamp, sc_int turns) +{ + sc_historyref_t history; + sc_int length; + assert (memo_is_valid (memento)); + + /* As with memos, reuse the allocation of the next slot if it has one. */ + history = memento->history + + memento->history_count % MEMO_HISTORY_TABLE_SIZE; + + /* + * Resize the allocation for this slot if required. Strings tend to be + * short, so round up to a block to avoid too many reallocs. + */ + length = strlen (command) + 1; + if (history->allocation < length) + { + sc_int required; + + required = memo_round_up (length); + history->command = (sc_char *)sc_realloc (history->command, required); + history->allocation = required; + } + + /* Save the string into this slot, and normalize it for neatness. */ + strcpy (history->command, command); + sc_normalize_string (history->command); + history->sequence = memento->history_count + 1; + history->timestamp = timestamp; + history->turns = turns; + history->length = length; + + /* Increment the count of histories handled. */ + memento->history_count++; +} + + +/* + * memo_unsave_command() + * + * Remove the last saved command. This is special functionality for the + * history lister. To keep synchronized with the runner main loop, it needs + * to "invent" a history item at the end of the list before listing, then + * remove it again as the main runner loop will add the real thing. + */ +void +memo_unsave_command (sc_memo_setref_t memento) +{ + assert (memo_is_valid (memento)); + + /* Do nothing if for some reason there's no history to unsave. */ + if (memento->history_count > 0) + { + sc_historyref_t history; + + /* Decrement the count of histories handled, erase the prior entry. */ + memento->history_count--; + history = memento->history + + memento->history_count % MEMO_HISTORY_TABLE_SIZE; + history->sequence = 0; + history->timestamp = 0; + history->turns = 0; + history->length = 0; + } +} + + +/* + * memo_get_command_count() + * + * Return a count of available saved commands. + */ +sc_int +memo_get_command_count (sc_memo_setref_t memento) +{ + assert (memo_is_valid (memento)); + + /* Return the lesser of the history count and the history table size. */ + if (memento->history_count < MEMO_HISTORY_TABLE_SIZE) + return memento->history_count; + else + return MEMO_HISTORY_TABLE_SIZE; +} + + +/* + * memo_first_command() + * + * Iterator rewind function, reset current location to the first command. + */ +void +memo_first_command (sc_memo_setref_t memento) +{ + sc_int cursor; + sc_historyref_t history; + assert (memo_is_valid (memento)); + + /* + * If the buffer has cycled, we have the full complement of saved commands, + * so start iterating at the current cursor. Otherwise, start from index 0. + * Detect cycling by looking at the current slot; if it's filled, we've + * been here before. Set at_start flag to indicate the special case for + * circular buffers. + */ + cursor = memento->history_count % MEMO_HISTORY_TABLE_SIZE; + history = memento->history + cursor; + memento->current_history = (history->length > 0) ? cursor : 0; + memento->is_at_start = TRUE; +} + + +/* + * memo_next_command() + * + * Iterator function, return the next saved command and its sequence id + * starting at 1, and the timestamp and turns when the command was saved. + */ +void +memo_next_command (sc_memo_setref_t memento, + const sc_char **command, sc_int *sequence, + sc_int *timestamp, sc_int *turns) +{ + assert (memo_is_valid (memento)); + + /* If valid, return the current command and advance. */ + if (memo_more_commands (memento)) + { + sc_historyref_t history; + + /* Note the current history, and advance its index. */ + history = memento->history + memento->current_history; + memento->current_history++; + memento->current_history %= MEMO_HISTORY_TABLE_SIZE; + memento->is_at_start = FALSE; + + /* Return details from the history noted above. */ + *command = history->command; + *sequence = history->sequence; + *timestamp = history->timestamp; + *turns = history->turns; + } + else + { + /* Return NULL and zeroes if no more commands available. */ + *command = NULL; + *sequence = 0; + *timestamp = 0; + *turns = 0; + } +} + + +/* + * memo_more_commands() + * + * Iterator end function, returns TRUE if more commands are readable. + */ +sc_bool +memo_more_commands (sc_memo_setref_t memento) +{ + sc_int cursor; + sc_historyref_t history; + assert (memo_is_valid (memento)); + + /* Get the current effective write position, and the current history. */ + cursor = memento->history_count % MEMO_HISTORY_TABLE_SIZE; + history = memento->history + memento->current_history; + + /* + * More data if the current history is behind the write position and is + * occupied, or if it matches and is occupied and we're at the start of + * iteration (circular buffer special case). + */ + if (memento->current_history == cursor) + return (memento->is_at_start) ? history->length > 0 : FALSE; + else + return history->length > 0; +} + + +/* + * memo_find_command() + * + * Find and return the command string for the given sequence number (-ve + * indicates an offset from the last defined), or NULL if not found. + */ +const sc_char * +memo_find_command (sc_memo_setref_t memento, sc_int sequence) +{ + sc_int target, index_; + sc_historyref_t matched; + assert (memo_is_valid (memento)); + + /* Decide on a search target, depending on the sign of sequence. */ + target = (sequence < 0) ? memento->history_count + sequence + 1: sequence; + + /* + * A backwards search starting at the write position would probably be more + * efficient here, but this is a rarely called function so we'll do it the + * simpler way. + */ + matched = NULL; + for (index_ = 0; index_ < MEMO_HISTORY_TABLE_SIZE; index_++) + { + sc_historyref_t history; + + history = memento->history + index_; + if (history->sequence == target) + { + matched = history; + break; + } + } + + /* + * Return the command or NULL. If sequence passed in was zero, and the + * history was not full, this will still return NULL as it should, since + * this unused history's command found by the search above will be NULL. + */ + return matched ? matched->command : NULL; +} + + +/* + * memo_clear_commands() + * + * Forget all saved commands. + */ +void +memo_clear_commands (sc_memo_setref_t memento) +{ + sc_int index_; + assert (memo_is_valid (memento)); + + /* Deallocate every entry. */ + for (index_ = 0; index_ < MEMO_HISTORY_TABLE_SIZE; index_++) + { + sc_historyref_t history; + + history = memento->history + index_; + sc_free (history->command); + } + + /* Reset all entries, the count, and the iteration variables. */ + memset (memento->history, 0, sizeof (memento->history)); + memento->history_count = 0; + memento->current_history = 0; + memento->is_at_start = FALSE; +} + +} // End of namespace Adrift +} // End of namespace Glk |