Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions include/libbase/def.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,20 @@
})
#endif // !defined(BS_MAX)

/** Helper to retrieve the base container, given a pointer to an element. */
/**
* Helper to retrieve the base container, given a pointer to an element.
*
* NULL-safe, ie. returns NULL if elem_ptr is NULL.
*/
#define BS_CONTAINER_OF(elem_ptr, container_type, elem_field) \
(container_type*)( \
(uint8_t*)(elem_ptr) - \
offsetof(container_type, elem_field))
({ \
__typeof__(elem_ptr) __elem_ptr = (elem_ptr); \
(NULL == __elem_ptr ? \
(container_type*)__elem_ptr : \
(container_type*)( \
(uint8_t*)(elem_ptr) - \
offsetof(container_type, elem_field))); \
})

#endif /* __LIBBASE_DEF_H__ */
/* == End of def.h ========================================================= */
128 changes: 128 additions & 0 deletions include/libbase/signal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/* ========================================================================= */
/**
* @file signal.h
*
* Support for publisher/subscriber flows, similar to Wayland's server-side
* wl_signal. Implemented separately, so it can be used w/o Wayland's server.
*
* Copyright (c) 2026 Philipp Kaeser
*
* @copyright
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __SIGNAL_H__
#define __SIGNAL_H__

#include <libbase/libbase.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

struct bs_listener;

/** Notifier function for a listener. */
typedef void (*bs_signal_notify_t)(
struct bs_listener *listener_ptr,
void *data_ptr);

/** A signal (the publisher). */
struct bs_signal {
/** Subscribed listeners, through @ref bs_listener::dlnode. */
bs_dllist_t listeners;
};

/** A listener (the subscriber). */
struct bs_listener {
/** Element of @ref bs_signal::listeners. */
bs_dllist_node_t dlnode;
/** The notifier for this subscriber. */
bs_signal_notify_t notify;
};

/**
* Emit the signal, with the provided argument.
*
* Calls each listener, with `data_ptr` as argument. It is safe to disconnect
* `listener_ptr` from `signal_ptr`, but not to modify other listeners.
*
* @param signal_ptr
* @param data_ptr
*/
void bs_signal_emit(
const struct bs_signal *signal_ptr,
void *data_ptr);

/**
* Connects the listener with the provided notification function to the signal.
*
* @param signal_ptr
* @param listener_ptr
* @param notify
*/
void bs_signal_connect(
struct bs_signal *signal_ptr,
struct bs_listener *listener_ptr,
bs_signal_notify_t notify);

/**
* Disconnects the listener from the signal.
*
* @param signal_ptr
* @param listener_ptr
*/
void bs_signal_disconnect(
struct bs_signal *signal_ptr,
struct bs_listener *listener_ptr);

/** A listener that can be used in unit tests. */
struct bs_test_listener {
/** The listener. */
struct bs_listener listener;
/** Calls since initialization or last @ref bs_test_listener_clear. */
size_t calls;
/** Argumet of last call, if any since @ref bs_test_listener_clear. */
void *last_data_ptr;
};

/**
* Clears @ref bs_test_listener::calls and
* @ref bs_test_listener::last_data_ptr.
*
* @param test_listener_ptr
*/
void bs_test_listener_clear(
struct bs_test_listener *test_listener_ptr);

/** Connects the test listener to `signal_ptr`. */
void bs_test_listener_connect(
struct bs_signal *signal_ptr,
struct bs_test_listener *test_listener_ptr);

/** Disconnects the test listener to `signal_ptr`. */
void bs_test_listener_disconnect(
struct bs_signal *signal_ptr,
struct bs_test_listener *test_listener_ptr);

/** Unit tests set. */
extern const bs_test_set_t bs_signal_test_set;

#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus

