Hashmap: Implement prepared setting

With a prepared set, the potential allocation, which can fail, is split
from the actual setting, which can not fail.
This commit is contained in:
Laria 2022-02-25 20:50:43 +01:00
parent 40320aaa4a
commit bb464d8226
2 changed files with 84 additions and 19 deletions

View file

@ -139,24 +139,54 @@ find_key_in_bucket(const struct apfl_hashmap map, struct apfl_hashmap_bucket *bu
return false;
}
static bool
set_in_bucket(struct apfl_hashmap map, struct apfl_hashmap_bucket *bucket, void *key, void *value)
void
apfl_hashmap_set_prepared(struct apfl_hashmap_prepared_set prep, void *value)
{
size_t keysize = map.keysize;
size_t valsize = map.valsize;
size_t keysize = prep.map->keysize;
size_t valsize = prep.map->valsize;
if (prep.i < prep.bucket->len) {
// Replace
void *dest = KVADDR(prep.bucket->values, valsize, prep.i);
destroy_value(*prep.map, dest);
copy_value(*prep.map, dest, value);
} else {
// Append
assert(prep.i == prep.bucket->len);
assert(prep.i <= prep.bucket->keys_cap);
assert(prep.i <= prep.bucket->values_cap);
copy_key(*prep.map, KVADDR(prep.bucket->keys, keysize, prep.i), prep.key);
copy_value(*prep.map, KVADDR(prep.bucket->values, valsize, prep.i), value);
prep.bucket->len++;
}
}
static bool
prepare_set_in_bucket(
struct apfl_hashmap *map,
struct apfl_hashmap_prepared_set *prep,
struct apfl_hashmap_bucket *bucket,
void *key
) {
size_t keysize = map->keysize;
size_t valsize = map->valsize;
size_t i;
if (find_key_in_bucket(map, bucket, key, &i)) {
void *dest = KVADDR(bucket->values, valsize, i);
destroy_value(map, dest);
copy_value(map, dest, value);
if (find_key_in_bucket(*map, bucket, key, &i)) {
*prep = (struct apfl_hashmap_prepared_set) {
.map = map,
.bucket = bucket,
.key = key,
.i = i,
};
return true;
}
if (bucket->keys_cap != bucket->values_cap) {
assert(false); // A set operation was called on the hashmap,
// after a previous set operation failed.
assert(false); // A prepare set operation was called on the hashmap,
// after a previous prepare set operation failed.
return false;
}
@ -166,7 +196,7 @@ set_in_bucket(struct apfl_hashmap map, struct apfl_hashmap_bucket *bucket, void
void *newmem;
newmem = REALLOC_BYTES(
map.allocator,
map->allocator,
bucket->keys,
bucket->keys_cap * keysize,
new_cap * keysize
@ -178,7 +208,7 @@ set_in_bucket(struct apfl_hashmap map, struct apfl_hashmap_bucket *bucket, void
bucket->keys_cap = new_cap;
newmem = REALLOC_BYTES(
map.allocator,
map->allocator,
bucket->values,
bucket->values_cap * valsize,
new_cap * valsize
@ -190,11 +220,12 @@ set_in_bucket(struct apfl_hashmap map, struct apfl_hashmap_bucket *bucket, void
bucket->values_cap = new_cap;
}
copy_key(map, KVADDR(bucket->keys, keysize, bucket->len), key);
copy_value(map, KVADDR(bucket->values, valsize, bucket->len), value);
bucket->len++;
*prep = (struct apfl_hashmap_prepared_set) {
.map = map,
.bucket = bucket,
.key = key,
.i = bucket->len,
};
return true;
}
@ -332,7 +363,21 @@ apfl_hashmap_get(const struct apfl_hashmap *map, const void *key, void *value)
bool
apfl_hashmap_set(struct apfl_hashmap *map, void *key, void *value)
{
return set_in_bucket(*map, bucket_by_key(*map, key), key, value);
struct apfl_hashmap_prepared_set prep;
if (!apfl_hashmap_prepare_set(map, &prep, key)) {
return false;
}
apfl_hashmap_set_prepared(prep, value);
return true;
}
bool
apfl_hashmap_prepare_set(
struct apfl_hashmap *map,
struct apfl_hashmap_prepared_set *prep,
void *key
) {
return prepare_set_in_bucket(map, prep, bucket_by_key(*map, key), key);
}
static void

View file

@ -61,6 +61,13 @@ struct apfl_hashmap {
struct apfl_hashmap_bucket *buckets;
};
struct apfl_hashmap_prepared_set {
struct apfl_hashmap *map;
struct apfl_hashmap_bucket *bucket;
void *key;
size_t i;
};
apfl_hash apfl_hash_fnv1a_add(const void *, size_t len, apfl_hash);
apfl_hash apfl_hash_fnv1a(const void *, size_t len);
@ -78,6 +85,12 @@ bool apfl_hashmap_set(struct apfl_hashmap *, void *key, void *value);
size_t apfl_hashmap_count(const struct apfl_hashmap);
void apfl_hashmap_deinit(struct apfl_hashmap *);
// Prepares an apfl_hashmap_set operation. If there are no other set operations
// between the prepare and the set, this makes sure the set will always succeed.
bool apfl_hashmap_prepare_set(struct apfl_hashmap *, struct apfl_hashmap_prepared_set *, void *key);
void apfl_hashmap_set_prepared(struct apfl_hashmap_prepared_set, void *value);
struct apfl_hashmap apfl_hashmap_move(struct apfl_hashmap *src);
bool apfl_hashmap_copy(struct apfl_hashmap *dst, struct apfl_hashmap src);
@ -91,6 +104,13 @@ void apfl_hashmap_cursor_peek_value(struct apfl_hashmap_cursor, void **value_ptr
void apfl_hashmap_cursor_get_key(struct apfl_hashmap_cursor, void *key);
void apfl_hashmap_cursor_get_value(struct apfl_hashmap_cursor, void *value);
#define HASHMAP_EACH(map_ptr,cur) \
for ( \
struct apfl_hashmap_cursor cur = apfl_hashmap_get_cursor(map_ptr); \
!apfl_hashmap_cursor_is_end(cur); \
apfl_hashmap_cursor_next(&cur) \
)
#ifdef __cplusplus
}
#endif