1
0
forked from vitrine/wmaker

Replace WUtil hashtable with a Rust impl.

This tweaks the hashtable API, and it is incomplete because the WUtil proplist
impl depends heavily on a feature of the old API that is being discontinued.

Moving the proplist code into Rust is our next objective.
This commit is contained in:
2025-09-14 18:51:06 -04:00
parent 157a8e0d5a
commit 32c40643c2
13 changed files with 346 additions and 410 deletions

View File

@@ -20,6 +20,7 @@ libWUtil_la_LIBADD = @LIBBSD@ $(wutilrs)
EXTRA_DIST = BUGS make-rgb Examples Extras Tests
# wbutton.c
libWINGs_la_SOURCES = \
configuration.c \
@@ -69,7 +70,6 @@ libWUtil_la_SOURCES = \
error.h \
findfile.c \
handlers.c \
hashtable.c \
menuparser.c \
menuparser.h \
menuparser_macros.c \

View File

@@ -337,7 +337,8 @@ void WHandleEvents(void);
/* ---[ WINGs/hashtable.c ]----------------------------------------------- */
WMHashTable* WMCreateHashTable(const WMHashTableCallbacks callbacks);
WMHashTable* WMCreateIdentityHashTable();
WMHashTable* WMCreateStringHashTable();
void WMFreeHashTable(WMHashTable *table);

View File

