Update list implementation to work with memory pools (#168)
* Introduce indexed list implementation * Fix CI tests for pool * Take bitset out of pool * Replace joker bitset interactions with wrappers * Add bitset tests * Add test to gitignore --------- Co-authored-by: rfehr-idexx <ric-fehr@idexx.com>
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
/** @file bitset.h
|
||||
*
|
||||
* @brief A bitset for operating on flags
|
||||
*/
|
||||
#ifndef BITSET_H
|
||||
#define BITSET_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @def BITSET_BITS_PER_WORD
|
||||
* @brief Number of bits in a word for a bitset.
|
||||
*
|
||||
* Number of bits in a word for a bitset. Will always be 32 here.
|
||||
*/
|
||||
#define BITSET_BITS_PER_WORD 32
|
||||
|
||||
/**
|
||||
* @def BITSET_ARRAY_SIZE
|
||||
* @brief Number of words in a bitset
|
||||
*
|
||||
* Number of words in every bitset. This represents the maximum number and each
|
||||
* bitset will always use this number of words, though it's capacity can be any length
|
||||
* from `1` to `BITSET_BITS_PER_WORD * BITSET_ARRAY_SIZE`
|
||||
*/
|
||||
#define BITSET_ARRAY_SIZE 8
|
||||
|
||||
/**
|
||||
* @def BITSET_MAX_BITS
|
||||
* @brief Maximum number of bits in a bitset
|
||||
*/
|
||||
#define BITSET_MAX_BITS BITSET_BITS_PER_WORD * BITSET_ARRAY_SIZE
|
||||
|
||||
/**
|
||||
* @brief A bitset spread across multiple `uint32_t` words
|
||||
*/
|
||||
typedef struct Bitset
|
||||
{
|
||||
/**
|
||||
* @brief Word array of `uint32_t` to hold the bitset data
|
||||
*/
|
||||
uint32_t *w;
|
||||
|
||||
/**
|
||||
* @brief Number of bits in a word, will be 32
|
||||
*/
|
||||
uint32_t nbits;
|
||||
|
||||
/**
|
||||
* @brief Number of words int the `w` array
|
||||
*/
|
||||
uint32_t nwords;
|
||||
|
||||
/**
|
||||
* @brief Number of actual flags (nbits * nwords)
|
||||
*/
|
||||
uint32_t cap;
|
||||
} Bitset;
|
||||
|
||||
/**
|
||||
* @brief An iterator into a @ref Bitset
|
||||
*
|
||||
* This iterator will parse and find the next index to a '1' bit as efficiently as possible.
|
||||
*
|
||||
* There is no implementation of the following (yet):
|
||||
* - Reverse iteration
|
||||
* - Bit-by-bit iteration
|
||||
* - Iterating on offsets to '0' bits
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/**
|
||||
* @brief @ref Bitset this is iterating through
|
||||
*/
|
||||
const Bitset *bitset;
|
||||
|
||||
/**
|
||||
* @brief Current word the iterator is on
|
||||
*/
|
||||
int word;
|
||||
|
||||
/**
|
||||
* @brief Current bit the iterator is on
|
||||
*/
|
||||
int bit;
|
||||
|
||||
/**
|
||||
* @brief Number of bits that have been iterated through in total
|
||||
*/
|
||||
int itr;
|
||||
} BitsetItr;
|
||||
|
||||
/**
|
||||
* @brief Set a flag in a bitset to a value
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
* @param idx the index of the flag to set
|
||||
* @param on the value to set the flag to
|
||||
*/
|
||||
void bitset_set_idx(Bitset *bitset, int idx, bool on);
|
||||
|
||||
/**
|
||||
* @brief Get the value of a flag
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
* @param idx the index of the flag to get
|
||||
*
|
||||
* @return the value of the flag as `true` or `false`
|
||||
*/
|
||||
bool bitset_get_idx(Bitset *bitset, int idx);
|
||||
|
||||
// Get the next free (set to 0) index in the bitset.
|
||||
// It also sets the bit which it maybe should do... It really shouldn't do two things
|
||||
// But it's such a fast operation idk. // TODO: decide what you wanna do
|
||||
int bitset_allocate_idx(Bitset *bitset);
|
||||
|
||||
/**
|
||||
* @brief Clear the bitset, all to 0
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
*/
|
||||
void bitset_clear(Bitset *bitset);
|
||||
|
||||
/**
|
||||
* @brief Check if a bitset is empty (all 0's)
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
*
|
||||
* @return `true` if empty, `false` otherwise
|
||||
*/
|
||||
bool bitset_is_empty(Bitset *bitset);
|
||||
|
||||
/**
|
||||
* @brief Count how many bits are set to `1` in a bitset
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
*
|
||||
* @return The number of flags set to `1` in a bitset
|
||||
*/
|
||||
int bitset_num_set_bits(Bitset *bitset);
|
||||
|
||||
/**
|
||||
* @brief Find the index of the nth set bit
|
||||
*
|
||||
* Find the index of the nth flag set to `1`. This function is useful to get one value quickly,
|
||||
* but does not operate iteratively well. Use a @BitsetItr for iterative access to a bitset.
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
*
|
||||
* @return The index of the nth flag set to `1` in the bitset
|
||||
*/
|
||||
int bitset_find_idx_of_nth_set(const Bitset *bitset, int n);
|
||||
|
||||
/**
|
||||
* @brief Declare a @ref BitsetItr
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
*
|
||||
* @return A newly constructed BitsetItr
|
||||
*/
|
||||
BitsetItr bitset_itr_create(const Bitset* bitset);
|
||||
|
||||
/**
|
||||
* @brief Get the index of the next set bit in the bitset from a @ref BitsetItr
|
||||
*
|
||||
* @param itr A @ref BitsetItr to operate on
|
||||
*
|
||||
* @return a positive number if successful, UNDEFINED otherwise (out-of-bounds)
|
||||
*/
|
||||
int bitset_itr_next(BitsetItr* itr);
|
||||
|
||||
/**
|
||||
* @def BITSET_DEFINE
|
||||
* @brief Make a standard bitset
|
||||
*
|
||||
* Make a bitset with a valid static array to store it's array of words.
|
||||
*
|
||||
* Use this to define bitsets in the code, specifically as a `static` scoped
|
||||
* variable. The passed `name` will be the same name as the bitset.
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* ```c
|
||||
* BITSET_DEFINE(_my_bitset, 128);
|
||||
* // normal operation...
|
||||
* bitset_clear(&_my_bitset);
|
||||
* ```
|
||||
*
|
||||
* @param name the name of the bitset
|
||||
* @param capacity the capacity of the bitset
|
||||
*/
|
||||
#define BITSET_DEFINE(name, capacity) \
|
||||
static uint32_t name##_w[BITSET_ARRAY_SIZE] = {0}; \
|
||||
static Bitset name = \
|
||||
{ \
|
||||
.w = name##_w, \
|
||||
.nbits = BITSET_BITS_PER_WORD, \
|
||||
.nwords = BITSET_ARRAY_SIZE, \
|
||||
.cap = capacity, \
|
||||
};
|
||||
|
||||
|
||||
#endif // BITSET_H
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "sprite.h"
|
||||
#include "joker.h"
|
||||
#include "card.h"
|
||||
#include "list.h"
|
||||
|
||||
POOL_ENTRY(Sprite, MAX_SPRITES);
|
||||
POOL_ENTRY(SpriteObject, MAX_SPRITE_OBJECTS);
|
||||
@@ -8,3 +9,4 @@ POOL_ENTRY(Joker, MAX_ACTIVE_JOKERS);
|
||||
POOL_ENTRY(JokerObject, MAX_ACTIVE_JOKERS);
|
||||
POOL_ENTRY(Card, MAX_CARDS);
|
||||
POOL_ENTRY(CardObject, MAX_CARDS_ON_SCREEN);
|
||||
POOL_ENTRY(ListNode, MAX_LIST_NODES);
|
||||
|
||||
+2
-3
@@ -91,7 +91,6 @@ void game_init();
|
||||
void game_update();
|
||||
void game_change_state(enum GameState new_game_state);
|
||||
|
||||
// Forward declaration
|
||||
struct List;
|
||||
typedef struct List List;
|
||||
|
||||
@@ -106,9 +105,9 @@ int hand_get_size(void);
|
||||
CardObject** get_played_array(void);
|
||||
int get_played_top(void);
|
||||
int get_scored_card_index(void);
|
||||
List* get_jokers(void);
|
||||
bool is_joker_owned(int joker_id);
|
||||
bool card_is_face(Card *card);
|
||||
List* get_jokers_list(void);
|
||||
|
||||
int get_deck_top(void);
|
||||
int get_num_discards_remaining(void);
|
||||
@@ -122,4 +121,4 @@ void set_game_speed(int new_game_speed);
|
||||
bool is_shortcut_joker_active(void);
|
||||
int get_straight_and_flush_size(void);
|
||||
|
||||
#endif // GAME_H
|
||||
#endif // GAME_H
|
||||
|
||||
+2
-1
@@ -12,6 +12,8 @@
|
||||
// plus the amount that can fit in the shop, 8 should be fine. For now...
|
||||
#define MAX_ACTIVE_JOKERS 8
|
||||
|
||||
#define MAX_DEFINABLE_JOKERS 150
|
||||
|
||||
#define JOKER_TID (MAX_HAND_SIZE + MAX_SELECTION_SIZE) * JOKER_SPRITE_OFFSET // Tile ID for the starting index in the tile memory
|
||||
#define JOKER_SPRITE_OFFSET 16 // Offset for the joker sprites
|
||||
#define JOKER_BASE_PB 4 // The starting palette index for the jokers
|
||||
@@ -68,7 +70,6 @@ enum JokerEvent
|
||||
#define SHORTCUT_JOKER_ID 26
|
||||
#define FOUR_FINGERS_JOKER_ID 48
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u8 id; // Unique ID for the joker, used to identify different jokers
|
||||
|
||||
+187
-27
@@ -1,27 +1,187 @@
|
||||
#ifndef LIST_H
|
||||
#define LIST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct List
|
||||
{
|
||||
void** _array;
|
||||
int size;
|
||||
int allocated_size;
|
||||
} List;
|
||||
|
||||
List *list_new(int init_size);
|
||||
void list_destroy(List **list);
|
||||
bool list_append(List *list, void *value);
|
||||
bool list_remove_by_idx(List *list, int index);
|
||||
void* list_get(List *list, int index);
|
||||
int list_get_size(List *list);
|
||||
bool list_remove_by_value(List *list, void *value);
|
||||
bool list_exists(List *list, void *value);
|
||||
|
||||
bool int_list_append(List *list, intptr_t value);
|
||||
intptr_t int_list_get(List *list, int index);
|
||||
bool int_list_remove_by_value(List *list, intptr_t value);
|
||||
bool int_list_exists(List *list, intptr_t value);
|
||||
|
||||
#endif
|
||||
/** @file list.h
|
||||
*
|
||||
* @brief A doubly-linked list
|
||||
*
|
||||
* List Implementation
|
||||
* ===================
|
||||
*
|
||||
* - This @ref List operates as a linked list @ref ListNodes. It operates as a regular doubly-linked list
|
||||
* but doesn't allocate memory and rather gets @ref ListNodes from a pool.
|
||||
*/
|
||||
#ifndef LIST_H
|
||||
#define LIST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define MAX_LIST_NODES 128
|
||||
|
||||
typedef struct ListNode ListNode;
|
||||
|
||||
/**
|
||||
* @brief A single entry in a @ref List
|
||||
*/
|
||||
struct ListNode
|
||||
{
|
||||
/**
|
||||
* @brief The previous @ref ListNode in the associated @ref List, NULL if at the `head` of the list
|
||||
*/
|
||||
ListNode* prev;
|
||||
|
||||
/**
|
||||
* @brief The next @ref ListNode in the associated @ref List, NULL if at the `tail` of the list
|
||||
*/
|
||||
ListNode* next;
|
||||
|
||||
/**
|
||||
* @brief Pointer to generic data stored in this node
|
||||
*/
|
||||
void* data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A doubly-linked list
|
||||
*/
|
||||
typedef struct List
|
||||
{
|
||||
/**
|
||||
* @brief The first entry in the list
|
||||
*/
|
||||
ListNode* head;
|
||||
|
||||
/**
|
||||
* @brief The last entry in the list
|
||||
*/
|
||||
ListNode* tail;
|
||||
|
||||
/**
|
||||
* @brief Number of elements in list
|
||||
*/
|
||||
int len;
|
||||
} List;
|
||||
|
||||
/**
|
||||
* @brief An iterator into a list
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/**
|
||||
* @brief A pointer to the @ref List this is iterating through
|
||||
*/
|
||||
List* list;
|
||||
|
||||
/**
|
||||
* @brief The next node in the list
|
||||
*/
|
||||
ListNode* next_node;
|
||||
|
||||
/**
|
||||
* @brief The current node in the list iterator
|
||||
*
|
||||
* The node of the most recently returned data from @ref list_itr_next() .
|
||||
*/
|
||||
ListNode* current_node;
|
||||
} ListItr;
|
||||
|
||||
/**
|
||||
* Create a list.
|
||||
*
|
||||
* While this function does not allocate memory for the list itself, the list does allocate memory for each element.
|
||||
* So every created list must be freed with @ref list_clear to ensure the list's nodes are deleted properly.
|
||||
*
|
||||
* @return A @ref List with head and tail reset.
|
||||
*/
|
||||
List list_create(void);
|
||||
|
||||
/**
|
||||
* Clear a list.
|
||||
*
|
||||
* Go through the list and free each node and set the `head` and `tail` to `NULL`.
|
||||
* Note, it doesn't "free" the data at the node.
|
||||
*
|
||||
* @param list pointer to a @ref List to clear
|
||||
*/
|
||||
void list_clear(List* list);
|
||||
|
||||
/**
|
||||
* Check if a list is empty
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
*
|
||||
* @return `true` if the `list` is empty, `false` otherwise.
|
||||
*/
|
||||
bool list_is_empty(const List* list);
|
||||
|
||||
/**
|
||||
* Prepend an entry to the `head` of a @ref list
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
* @param data pointer to data to put into the @ref List
|
||||
*/
|
||||
void list_push_front(List* list, void* data);
|
||||
|
||||
/**
|
||||
* Append an entry to the `tail` of a @ref list
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
* @param data pointer to data to put into the @ref List
|
||||
*/
|
||||
void list_push_back(List* list, void* data);
|
||||
|
||||
/**
|
||||
* Get a List's node at it's nth index
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
* @param n index of the desired @ref ListNode in the list
|
||||
*
|
||||
* @return a pointer to the data at the nth @ref ListNode, or NULL if out-of-bounds
|
||||
*/
|
||||
void* list_get_at_idx(List *list, int n);
|
||||
|
||||
/**
|
||||
* Remove a List's node at it's nth index
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
* @param n index of the desired @ref ListNode in the list
|
||||
*
|
||||
* @return `true` if successfully removed, `false` if out-of-bounds
|
||||
*/
|
||||
bool list_remove_at_idx(List *list, int n);
|
||||
|
||||
/**
|
||||
* Get the number of elements in a @ref List
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
*
|
||||
* @return The number of elements in the list
|
||||
*/
|
||||
int list_get_len(const List* list);
|
||||
|
||||
/**
|
||||
* Declare a @ref ListItr
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
*
|
||||
* @return A new @ref ListItr
|
||||
*/
|
||||
ListItr list_itr_create(List* list);
|
||||
|
||||
/**
|
||||
* Get the next data entry in a @ref ListItr
|
||||
*
|
||||
* @param itr pointer to the @ref ListItr
|
||||
*
|
||||
* @return A pointer to the data pointer at the next @ref ListNode if valid, otherwise return NULL.
|
||||
*/
|
||||
void* list_itr_next(ListItr* itr);
|
||||
|
||||
/**
|
||||
* Remove the current @ref ListNode from the iterator.
|
||||
*
|
||||
* The "current node" corresponds to the list node associated with the
|
||||
* most recently returned valu from @ref list_itr_next()
|
||||
*
|
||||
* @param itr pointer to the @ref ListItr
|
||||
*/
|
||||
void list_itr_remove_current_node(ListItr* itr);
|
||||
|
||||
#endif
|
||||
|
||||
+21
-23
@@ -2,6 +2,9 @@
|
||||
#define POOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "bitset.h"
|
||||
|
||||
#ifdef POOLS_TEST_ENV
|
||||
#define POOLS_DEF_FILE "def_test_mempool.h"
|
||||
@@ -9,44 +12,28 @@
|
||||
#define POOLS_DEF_FILE "def_balatro_mempool.h"
|
||||
#endif
|
||||
|
||||
#define POOL_BITS_PER_WORD 32
|
||||
#define POOL_BITMAP_BYTES 8
|
||||
|
||||
typedef struct PoolBitmap {
|
||||
uint32_t *w;
|
||||
uint32_t nbits;
|
||||
uint32_t nwords;
|
||||
uint32_t cap;
|
||||
} PoolBitmap;
|
||||
|
||||
void pool_bm_clear_idx(PoolBitmap *bm, int idx);
|
||||
int pool_bm_get_free_idx(PoolBitmap *bm);
|
||||
|
||||
#define POOL_DECLARE_TYPE(type) \
|
||||
typedef struct \
|
||||
{ \
|
||||
PoolBitmap bm; \
|
||||
Bitset* bitset; \
|
||||
type * objects; \
|
||||
} type##Pool; \
|
||||
type *pool_get_##type(); \
|
||||
void pool_free_##type(type *obj); \
|
||||
int pool_idx_##type(type *obj); \
|
||||
type *pool_at_##type(int idx);
|
||||
|
||||
#define POOL_DEFINE_TYPE(type, capacity) \
|
||||
BITSET_DEFINE(type##_bitset, capacity) \
|
||||
static type type##_storage[capacity]; \
|
||||
static uint32_t type##_bitmap_w[POOL_BITMAP_BYTES] = {0}; \
|
||||
static type##Pool type##_pool = \
|
||||
{ \
|
||||
.bm = { \
|
||||
.w = type##_bitmap_w, \
|
||||
.nbits = POOL_BITS_PER_WORD, \
|
||||
.nwords = POOL_BITMAP_BYTES, \
|
||||
.cap = capacity, \
|
||||
}, \
|
||||
.bitset = & type##_bitset, \
|
||||
.objects = type##_storage, \
|
||||
}; \
|
||||
type * pool_get_##type() \
|
||||
{ \
|
||||
int free_offset = pool_bm_get_free_idx(&type##_pool.bm); \
|
||||
int free_offset = bitset_allocate_idx(type##_pool.bitset); \
|
||||
if(free_offset == -1) return NULL; \
|
||||
return &type##_pool.objects[free_offset]; \
|
||||
} \
|
||||
@@ -54,11 +41,22 @@ int pool_bm_get_free_idx(PoolBitmap *bm);
|
||||
{ \
|
||||
if(entry == NULL) return; \
|
||||
int offset = entry - &type##_pool.objects[0]; \
|
||||
pool_bm_clear_idx(&type##_pool.bm, offset); \
|
||||
bitset_set_idx(type##_pool.bitset, offset, false); \
|
||||
} \
|
||||
int pool_idx_##type(type *entry) \
|
||||
{ \
|
||||
return entry - &type##_pool.objects[0]; \
|
||||
} \
|
||||
type *pool_at_##type(int idx) \
|
||||
{ \
|
||||
if(idx < 0 || idx >= (type##_pool.bitset)->cap) return NULL; \
|
||||
return &type##_pool.objects[idx]; \
|
||||
}
|
||||
|
||||
#define POOL_GET(type) pool_get_##type()
|
||||
#define POOL_FREE(type, obj) pool_free_##type(obj)
|
||||
#define POOL_IDX(type, obj) pool_idx_##type(obj) // the index of the object
|
||||
#define POOL_AT(type, idx) pool_at_##type(idx) // the object at
|
||||
|
||||
#define POOL_ENTRY(name, capacity) \
|
||||
POOL_DECLARE_TYPE(name);
|
||||
|
||||
Reference in New Issue
Block a user