hashtable: Redesign the hashtable API.

This was intended to make the API public, so SDL_hashtable.h got an extreme
documentation makeover, but for now this remains a private header.

This makes several significant interface changes to SDL_HashTable, and
improves code that makes use of it in various ways.

- The ability to make "stackable" tables is removed. Apparently this still
  worked with the current implementation, but I could see a future
  implementation struggle mightily to support this. It'll be better for
  something external to build on top of the table if it needs it, inserting a
  linked list of stacked items as the hash values and managing them separately.
  There was only one place in SDL using this, unnecessarily, and that has also
  been cleaned up to not need it.
- You no longer specify "buckets" when creating a table, but rather an
  estimated number of items the table is meant to hold. The bucket count was
  crucial to our classic hashtable implementation, but meant less once we
  moved to an Open Addressing implementation anyhow, since the bucket count
  isn't static (and they aren't really "buckets" anymore either). Now you
  can just report how many items you think the hash will hold and SDL will
  allocate a reasonable default for you...or 0 to not guess, and SDL will
  start small and grow as necessary, which is often the correct thing to do.
- There's no more SDL_IterateHashTableKey because there's no more "stackable"
  hash tables.
- SDL_IterateHashTable() now uses a callback, which matches other parts of SDL,
  and also lets us hold the read-lock for the entire iteration and get rid of
  the goofy iterator state variable.
- SDL_InsertIntoHashTable() now lets you specify whether to replace existing
  keys or fail if the key already exists.
- Callbacks now use SDL conventions (userdata as the first param).
- Other naming convention fixes.

I discovered we use a lot of hash tables in SDL3 internally. :) So the bulk
of this work is fixing up that code to use the new interfaces, and simplifying
things (like checking for an item to remove it if it already exists before
inserting a replacement...just do the insert atomically, it'll do all that
for you!).
This commit is contained in:
Ryan C. Gordon
2025-02-13 16:13:43 -05:00
parent 4a9b579195
commit 84a236c92e
13 changed files with 1344 additions and 866 deletions

View File

