blob: 3a42c0e4a50007a34daeabea2e2f5a25c5add45e [file] [log] [blame]
/*
* Copyright (c) 2018 The Fuchsia Authors.
*
* 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 "sparse_array.h"
#include <stdlib.h>
#include <zircon/assert.h>
// An individual element is either a part of the used list or the free list at
// any given time, each of which is a non-circular doubly-linked list terminated
// at the head and tail by an index value of -1.
struct sa_elem {
ssize_t prev_ndx;
ssize_t next_ndx;
void* ptr;
};
// We store a sparse array as a set of elements with two lists -- one for available
// elements and one for in use elements. "free" and "used" provide the index of the
// head of the list of unused and used indices, respectively.
struct sparse_array {
size_t size;
ssize_t free;
ssize_t used;
struct sa_elem elems[0];
};
#define RANGE_CHECK(sa, ndx) ZX_DEBUG_ASSERT(((ndx) >= 0) && (((size_t)(ndx)) < sa->size))
void sa_init(sparse_array_t* psa, size_t size) {
ZX_DEBUG_ASSERT(size > 0);
size_t total_size = sizeof(struct sparse_array) + (sizeof(struct sa_elem) * size);
*psa = calloc(1, total_size);
if (*psa == NULL) { return; }
sparse_array_t sa = *psa;
sa->size = size;
// Initialize used list as empty
sa->used = -1;
// Add all elements to the free list
sa->elems[0].prev_ndx = -1;
for (ssize_t ndx = 0; ndx < ((ssize_t)size - 1); ndx++) {
sa->elems[ndx + 1].prev_ndx = ndx;
sa->elems[ndx].next_ndx = ndx + 1;
}
sa->elems[size - 1].next_ndx = -1;
// Initialize free list to point to a list of all elements
sa->free = 0;
}
void sa_free(sparse_array_t sa) {
free(sa);
}
ssize_t sa_add(sparse_array_t sa, void* payload) {
ssize_t elem_ndx = sa->free;
if (elem_ndx == -1) { return -1; }
RANGE_CHECK(sa, elem_ndx);
struct sa_elem* elem = &sa->elems[elem_ndx];
// Remove from free list
sa->free = elem->next_ndx;
if (sa->free != -1) {
RANGE_CHECK(sa, sa->free);
sa->elems[sa->free].prev_ndx = -1;
}
// Add to used list
elem->next_ndx = sa->used;
if (sa->used != -1) {
RANGE_CHECK(sa, sa->used);
sa->elems[sa->used].prev_ndx = elem_ndx;
}
sa->used = elem_ndx;
ZX_DEBUG_ASSERT(elem->ptr == NULL);
elem->ptr = payload;
return elem_ndx;
}
void* sa_get(sparse_array_t sa, ssize_t ndx) {
RANGE_CHECK(sa, ndx);
return sa->elems[ndx].ptr;
}
void sa_remove(sparse_array_t sa, ssize_t ndx) {
RANGE_CHECK(sa, ndx);
struct sa_elem* elem = &sa->elems[ndx];
ssize_t prev_ndx = elem->prev_ndx;
ssize_t next_ndx = elem->next_ndx;
// Remove from used list
if (prev_ndx == -1) {
sa->used = next_ndx;
} else {
RANGE_CHECK(sa, prev_ndx);
struct sa_elem* prev_elem = &sa->elems[prev_ndx];
prev_elem->next_ndx = next_ndx;
}
if (next_ndx != -1) {
RANGE_CHECK(sa, next_ndx);
struct sa_elem* next_elem = &sa->elems[next_ndx];
next_elem->prev_ndx = prev_ndx;
}
// Add to free list
next_ndx = sa->free;
elem->prev_ndx = -1;
elem->next_ndx = next_ndx;
elem->ptr = NULL;
sa->free = ndx;
if (next_ndx != -1) {
RANGE_CHECK(sa, next_ndx);
struct sa_elem* next_elem = &sa->elems[next_ndx];
next_elem->prev_ndx = ndx;
}
}
void sa_for_each(sparse_array_t sa, void (*fn)(ssize_t, void*, void*), void* ctx) {
ssize_t next_ndx = sa->used;
while (next_ndx != -1) {
RANGE_CHECK(sa, next_ndx);
struct sa_elem* elem = &sa->elems[next_ndx];
fn(next_ndx, elem->ptr, ctx);
next_ndx = sa->elems[next_ndx].next_ndx;
}
}