@@ -1,401 +0,0 @@
#include <config.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "WUtil.h"
#define INITIAL_CAPACITY 23
typedef struct HashItem {
const void *key;
const void *data;
struct HashItem *next; /* collided item list */
} HashItem;
typedef struct W_HashTable {
WMHashTableCallbacks callbacks;
unsigned itemCount;
unsigned size; /* table size */
HashItem **table;
} HashTable;
#define HASH(table, key) (((table)->callbacks.hash ? \
(*(table)->callbacks.hash)(key) : hashPtr(key)) % (table)->size)
static inline unsigned hashString(const void *param)
{
const char *key = param;
unsigned ret = 0;
unsigned ctr = 0;
while (*key) {
ret ^= *key++ << ctr;
ctr = (ctr + 1) % sizeof(char *);
}
return ret;
}
static inline unsigned hashPtr(const void *key)
{
return ((size_t) key / sizeof(char *));
}
static void rellocateItem(WMHashTable * table, HashItem * item)
{
unsigned h;
h = HASH(table, item->key);
item->next = table->table[h];
table->table[h] = item;
}
static void rebuildTable(WMHashTable * table)
{
HashItem *next;
HashItem **oldArray;
int i;
int oldSize;
int newSize;
oldArray = table->table;
oldSize = table->size;
newSize = table->size * 2;
table->table = wmalloc(sizeof(char *) * newSize);
table->size = newSize;
for (i = 0; i < oldSize; i++) {
while (oldArray[i] != NULL) {
next = oldArray[i]->next;
rellocateItem(table, oldArray[i]);
oldArray[i] = next;
}
}
wfree(oldArray);
}
WMHashTable *WMCreateHashTable(const WMHashTableCallbacks callbacks)
{
HashTable *table;
table = wmalloc(sizeof(HashTable));
table->callbacks = callbacks;
table->size = INITIAL_CAPACITY;
table->table = wmalloc(sizeof(HashItem *) * table->size);
return table;
}
void WMResetHashTable(WMHashTable * table)
{
HashItem *item, *tmp;
int i;
for (i = 0; i < table->size; i++) {
item = table->table[i];
while (item) {
tmp = item->next;
wfree(item);
item = tmp;
}
}
table->itemCount = 0;
if (table->size > INITIAL_CAPACITY) {
wfree(table->table);
table->size = INITIAL_CAPACITY;
table->table = wmalloc(sizeof(HashItem *) * table->size);
} else {
memset(table->table, 0, sizeof(HashItem *) * table->size);
}
}
void WMFreeHashTable(WMHashTable * table)
{
HashItem *item, *tmp;
int i;
for (i = 0; i < table->size; i++) {
item = table->table[i];
while (item) {
tmp = item->next;
wfree(item);
item = tmp;
}
}
wfree(table->table);
wfree(table);
}
unsigned WMCountHashTable(WMHashTable * table)
{
return table->itemCount;
}
static HashItem *hashGetItem(WMHashTable *table, const void *key)
{
unsigned h;
HashItem *item;
h = HASH(table, key);
item = table->table[h];
if (table->callbacks.keyIsEqual) {
while (item) {
if ((*table->callbacks.keyIsEqual) (key, item->key)) {
break;
}
item = item->next;
}
} else {
while (item) {
if (key == item->key) {
break;
}
item = item->next;
}
}
return item;
}
void *WMHashGet(WMHashTable * table, const void *key)
{
HashItem *item;
item = hashGetItem(table, key);
if (!item)
return NULL;
return (void *)item->data;
}
Bool WMHashGetItemAndKey(WMHashTable * table, const void *key, void **retItem, void **retKey)
{
HashItem *item;
item = hashGetItem(table, key);
if (!item)
return False;
if (retKey)
*retKey = (void *)item->key;
if (retItem)
*retItem = (void *)item->data;
return True;
}
void *WMHashInsert(WMHashTable * table, const void *key, const void *data)
{
unsigned h;
HashItem *item;
int replacing = 0;
h = HASH(table, key);
/* look for the entry */
item = table->table[h];
if (table->callbacks.keyIsEqual) {
while (item) {
if ((*table->callbacks.keyIsEqual) (key, item->key)) {
replacing = 1;
break;
}
item = item->next;
}
} else {
while (item) {
if (key == item->key) {
replacing = 1;
break;
}
item = item->next;
}
}
if (replacing) {
const void *old;
old = item->data;
item->data = data;
item->key = key;
return (void *)old;
} else {
HashItem *nitem;
nitem = wmalloc(sizeof(HashItem));
nitem->key = key;
nitem->data = data;
nitem->next = table->table[h];
table->table[h] = nitem;
table->itemCount++;
}
/* OPTIMIZE: put this in an idle handler. */
if (table->itemCount > table->size) {
#ifdef DEBUG0
printf("rebuilding hash table...\n");
#endif
rebuildTable(table);
#ifdef DEBUG0
printf("finished rebuild.\n");
#endif
}
return NULL;
}
static HashItem *deleteFromList(HashTable * table, HashItem * item, const void *key)
{
HashItem *next;
if (item == NULL)
return NULL;
if ((table->callbacks.keyIsEqual && (*table->callbacks.keyIsEqual) (key, item->key))
|| (!table->callbacks.keyIsEqual && key == item->key)) {
next = item->next;
wfree(item);
table->itemCount--;
return next;
}
item->next = deleteFromList(table, item->next, key);
return item;
}
void WMHashRemove(WMHashTable * table, const void *key)
{
unsigned h;
h = HASH(table, key);
table->table[h] = deleteFromList(table, table->table[h], key);
}
WMHashEnumerator WMEnumerateHashTable(WMHashTable * table)
{
WMHashEnumerator enumerator;
enumerator.table = table;
enumerator.index = 0;
enumerator.nextItem = table->table[0];
return enumerator;
}
void *WMNextHashEnumeratorItem(WMHashEnumerator * enumerator)
{
const void *data = NULL;
/* this assumes the table doesn't change between
* WMEnumerateHashTable() and WMNextHashEnumeratorItem() calls */
if (enumerator->nextItem == NULL) {
HashTable *table = enumerator->table;
while (++enumerator->index < table->size) {
if (table->table[enumerator->index] != NULL) {
enumerator->nextItem = table->table[enumerator->index];
break;
}
}
}
if (enumerator->nextItem) {
data = ((HashItem *) enumerator->nextItem)->data;
enumerator->nextItem = ((HashItem *) enumerator->nextItem)->next;
}
return (void *)data;
}
void *WMNextHashEnumeratorKey(WMHashEnumerator * enumerator)
{
const void *key = NULL;
/* this assumes the table doesn't change between
* WMEnumerateHashTable() and WMNextHashEnumeratorKey() calls */
if (enumerator->nextItem == NULL) {
HashTable *table = enumerator->table;
while (++enumerator->index < table->size) {
if (table->table[enumerator->index] != NULL) {
enumerator->nextItem = table->table[enumerator->index];
break;
}
}
}
if (enumerator->nextItem) {
key = ((HashItem *) enumerator->nextItem)->key;
enumerator->nextItem = ((HashItem *) enumerator->nextItem)->next;
}
return (void *)key;
}
Bool WMNextHashEnumeratorItemAndKey(WMHashEnumerator * enumerator, void **item, void **key)
{
/* this assumes the table doesn't change between
* WMEnumerateHashTable() and WMNextHashEnumeratorItemAndKey() calls */
if (enumerator->nextItem == NULL) {
HashTable *table = enumerator->table;
while (++enumerator->index < table->size) {
if (table->table[enumerator->index] != NULL) {
enumerator->nextItem = table->table[enumerator->index];
break;
}
}
}
if (enumerator->nextItem) {
if (item)
*item = (void *)((HashItem *) enumerator->nextItem)->data;
if (key)
*key = (void *)((HashItem *) enumerator->nextItem)->key;
enumerator->nextItem = ((HashItem *) enumerator->nextItem)->next;
return True;
}
return False;
}
static Bool compareStrings(const void *param1, const void *param2)
{
const char *key1 = param1;
const char *key2 = param2;
return strcmp(key1, key2) == 0;
}
typedef void *(*retainFunc) (const void *);
typedef void (*releaseFunc) (const void *);
const WMHashTableCallbacks WMIntHashCallbacks = {
NULL,
NULL,
};
const WMHashTableCallbacks WMStringPointerHashCallbacks = {
hashString,
compareStrings,
};