#endif // __SIGNAL_H__
/* == End of signal.h ====================================================== */
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ set(public_header_files
ptr_stack.h
ptr_vector.h
ref.h
signal.h
sock.h
strutil.h
subprocess.h
Expand All @@ -54,6 +55,7 @@ set(sources
ptr_stack.c
ptr_vector.c
ref.c
signal.c
sock.c
strutil.c
subprocess.c
Expand Down
182 changes: 182 additions & 0 deletions src/signal.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/* ========================================================================= */
/**
* @file signal.c
* Copyright (c) 2026 Philipp Kaeser
*
* @copyright
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <libbase/signal.h>

#include <libbase/libbase.h>
#include <stdbool.h>

/* == Declarations ========================================================= */

static void _bs_signal_notify_dlnode(
bs_dllist_node_t *dlnode_ptr,
void *ud_ptr);
static void _bs_test_listener_notify(
struct bs_listener *listener_ptr,
void *data_ptr);

/* == Exported methods ===================================================== */

/* ------------------------------------------------------------------------- */
void bs_signal_emit(
const struct bs_signal *signal_ptr,
void *data_ptr)
{
bs_dllist_for_each(
&signal_ptr->listeners,
_bs_signal_notify_dlnode,
data_ptr);
}

/* ------------------------------------------------------------------------- */
void bs_signal_connect(
struct bs_signal *signal_ptr,
struct bs_listener *listener_ptr,
bs_signal_notify_t notify)
{
listener_ptr->notify = notify;
bs_dllist_push_back(
&signal_ptr->listeners,
&listener_ptr->dlnode);
}

/* ------------------------------------------------------------------------- */
void bs_signal_disconnect(
struct bs_signal *signal_ptr,
struct bs_listener *listener_ptr)
{
bs_dllist_remove(
&signal_ptr->listeners,
&listener_ptr->dlnode);
}

/* ------------------------------------------------------------------------- */
void bs_test_listener_clear(
struct bs_test_listener *test_listener_ptr)
{
test_listener_ptr->calls = 0;
test_listener_ptr->last_data_ptr = NULL;
}

/* ------------------------------------------------------------------------- */
void bs_test_listener_connect(
struct bs_signal *signal_ptr,
struct bs_test_listener *test_listener_ptr)
{
bs_signal_connect(
signal_ptr,
&test_listener_ptr->listener,
_bs_test_listener_notify);
}

/* ------------------------------------------------------------------------- */
void bs_test_listener_disconnect(
struct bs_signal *signal_ptr,
struct bs_test_listener *test_listener_ptr)
{
bs_signal_disconnect(
signal_ptr,
&test_listener_ptr->listener);
}

/* == Local (static) methods =============================================== */

/* ------------------------------------------------------------------------- */
/** Iterator for @ref bs_dllist_for_each: Calls @ref bs_listener::notify. */
void _bs_signal_notify_dlnode(
bs_dllist_node_t *dlnode_ptr,
void *ud_ptr)
{
struct bs_listener *listener_ptr = BS_CONTAINER_OF(
dlnode_ptr, struct bs_listener, dlnode);
listener_ptr->notify(listener_ptr, ud_ptr);
}

/* ------------------------------------------------------------------------- */
void _bs_test_listener_notify(
struct bs_listener *listener_ptr,
void *data_ptr)
{
struct bs_test_listener *tl = BS_CONTAINER_OF(
listener_ptr, struct bs_test_listener, listener);
tl->calls++;
tl->last_data_ptr = data_ptr;
}

/* == Unit tests =========================================================== */

void _bs_signal_test_simple(bs_test_t *test_ptr);

/** Unit test cases. */
static const bs_test_case_t _bs_signal_test_cases[] = {
{ true, "simple", _bs_signal_test_simple },
BS_TEST_CASE_SENTINEL()
};

/** Unit tests set. */
const bs_test_set_t bs_signal_test_set = BS_TEST_SET(
true, "signal", _bs_signal_test_cases);

/* ------------------------------------------------------------------------- */
/** Simple test case for the signal. */
void _bs_signal_test_simple(bs_test_t *test_ptr)
{
struct bs_test_listener tl1 = {}, tl2 = {};
struct bs_signal s = {};

bs_test_listener_connect(&s, &tl1);

BS_TEST_VERIFY_EQ(
test_ptr,
&tl1,
BS_CONTAINER_OF(&tl1.calls, struct bs_test_listener, calls));
void *p = NULL;
BS_TEST_VERIFY_EQ(
test_ptr,
NULL,
BS_CONTAINER_OF(p, struct bs_test_listener, calls));

BS_TEST_VERIFY_EQ(test_ptr, 0, tl1.calls);
BS_TEST_VERIFY_EQ(test_ptr, 0, tl2.calls);

bs_signal_emit(&s, _bs_signal_test_simple);
BS_TEST_VERIFY_EQ(test_ptr, 1, tl1.calls);
BS_TEST_VERIFY_EQ(test_ptr, _bs_signal_test_simple, tl1.last_data_ptr);
BS_TEST_VERIFY_EQ(test_ptr, 0, tl2.calls);

bs_test_listener_connect(&s, &tl2);
bs_signal_emit(&s, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 2, tl1.calls);
BS_TEST_VERIFY_EQ(test_ptr, NULL, tl1.last_data_ptr);
BS_TEST_VERIFY_EQ(test_ptr, 1, tl2.calls);
BS_TEST_VERIFY_EQ(test_ptr, NULL, tl2.last_data_ptr);

bs_test_listener_disconnect(&s, &tl1);
bs_signal_emit(&s, NULL);
BS_TEST_VERIFY_EQ(test_ptr, 2, tl2.calls);

bs_test_listener_clear(&tl1);
BS_TEST_VERIFY_EQ(test_ptr, 0, tl1.calls);
bs_test_listener_clear(&tl2);
BS_TEST_VERIFY_EQ(test_ptr, 0, tl2.calls);
}

/* == End of signal.c ====================================================== */
2 changes: 2 additions & 0 deletions tests/libbase_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*/

#include <libbase/libbase.h>
#include <libbase/signal.h>
#include <limits.h>
#include <poll.h>
#include <signal.h>
Expand Down Expand Up @@ -269,6 +270,7 @@ int main(int argc, const char **argv)
&bs_ptr_stack_test_set,
&bs_ptr_vector_test_set,
&bs_ref_test_set,
&bs_signal_test_set,
&bs_strutil_test_set,
&bs_subprocess_extra_test_set,
&bs_subprocess_test_set,
Expand Down
Loading