mirror of
https://github.com/cellos51/balatro-gba.git
synced 2026-09-10 01:45:54 -05:00
Move data structures into their own directory
This commit is contained in:
171
source/data_structures/bitset.c
Normal file
171
source/data_structures/bitset.c
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "bitset.h"
|
||||
|
||||
#include "util.h"
|
||||
|
||||
void bitset_set_idx(Bitset* bitset, int idx, bool on)
|
||||
{
|
||||
uint32_t i = idx / BITSET_BITS_PER_WORD;
|
||||
uint32_t b = idx % BITSET_BITS_PER_WORD;
|
||||
|
||||
// Below are the "fast" forms of the above operations, respectively.
|
||||
// These are more efficient, but removed for readability
|
||||
// See: https://github.com/cellos51/balatro-gba/pull/132#discussion_r2365966071
|
||||
// Divide by 32 to get the word index
|
||||
// uint32_t i = idx >> 5;
|
||||
// Get last 5-bits, same as a modulo (% 32) operation on positive numbers
|
||||
// uint32_t b = idx & 0x1F;
|
||||
if (on)
|
||||
{
|
||||
bitset->w[i] |= (uint32_t)1 << b;
|
||||
}
|
||||
else
|
||||
{
|
||||
bitset->w[i] &= ~((uint32_t)1 << b);
|
||||
}
|
||||
}
|
||||
|
||||
int bitset_set_next_free_idx(Bitset* bitset)
|
||||
{
|
||||
for (uint32_t i = 0; i < bitset->nwords; i++)
|
||||
{
|
||||
uint32_t inv = ~bitset->w[i];
|
||||
|
||||
// guard so we don't call `ctz` with 0, since __builtin_ctz(0) is undefined
|
||||
// https://gcc.gnu.org/onlinedocs/gcc/Bit-Operation-Builtins.html#index-_005f_005fbuiltin_005fctz
|
||||
//
|
||||
// By using the bitwise inverse of the word, you can skip words that are full
|
||||
// quickly (where the value is 0 or 'false' since all bits are '1', or 'in use'). Any value
|
||||
// greater than 0 indicates there is a free slot. Then, when counting the trailing 0's, you
|
||||
// can test very quickly where the first free slot is. This operation prevents looping
|
||||
// through every bit of filled flags, and will instead operate only on the first word with
|
||||
// free slots.
|
||||
if (inv)
|
||||
{
|
||||
int bit = __builtin_ctz(inv);
|
||||
bitset->w[i] |= ((uint32_t)1 << bit);
|
||||
int idx = i * BITSET_BITS_PER_WORD + bit;
|
||||
return (idx < bitset->cap) ? idx : UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
void bitset_clear(Bitset* bitset)
|
||||
{
|
||||
for (int i = 0; i < bitset->nwords; i++)
|
||||
{
|
||||
bitset->w[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool bitset_is_empty(Bitset* bitset)
|
||||
{
|
||||
for (int i = 0; i < bitset->nwords; i++)
|
||||
{
|
||||
if (bitset->w[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bitset_get_idx(Bitset* bitset, int idx)
|
||||
{
|
||||
uint32_t i = idx / BITSET_BITS_PER_WORD;
|
||||
uint32_t b = idx % BITSET_BITS_PER_WORD;
|
||||
|
||||
return bitset->w[i] & (uint32_t)1 << b;
|
||||
}
|
||||
|
||||
int bitset_num_set_bits(Bitset* bitset)
|
||||
{
|
||||
int sum = 0;
|
||||
|
||||
for (int i = 0; i < bitset->nwords; i++)
|
||||
{
|
||||
sum += __builtin_popcount(bitset->w[i]);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
int bitset_find_idx_of_nth_set(const Bitset* bitset, int n)
|
||||
{
|
||||
int tracker = 0;
|
||||
int prev_tracker = 0;
|
||||
|
||||
for (int i = 0; i < bitset->nwords; i++)
|
||||
{
|
||||
tracker += __builtin_popcount(bitset->w[i]);
|
||||
|
||||
if (tracker > n)
|
||||
{
|
||||
// The index is here somewhere
|
||||
// this one is to count the 1's not the offset, underflow to -1 is good for finding the
|
||||
// 0 index
|
||||
int base = prev_tracker - 1;
|
||||
// this one is for the actual offset we want to map the id to
|
||||
int offset = bitset->nbits * i;
|
||||
for (int j = 0; j < bitset->nbits; j++)
|
||||
{
|
||||
if (base == n)
|
||||
{
|
||||
return offset - 1;
|
||||
}
|
||||
base += (bitset->w[i] >> j) & 0x01;
|
||||
offset++;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
prev_tracker = tracker;
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
BitsetItr bitset_itr_create(const Bitset* bitset)
|
||||
{
|
||||
BitsetItr itr = {
|
||||
.bitset = bitset,
|
||||
.word = 0,
|
||||
.bit = 0,
|
||||
.itr = 0,
|
||||
};
|
||||
|
||||
return itr;
|
||||
}
|
||||
|
||||
int bitset_itr_next(BitsetItr* itr)
|
||||
{
|
||||
// So, worst case scenario for this is one bit at the end of the last
|
||||
// word in the bitset. You would look (32 * 7) + 31 times!
|
||||
// This can be sped up with by checking if the word is empty first.
|
||||
// Then the worst enemy of this method would be something like a set bit at the end
|
||||
// of every word. In that case you would need to loop 31 times maximum.
|
||||
// So one last thing you could do is something like `bitset_allocate_idx` does with the
|
||||
// __builtin_ctz function as well.
|
||||
//
|
||||
// The point being, this can be very slow, but it's simple and can be much faster.
|
||||
for (; itr->word < itr->bitset->nwords; itr->word++)
|
||||
{
|
||||
for (; itr->bit < itr->bitset->nbits; itr->bit++)
|
||||
{
|
||||
itr->itr++;
|
||||
if (itr->bitset->w[itr->word] & (1 << itr->bit))
|
||||
{
|
||||
// if itr->bit == nbits on the next run, the for loop will handle it
|
||||
itr->bit++;
|
||||
// above we always make it one more than it is
|
||||
// it's so we can return without mutating the actual iterator
|
||||
// once it gets here. Just subtract one
|
||||
return itr->itr - 1;
|
||||
}
|
||||
}
|
||||
itr->bit = 0;
|
||||
}
|
||||
itr->word = 0;
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
331
source/data_structures/list.c
Normal file
331
source/data_structures/list.c
Normal file
@@ -0,0 +1,331 @@
|
||||
/**
|
||||
* @file list.c
|
||||
*
|
||||
* @brief List functions implementation.
|
||||
*/
|
||||
|
||||
#include "list.h"
|
||||
|
||||
#include "pool.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* Remove a node from a list.
|
||||
*
|
||||
* Remove a @ref ListNode from a @ref List. There are no checks to ensure that the
|
||||
* passed `node` is actually part of the passed `list`. Handle with care.
|
||||
* This is used with the @ref ListItr specifically.
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
* @param node pointer to a @ref ListNode
|
||||
*/
|
||||
static void s_list_remove_node(List* list, ListNode* node);
|
||||
|
||||
/**
|
||||
* Get the next @ref ListNode in a @ref ListItr
|
||||
*
|
||||
* Note: Use of this function outside of testing is strongly discouraged. Unless
|
||||
* you really want to access the @ref ListNode itself, it's preferred to just use
|
||||
* @ref list_itr_next .
|
||||
*
|
||||
* @param itr pointer to the @ref ListItr
|
||||
*
|
||||
* @return A pointer to the @ref ListNode in the itr, otherwise return NULL.
|
||||
*/
|
||||
static ListNode* s_list_itr_node_next(ListItr* itr);
|
||||
|
||||
List list_init(void)
|
||||
{
|
||||
List list = LIST_DEFAULT;
|
||||
return list;
|
||||
}
|
||||
|
||||
void list_clear(List* list)
|
||||
{
|
||||
if (list_is_empty(list))
|
||||
return;
|
||||
|
||||
ListItr itr = list_itr_create(list);
|
||||
ListNode* ln;
|
||||
|
||||
while ((ln = s_list_itr_node_next(&itr)))
|
||||
{
|
||||
POOL_FREE(ListNode, ln);
|
||||
}
|
||||
|
||||
list->head = NULL;
|
||||
list->tail = NULL;
|
||||
list->len = 0;
|
||||
}
|
||||
|
||||
bool list_is_empty(const List* list)
|
||||
{
|
||||
return list->len == 0;
|
||||
}
|
||||
|
||||
void list_push_front(List* list, void* data)
|
||||
{
|
||||
ListNode* node = POOL_GET(ListNode);
|
||||
|
||||
node->data = data;
|
||||
node->prev = NULL;
|
||||
node->next = list->head;
|
||||
|
||||
if (list_is_empty(list))
|
||||
{
|
||||
list->tail = node;
|
||||
}
|
||||
else
|
||||
{
|
||||
list->head->prev = node;
|
||||
}
|
||||
|
||||
list->head = node;
|
||||
|
||||
list->len++;
|
||||
}
|
||||
|
||||
void list_push_back(List* list, void* data)
|
||||
{
|
||||
ListNode* node = POOL_GET(ListNode);
|
||||
node->data = data;
|
||||
node->prev = list->tail;
|
||||
node->next = NULL;
|
||||
|
||||
if (list_is_empty(list))
|
||||
{
|
||||
list->head = node;
|
||||
}
|
||||
else
|
||||
{
|
||||
list->tail->next = node;
|
||||
}
|
||||
|
||||
list->tail = node;
|
||||
|
||||
list->len++;
|
||||
}
|
||||
|
||||
void list_insert(List* list, void* data, unsigned int idx)
|
||||
{
|
||||
if (idx >= list->len)
|
||||
{
|
||||
list_push_back(list, data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx == 0)
|
||||
{
|
||||
list_push_front(list, data);
|
||||
return;
|
||||
}
|
||||
|
||||
// After the above two checks the index is guaranteed to be inbetween the
|
||||
// `head` and `tail` of the `list`. This means the actual list doesn't need
|
||||
// to modify it's head and tail, only it's length. Simplifying the code below:
|
||||
|
||||
unsigned int curr_idx = 0;
|
||||
ListItr itr = list_itr_create(list);
|
||||
ListNode* ln;
|
||||
|
||||
while ((ln = s_list_itr_node_next(&itr)))
|
||||
{
|
||||
if (idx == curr_idx++)
|
||||
{
|
||||
ListNode* node = POOL_GET(ListNode);
|
||||
node->prev = ln->prev;
|
||||
node->next = ln;
|
||||
ln->prev->next = node;
|
||||
ln->prev = node;
|
||||
node->data = data;
|
||||
list->len++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool list_swap(List* list, unsigned int idx_a, unsigned int idx_b)
|
||||
{
|
||||
if (idx_a >= list->len || idx_b >= list->len)
|
||||
return false;
|
||||
if (idx_a == idx_b)
|
||||
return true; // swapping with yourself isn't technically an error
|
||||
|
||||
unsigned int curr_idx = 0;
|
||||
unsigned int max_idx = idx_a > idx_b ? idx_a : idx_b;
|
||||
ListNode* node_a = NULL;
|
||||
ListNode* node_b = NULL;
|
||||
|
||||
ListItr itr = list_itr_create(list);
|
||||
ListNode* ln;
|
||||
|
||||
do
|
||||
{
|
||||
ln = s_list_itr_node_next(&itr);
|
||||
if (idx_a == curr_idx)
|
||||
{
|
||||
node_a = ln;
|
||||
continue;
|
||||
}
|
||||
if (idx_b == curr_idx)
|
||||
{
|
||||
node_b = ln;
|
||||
continue;
|
||||
}
|
||||
} while (max_idx != curr_idx++);
|
||||
|
||||
// Just swap the data pointers
|
||||
void* tmp = node_a->data;
|
||||
node_a->data = node_b->data;
|
||||
node_b->data = tmp;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void s_list_remove_node(List* list, ListNode* node)
|
||||
{
|
||||
if (node->prev && !node->next) // end of list
|
||||
{
|
||||
node->prev->next = NULL;
|
||||
list->tail = node->prev;
|
||||
}
|
||||
else if (node->prev && node->next) // somewhere in between
|
||||
{
|
||||
node->prev->next = node->next;
|
||||
node->next->prev = node->prev;
|
||||
}
|
||||
else if (node->next && !node->prev) // beginning of list
|
||||
{
|
||||
node->next->prev = NULL;
|
||||
list->head = node->next;
|
||||
}
|
||||
else if (!node->prev && !node->next) // only element in list
|
||||
{
|
||||
list->head = NULL;
|
||||
list->tail = NULL;
|
||||
}
|
||||
|
||||
POOL_FREE(ListNode, node);
|
||||
|
||||
list->len--;
|
||||
}
|
||||
|
||||
int list_get_len(const List* list)
|
||||
{
|
||||
return list->len;
|
||||
}
|
||||
|
||||
void* list_get_at_idx(List* list, unsigned int idx)
|
||||
{
|
||||
if (idx >= list_get_len(list))
|
||||
return NULL;
|
||||
|
||||
int curr_idx = 0;
|
||||
ListItr itr = list_itr_create(list);
|
||||
void* data = NULL;
|
||||
|
||||
while ((data = list_itr_next(&itr)))
|
||||
{
|
||||
if (idx == curr_idx++)
|
||||
return data;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool list_remove_at_idx(List* list, unsigned int idx)
|
||||
{
|
||||
if (idx >= list_get_len(list))
|
||||
return false;
|
||||
|
||||
int len = 0;
|
||||
ListItr itr = list_itr_create(list);
|
||||
ListNode* ln;
|
||||
|
||||
while ((ln = s_list_itr_node_next(&itr)))
|
||||
{
|
||||
if (idx == len++)
|
||||
{
|
||||
s_list_remove_node(list, ln);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ListItr list_itr_create(List* list)
|
||||
{
|
||||
ListItr itr = {
|
||||
.list = list,
|
||||
.next_node = !list_is_empty(list) ? list->head : NULL,
|
||||
.current_node = NULL,
|
||||
.direction = LIST_ITR_FORWARD,
|
||||
};
|
||||
|
||||
return itr;
|
||||
}
|
||||
|
||||
ListItr rev_list_itr_create(List* list)
|
||||
{
|
||||
ListItr itr = {
|
||||
.list = list,
|
||||
.next_node = !list_is_empty(list) ? list->tail : NULL,
|
||||
.current_node = NULL,
|
||||
.direction = LIST_ITR_REVERSE,
|
||||
};
|
||||
|
||||
return itr;
|
||||
}
|
||||
|
||||
void* list_itr_next(ListItr* itr)
|
||||
{
|
||||
ListNode* ln = s_list_itr_node_next(itr);
|
||||
return ln ? ln->data : NULL;
|
||||
}
|
||||
|
||||
static ListNode* s_list_itr_node_next(ListItr* itr)
|
||||
{
|
||||
if (!itr->next_node)
|
||||
return NULL;
|
||||
|
||||
itr->current_node = itr->next_node;
|
||||
|
||||
ListNode* ln = itr->next_node;
|
||||
ListNode* next_itr_node = (itr->direction == LIST_ITR_FORWARD) ? ln->next : ln->prev;
|
||||
|
||||
if (next_itr_node)
|
||||
{
|
||||
itr->next_node = next_itr_node;
|
||||
return ln;
|
||||
}
|
||||
|
||||
itr->next_node = NULL;
|
||||
return ln;
|
||||
}
|
||||
|
||||
void list_itr_remove_current_node(ListItr* itr)
|
||||
{
|
||||
if (!itr || !itr->current_node)
|
||||
return;
|
||||
ListNode* tmp_prev = itr->current_node->prev;
|
||||
s_list_remove_node(itr->list, itr->current_node);
|
||||
itr->current_node = tmp_prev;
|
||||
}
|
||||
|
||||
bool list_remove_data(List* list, void* data)
|
||||
{
|
||||
ListItr itr = list_itr_create(list);
|
||||
ListNode* ln;
|
||||
|
||||
while ((ln = s_list_itr_node_next(&itr)))
|
||||
{
|
||||
if (ln->data == data)
|
||||
{
|
||||
s_list_remove_node(list, ln);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
5
source/data_structures/pool.c
Normal file
5
source/data_structures/pool.c
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "pool.h"
|
||||
|
||||
#define POOL_ENTRY(name, capacity) POOL_DEFINE_TYPE(name, capacity);
|
||||
#include POOLS_DEF_FILE
|
||||
#undef POOL_ENTRY
|
||||
Reference in New Issue
Block a user