View File

@@ -88,10 +88,10 @@ static NotificationCenter *notificationCenter = NULL;
void W_InitNotificationCenter(void)
{
notificationCenter = wmalloc(sizeof(NotificationCenter));
notificationCenter->nameTable = WMCreateHashTable(WMStringPointerHashCallbacks);
notificationCenter->objectTable = WMCreateHashTable(WMIntHashCallbacks);
notificationCenter->nameTable = WMCreateStringHashTable();
notificationCenter->objectTable = WMCreateIdentityHashTable();
notificationCenter->nilList = NULL;
notificationCenter->observerTable = WMCreateHashTable(WMIntHashCallbacks);
notificationCenter->observerTable = WMCreateIdentityHashTable();
}
void W_ReleaseNotificationCenter(void)

View File

@@ -65,7 +65,7 @@ struct W_Balloon *W_CreateBalloon(WMScreen * scr)
W_ResizeView(bPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
bPtr->flags.alignment = DEFAULT_ALIGNMENT;
bPtr->table = WMCreateHashTable(WMIntHashCallbacks);
bPtr->table = WMCreateIdentityHashTable();
bPtr->delay = DEFAULT_DELAY;

View File

@@ -535,7 +535,7 @@ static void listFamilies(WMScreen * scr, WMFontPanel * panel)
if (pat)
FcPatternDestroy(pat);
families = WMCreateHashTable(WMStringPointerHashCallbacks);
families = WMCreateStringHashTable();
if (fs) {
for (i = 0; i < fs->nfont; i++) {

View File

@@ -630,7 +630,7 @@ WMScreen *WMCreateScreenWithRContext(Display * display, int screen, RContext * c
scrPtr->rootWin = RootWindow(display, screen);
scrPtr->fontCache = WMCreateHashTable(WMStringPointerHashCallbacks);
scrPtr->fontCache = WMCreateStringHashTable();
scrPtr->xftdraw = XftDrawCreate(scrPtr->display, W_DRAWABLE(scrPtr), scrPtr->visual, scrPtr->colormap);

View File

@@ -67,6 +67,7 @@ WPrefs_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la
WPrefs_LDADD = \
$(top_builddir)/wutil-rs/target/debug/libwutil_rs.a\
$(top_builddir)/wings-rs/target/debug/libwings_rs.la\
$(top_builddir)/WINGs/libWINGs.la\
$(top_builddir)/WINGs/libWUtil.la\
$(top_builddir)/wrlib/libwraster.la \

View File

@@ -134,7 +134,7 @@ else
nodist_wmaker_SOURCES = misc.hack_nf.c \
xmodifier.hack_nf.c
CLEANFILES = $(nodist_wmaker_SOURCES) ../wmaker-rs/target
CLEANFILES = $(nodist_wmaker_SOURCES)
misc.hack_nf.c: misc.c $(top_srcdir)/script/nested-func-to-macro.sh
$(AM_V_GEN)$(top_srcdir)/script/nested-func-to-macro.sh \
@@ -160,6 +160,7 @@ wmaker_LDADD = \
$(top_builddir)/WINGs/libWINGs.la\
$(top_builddir)/WINGs/libWUtil.la\
$(top_builddir)/wrlib/libwraster.la\
$(top_builddir)/wutil-rs/target/debug/libwutil_rs.a\
$(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\
@XLFLAGS@ \
@LIBXRANDR@ \

View File

@@ -11,5 +11,8 @@ cc = "1.0"
[dependencies]
atomic-write-file = "0.3"
hashbrown = "0.16.0"
libc = "0.2.175"
nom = "8.0"
nom-language = "0.1"
x11 = "2.21.0"

View File

@@ -2,9 +2,11 @@ AUTOMAKE_OPTIONS =
RUST_SOURCES = \
src/array.rs \
src/data.rs \
src/defines.c \
src/defines.rs \
src/find_file.rs \
src/hash_table.rs \
src/lib.rs \
src/memory.rs \
src/prop_list.rs

328
wutil-rs/src/hash_table.rs Normal file
View File

@@ -0,0 +1,328 @@
use hashbrown::hash_map::{self, HashMap};
use std::{
borrow::Borrow,
ffi::{CStr, c_void},
hash::{Hash, Hasher},
mem,
};
pub enum HashTable {
PointerKeyed(HashMap<VoidPointer, VoidPointer>),
StringKeyed(HashMap<StringPointer, VoidPointer>),
}
impl HashTable {
pub fn new_pointer_keyed() -> Self {
HashTable::PointerKeyed(HashMap::new())
}
pub fn new_string_keyed() -> Self {
HashTable::StringKeyed(HashMap::new())
}
pub fn clear(&mut self) {
match self {
HashTable::PointerKeyed(m) => m.clear(),
HashTable::StringKeyed(m) => m.clear(),
}
}
pub fn len(&self) -> usize {
match self {
HashTable::PointerKeyed(m) => m.len(),
HashTable::StringKeyed(m) => m.len(),
}
}
pub unsafe fn get(&self, key: *const i8) -> Option<*mut i8> {
match self {
HashTable::PointerKeyed(m) => {
let key = key.cast_mut();
m.get(&key).map(|x| x.0)
}
HashTable::StringKeyed(m) => {
let key = StringPointer(key.cast_mut());
let v = m.get(&key).map(|x| x.0);
mem::forget(key);
v
}
}
}
pub unsafe fn insert(&mut self, key: *mut i8, data: VoidPointer) -> Option<VoidPointer> {
match self {
HashTable::PointerKeyed(m) => m.insert(VoidPointer(key), data),
HashTable::StringKeyed(m) => m.insert(StringPointer(key), data),
}
}
pub unsafe fn remove(&mut self, key: *const i8) {
match self {
HashTable::PointerKeyed(m) => {
let key = key.cast_mut();
m.remove(&key);
}
HashTable::StringKeyed(m) => {
let key = StringPointer(key.cast_mut());
m.remove(&key);
mem::forget(key);
}
}
}
}
#[derive(Debug, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct VoidPointer(*mut i8);
impl Drop for VoidPointer {
fn drop(&mut self) {
unsafe { libc::free(self.0.cast::<c_void>()) }
}
}
impl Borrow<*mut i8> for VoidPointer {
fn borrow(&self) -> &*mut i8 {
&self.0
}
}
#[derive(Debug)]
#[repr(transparent)]
pub struct StringPointer(*mut i8);
impl PartialEq for StringPointer {
fn eq(&self, other: &Self) -> bool {
match (self.0.is_null(), other.0.is_null()) {
(true, true) => true,
(true, false) => false,
(false, true) => false,
(false, false) => unsafe { CStr::from_ptr(self.0) == CStr::from_ptr(other.0) },
}
}
}
impl Eq for StringPointer {}
impl Hash for StringPointer {
fn hash<H: Hasher>(&self, h: &mut H) {
if self.0.is_null() {
h.write_usize(0)
} else {
unsafe { CStr::from_ptr(self.0).hash(h) }
}
}
}
impl Drop for StringPointer {
fn drop(&mut self) {
unsafe {
libc::free(self.0.cast::<c_void>());
}
}
}
pub enum Enumerator<'a> {
PointerKeyed(hash_map::IterMut<'a, VoidPointer, VoidPointer>),
StringKeyed(hash_map::IterMut<'a, StringPointer, VoidPointer>),
}
pub mod ffi {
use std::{
ffi::{c_int, c_uint, c_void},
mem, ptr,
};
use super::{Enumerator, HashTable, StringPointer, VoidPointer};
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMCreateIdentityHashTable() -> *mut HashTable {
Box::leak(Box::new(HashTable::new_pointer_keyed()))
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMCreateStringHashTable() -> *mut HashTable {
Box::leak(Box::new(HashTable::new_string_keyed()))
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMFreeHashTable(table: *mut HashTable) {
if !table.is_null() {
let _ = unsafe { Box::from_raw(table) };
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMResetHashTable(table: *mut HashTable) {
if !table.is_null() {
unsafe {
(*table).clear();
}
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMCountHashTable(table: *mut HashTable) -> c_uint {
if table.is_null() {
0
} else {
(unsafe { (*table).len() }) as c_uint
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMHashGet(table: *mut HashTable, key: *const c_void) -> *mut c_void {
if table.is_null() {
return ptr::null_mut();
}
let key = key.cast::<i8>();
(unsafe { (*table).get(key) })
.map(|v| v.cast::<c_void>())
.unwrap_or(ptr::null_mut())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMHashGetItemAndKey(
table: *mut HashTable,
key: *const c_void,
value_dest: *mut *mut c_void,
key_dest: *mut *const c_void,
) -> c_int {
if table.is_null() {
return 0;
}
let table = unsafe { &mut *table };
match table {
HashTable::PointerKeyed(m) => {
let key = VoidPointer(key.cast::<i8>().cast_mut());
let result = match m.get_key_value_mut(&key) {
Some((k, v)) => {
unsafe {
*key_dest = k.0.cast::<c_void>();
*value_dest = v.0.cast::<c_void>();
}
1
}
None => 0,
};
mem::forget(key);
result
}
HashTable::StringKeyed(m) => {
let key = StringPointer(key.cast::<i8>().cast_mut());
let result = match m.get_key_value_mut(&key) {
Some((k, v)) => {
unsafe {
*key_dest = k.0.cast::<c_void>();
*value_dest = v.0.cast::<c_void>();
}
1
}
None => 0,
};
mem::forget(key);
return result;
}
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMHashInsert(
table: *mut HashTable,
key: *mut c_void,
data: *mut c_void,
) -> *mut c_void {
if table.is_null() {
return ptr::null_mut();
}
match unsafe { (*table).insert(key.cast::<i8>(), VoidPointer(data.cast::<i8>())) } {
Some(v) => {
let raw = v.0;
mem::forget(v);
raw.cast::<c_void>()
}
None => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn WMHashRemove(table: *mut HashTable, key: *mut c_void) {
if table.is_null() {
return;
}
unsafe {
(*table).remove(key.cast::<i8>());
}
}
/// Important note: this may leak memory if you don't pass the enumerator
/// back to [`WMFreeHashEnumerator`]. This is a breaking change from the
/// original C implementation, which did not require any resource cleanup.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMEnumerateHashTable(
table: *mut HashTable,
) -> *mut Enumerator<'static> {
if table.is_null() {
return ptr::null_mut();
}
let table = unsafe { &mut *table };
match table {
HashTable::PointerKeyed(m) => {
Box::leak(Box::new(Enumerator::PointerKeyed(m.iter_mut())))
}
HashTable::StringKeyed(m) => Box::leak(Box::new(Enumerator::StringKeyed(m.iter_mut()))),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMFreeHashEnumerator(e: *mut Enumerator<'static>) {
if !e.is_null() {
let _ = unsafe { Box::from_raw(e) };
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMNextHashEnumeratorItem(e: *mut Enumerator<'static>) -> *mut c_void {
if e.is_null() {
return ptr::null_mut();
}
let e = unsafe { &mut *e };
match e {
Enumerator::PointerKeyed(i) => match i.next() {
Some((_, v)) => v.0.cast::<c_void>(),
None => ptr::null_mut(),
},
Enumerator::StringKeyed(i) => match i.next() {
Some((_, v)) => v.0.cast::<c_void>(),
None => ptr::null_mut(),
},
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMNextHashEnumeratorKey(e: *mut Enumerator<'static>) -> *mut c_void {
if e.is_null() {
return ptr::null_mut();
}
let e = unsafe { &mut *e };
match e {
Enumerator::PointerKeyed(i) => match i.next() {
Some((k, _)) => k.0.cast::<c_void>(),
None => ptr::null_mut(),
},
Enumerator::StringKeyed(i) => match i.next() {
Some((k, _)) => k.0.cast::<c_void>(),
None => ptr::null_mut(),
},
}
}
}

View File

@@ -2,5 +2,6 @@ pub mod array;
pub mod data;
pub mod defines;
pub mod find_file;
pub mod hash_table;
pub mod memory;
pub mod prop_list;