@@ -282,21 +282,6 @@ static void ClosePhysicalCamera(SDL_Camera *device)
device->adjust_timestamp = 0;
}
// this must not be called while `device` is still in a device list, or while a device's camera thread is still running.
static void DestroyPhysicalCamera(SDL_Camera *device)
{
if (device) {
// Destroy any logical devices that still exist...
ClosePhysicalCamera(device);
camera_driver.impl.FreeDeviceHandle(device);
SDL_DestroyMutex(device->lock);
SDL_free(device->all_specs);
SDL_free(device->name);
SDL_free(device);
}
}
// Don't hold the device lock when calling this, as we may destroy the device!
void UnrefPhysicalCamera(SDL_Camera *device)
{
@@ -307,7 +292,6 @@ void UnrefPhysicalCamera(SDL_Camera *device)
SDL_AddAtomicInt(&camera_driver.device_count, -1);
}
SDL_UnlockRWLock(camera_driver.device_hash_lock);
DestroyPhysicalCamera(device); // ...and nuke it.
}
}
@@ -500,7 +484,7 @@ SDL_Camera *SDL_AddCamera(const char *name, SDL_CameraPosition position, int num
RefPhysicalCamera(device);
SDL_LockRWLockForWriting(camera_driver.device_hash_lock);
if (SDL_InsertIntoHashTable(camera_driver.device_hash, (const void *) (uintptr_t) device->instance_id, device)) {
if (SDL_InsertIntoHashTable(camera_driver.device_hash, (const void *) (uintptr_t) device->instance_id, device, false)) {
SDL_AddAtomicInt(&camera_driver.device_count, 1);
} else {
SDL_DestroyMutex(device->lock);
@@ -624,7 +608,25 @@ void SDL_CameraPermissionOutcome(SDL_Camera *device, bool approved)
}
}
typedef struct FindOnePhysicalCameraByCallbackData
{
bool (*callback)(SDL_Camera *device, void *userdata);
void *userdata;
SDL_Camera *device;
} FindOnePhysicalCameraByCallbackData;
static bool SDLCALL FindOnePhysicalCameraByCallback(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
{
FindOnePhysicalCameraByCallbackData *data = (FindOnePhysicalCameraByCallbackData *) userdata;
SDL_Camera *device = (SDL_Camera *) value;
if (data->callback(device, data->userdata)) {
data->device = device;
return false; // stop iterating.
}
return true; // keep iterating.
}
// !!! FIXME: this doesn't follow SDL convention of `userdata` being the first param of the callback.
SDL_Camera *SDL_FindPhysicalCameraByCallback(bool (*callback)(SDL_Camera *device, void *userdata), void *userdata)
{
if (!SDL_GetCurrentCameraDriver()) {
@@ -632,23 +634,17 @@ SDL_Camera *SDL_FindPhysicalCameraByCallback(bool (*callback)(SDL_Camera *device
return NULL;
}
const void *key;
const void *value;
void *iter = NULL;
FindOnePhysicalCameraByCallbackData data = { callback, userdata, NULL };
SDL_LockRWLockForReading(camera_driver.device_hash_lock);
while (SDL_IterateHashTable(camera_driver.device_hash, &key, &value, &iter)) {
SDL_Camera *device = (SDL_Camera *) value;
if (callback(device, userdata)) { // found it?
SDL_UnlockRWLock(camera_driver.device_hash_lock);
return device;
}
}
SDL_IterateHashTable(camera_driver.device_hash, FindOnePhysicalCameraByCallback, &data);
SDL_UnlockRWLock(camera_driver.device_hash_lock);
SDL_SetError("Device not found");
return NULL;
if (!data.device) {
SDL_SetError("Device not found");
}
return data.device;
}
void SDL_CloseCamera(SDL_Camera *camera)
@@ -704,6 +700,19 @@ SDL_CameraPosition SDL_GetCameraPosition(SDL_CameraID instance_id)
}
typedef struct GetOneCameraData
{
SDL_CameraID *result;
int devs_seen;
} GetOneCameraData;
static bool SDLCALL GetOneCamera(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
{
GetOneCameraData *data = (GetOneCameraData *) userdata;
data->result[data->devs_seen++] = (SDL_CameraID) (uintptr_t) key;
return true; // keep iterating.
}
SDL_CameraID *SDL_GetCameras(int *count)
{
int dummy_count;
@@ -725,16 +734,10 @@ SDL_CameraID *SDL_GetCameras(int *count)
if (!result) {
num_devices = 0;
} else {
int devs_seen = 0;
const void *key;
const void *value;
void *iter = NULL;
while (SDL_IterateHashTable(camera_driver.device_hash, &key, &value, &iter)) {
result[devs_seen++] = (SDL_CameraID) (uintptr_t) key;
}
SDL_assert(devs_seen == num_devices);
result[devs_seen] = 0; // null-terminated.
GetOneCameraData data = { result, 0 };
SDL_IterateHashTable(camera_driver.device_hash, GetOneCamera, &data);
SDL_assert(data.devs_seen == num_devices);
result[num_devices] = 0; // null-terminated.
}
SDL_UnlockRWLock(camera_driver.device_hash_lock);
@@ -1381,37 +1384,26 @@ void SDL_QuitCamera(void)
SDL_free(i);
}
const void *key;
const void *value;
void *iter = NULL;
while (SDL_IterateHashTable(device_hash, &key, &value, &iter)) {
DestroyPhysicalCamera((SDL_Camera *) value);
}
SDL_DestroyHashTable(device_hash);
// Free the driver data
camera_driver.impl.Deinitialize();
SDL_DestroyRWLock(camera_driver.device_hash_lock);
SDL_DestroyHashTable(device_hash);
SDL_zero(camera_driver);
}
static Uint32 HashCameraID(const void *key, void *data)
// Physical camera objects are only destroyed when removed from the device hash.
static void SDLCALL DestroyCameraHashItem(void *userdata, const void *key, const void *value)
{
// The values are unique incrementing integers, starting at 1, so just return minus 1 to start with bucket zero.
return ((Uint32) ((uintptr_t) key)) - 1;
}
static bool MatchCameraID(const void *a, const void *b, void *data)
{
return (a == b); // simple integers, just compare them as pointer values.
}
static void NukeCameraHashItem(const void *key, const void *value, void *data)
{
// no-op, keys and values in this hashtable are treated as Plain Old Data and don't get freed here.
SDL_Camera *device = (SDL_Camera *) userdata;
ClosePhysicalCamera(device);
camera_driver.impl.FreeDeviceHandle(device);
SDL_DestroyMutex(device->lock);
SDL_free(device->all_specs);
SDL_free(device->name);
SDL_free(device);
}
bool SDL_CameraInit(const char *driver_name)
@@ -1425,7 +1417,7 @@ bool SDL_CameraInit(const char *driver_name)
return false;
}
SDL_HashTable *device_hash = SDL_CreateHashTable(NULL, 8, HashCameraID, MatchCameraID, NukeCameraHashItem, false, false);
SDL_HashTable *device_hash = SDL_CreateHashTable(0, false, SDL_HashID, SDL_KeyMatchID, DestroyCameraHashItem, NULL);
if (!device_hash) {
SDL_DestroyRWLock(device_hash_lock);
return false;

View File

@@ -190,7 +190,7 @@ typedef struct SDL_CameraDriver
const char *desc; // The description of this camera driver
SDL_CameraDriverImpl impl; // the backend's interface
SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash`
SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash` // !!! FIXME: device_hash _also_ has a rwlock, see if we still need this one.
SDL_HashTable *device_hash; // the collection of currently-available camera devices
SDL_PendingCameraEvent pending_events;
SDL_PendingCameraEvent *pending_events_tail;