mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2025-03-21 01:41:10 +00:00
[util] Add a listener object
I decided cvars and input buttons/axes need listeners so any changes to them can be propagated. This will make using cvars in bindings feasible and I have an idea for automatic imt switching that would benefit from listeners attached to buttons and cvars.
This commit is contained in:
parent
67735182d7
commit
c069e7754f
3 changed files with 222 additions and 0 deletions
158
include/QF/listener.h
Normal file
158
include/QF/listener.h
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
listener.h
|
||||
|
||||
Listener objects
|
||||
|
||||
Copyright (C) 2021 Bill Currie <bill@taniwha.org>
|
||||
|
||||
Author: Bill Currie <bill@taniwha.org>
|
||||
Date: 2021/11/25
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
|
||||
Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __QF_listener_h
|
||||
#define __QF_listener_h
|
||||
|
||||
#include "QF/darray.h"
|
||||
|
||||
/** \defgroup listener Listeners
|
||||
\ingroup utils
|
||||
|
||||
Listener objects
|
||||
*/
|
||||
///@{
|
||||
|
||||
/** The structure defs for a listener set for an object of the given type.
|
||||
|
||||
This is just the defs of a struct delcaration: it is useless on its own.
|
||||
The intended usage is something like:
|
||||
|
||||
typedef struct obj_listenerset_s LISTENER_SET_TYPE(obj_t)
|
||||
obj_listenerset_t;
|
||||
|
||||
This allows full flexibility in just how the actual type is used.
|
||||
|
||||
The \a lfunc field is the function that will be called whenever the
|
||||
listener is invoked. \a ldata is an aribtrary pointer that is passed to
|
||||
the function as its \a data parameter. The function's \a obj parameter
|
||||
is the \a obj parameter passed to LISTENER_INVOKE(), and is intended to
|
||||
be the object being listened to.
|
||||
|
||||
There can be any number of \a lfunc, \a ldata pairs, but they should be
|
||||
unique, though this is not enforced.
|
||||
|
||||
\param type The type of the listened objects. A pointer to an object
|
||||
being listend to is passed to LISTENER_INVOKE() and that
|
||||
pointer is passed on to each listener function in the
|
||||
listener set.
|
||||
\hideinitializer
|
||||
*/
|
||||
#define LISTENER_SET_TYPE(type) \
|
||||
DARRAY_TYPE(struct { \
|
||||
void (*lfunc) (void *data, const type *obj); \
|
||||
void *ldata; \
|
||||
})
|
||||
|
||||
#define LISTENER_SET_STATIC_INIT(g) DARRAY_STATIC_INIT(g)
|
||||
|
||||
/** Initialize the listener set.
|
||||
|
||||
The set is initialized to be emtpy.
|
||||
|
||||
\param lset The *address* of the listener set being initialized (ie,
|
||||
a pointer to the listener set).
|
||||
\param growSice Amount by which the underlying array will grow.
|
||||
\hideinitializer
|
||||
*/
|
||||
#define LISTENER_SET_INIT(lset, growSice) DARRAY_INIT(lset, growSice)
|
||||
|
||||
/** Add a listener function/data par to the listener set.
|
||||
|
||||
The function's first parameter is the \a data pointer in the \a func
|
||||
\a data pair. The function's second parameter is the \a obj parameter
|
||||
of LISTENER_INVOKE(). Thus each added listener function will be called
|
||||
with its associated data pointer and the \a obj parameter from
|
||||
LISTENER_INVOKE().
|
||||
|
||||
Each pair should be unique in order to avoid double-action and so
|
||||
listeners can be removed properly.
|
||||
|
||||
\param lset The *address* of the listener set being modified (ie,
|
||||
a pointer to the listener set).
|
||||
\param func The function to be called when the listener is invoked.
|
||||
\param data Arbitrary pointer passed on to the function as its first
|
||||
parameter when when the listener is invoked.
|
||||
\hideinitializer
|
||||
*/
|
||||
#define LISTENER_ADD(lset, func, data) \
|
||||
do { \
|
||||
__auto_type ls = (lset); \
|
||||
typeof(ls->a[0]) l = {(func), (data)}; \
|
||||
DARRAY_APPEND (ls, l); \
|
||||
} while (0)
|
||||
|
||||
/** Remove a listener function/data pair from the listener set.
|
||||
|
||||
Each individual listener is identifed by its \a func \a data pair. Only
|
||||
the first instance of a pair is removed, thus only unique pairs should be
|
||||
added.
|
||||
|
||||
\param lset The *address* of the listener set being modified (ie,
|
||||
a pointer to the listener set).
|
||||
\param func The listener function being removed.
|
||||
\param data The function's associated data pointer.
|
||||
\hideinitializer
|
||||
*/
|
||||
#define LISTENER_REMOVE(lset, func, data) \
|
||||
do { \
|
||||
__auto_type ls = (lset); \
|
||||
typeof(ls->a[0]) l = {(func), (data)}; \
|
||||
for (size_t i = 0; i < ls->size; i++) { \
|
||||
if (ls->a[i].lfunc == l.lfunc && ls->a[i].ldata == l.ldata) { \
|
||||
DARRAY_REMOVE_AT (ls, i); \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/** Call all listener functions in the listener set.
|
||||
|
||||
Each listener function is passed its data pointer as the first parameter
|
||||
and \a obj as the second parameter.
|
||||
|
||||
\param lset The *address* of the listener set being invoked (ie,
|
||||
a pointer to the listener set).
|
||||
\param obj Pointer passed to each listener function as its second
|
||||
parameter.
|
||||
\hideinitializer
|
||||
*/
|
||||
#define LISTENER_INVOKE(lset, obj) \
|
||||
do { \
|
||||
__auto_type ls = (lset); \
|
||||
__auto_type o = (obj); \
|
||||
for (size_t i = 0; i < ls->size; i++) { \
|
||||
ls->a[i].lfunc (ls->a[i].ldata, o); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
///@}
|
||||
|
||||
#endif//__QF_listener_h
|
|
@ -10,6 +10,7 @@ libs_util_tests = \
|
|||
libs/util/test/test-dq \
|
||||
libs/util/test/test-half \
|
||||
libs/util/test/test-heapsort \
|
||||
libs/util/test/test-listener \
|
||||
libs/util/test/test-mat3 \
|
||||
libs/util/test/test-mat4 \
|
||||
libs/util/test/test-plist \
|
||||
|
@ -71,6 +72,10 @@ libs_util_test_test_heapsort_SOURCES=libs/util/test/test-heapsort.c
|
|||
libs_util_test_test_heapsort_LDADD=libs/util/libQFutil.la
|
||||
libs_util_test_test_heapsort_DEPENDENCIES=libs/util/libQFutil.la
|
||||
|
||||
libs_util_test_test_listener_SOURCES=libs/util/test/test-listener.c
|
||||
libs_util_test_test_listener_LDADD=libs/util/libQFutil.la
|
||||
libs_util_test_test_listener_DEPENDENCIES=libs/util/libQFutil.la
|
||||
|
||||
libs_util_test_test_mat3_SOURCES=libs/util/test/test-mat3.c
|
||||
libs_util_test_test_mat3_LDADD=libs/util/libQFutil.la
|
||||
libs_util_test_test_mat3_DEPENDENCIES=libs/util/libQFutil.la
|
||||
|
|
59
libs/util/test/test-listener.c
Normal file
59
libs/util/test/test-listener.c
Normal file
|
@ -0,0 +1,59 @@
|
|||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
#define remove remove_renamed
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "QF/listener.h"
|
||||
#include "QF/sys.h"
|
||||
#undef remove
|
||||
|
||||
typedef struct obj_s {
|
||||
int count;
|
||||
} obj_t;
|
||||
|
||||
struct LISTENER_SET_TYPE (obj_t) listeners = LISTENER_SET_STATIC_INIT(8);
|
||||
|
||||
static void
|
||||
listener_a (void *data, const obj_t *obj)
|
||||
{
|
||||
*(int *) data += 1;
|
||||
((obj_t *) obj)->count += 1;
|
||||
}
|
||||
|
||||
static void
|
||||
listener_b (void *data, const obj_t *obj)
|
||||
{
|
||||
*(int *) data += 2;
|
||||
((obj_t *) obj)->count += 2;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, const char **argv)
|
||||
{
|
||||
int res = 0;
|
||||
int a_count = 0;
|
||||
int b_count = 0;
|
||||
obj_t obj = { 0 };
|
||||
|
||||
LISTENER_ADD (&listeners, listener_a, &a_count);
|
||||
LISTENER_ADD (&listeners, listener_b, &b_count);
|
||||
LISTENER_INVOKE (&listeners, &obj);
|
||||
if (a_count != 1 || b_count != 2) {
|
||||
Sys_Error ("a_count = %d, b_count = %d", a_count, b_count);
|
||||
}
|
||||
if (obj.count != 3) {
|
||||
Sys_Error ("obj.count = %d", obj.count);
|
||||
}
|
||||
LISTENER_REMOVE (&listeners, listener_b, &b_count);
|
||||
LISTENER_INVOKE (&listeners, &obj);
|
||||
if (a_count != 2 || b_count != 2) {
|
||||
Sys_Error ("a_count = %d, b_count = %d", a_count, b_count);
|
||||
}
|
||||
if (obj.count != 4) {
|
||||
Sys_Error ("obj.count = %d", obj.count);
|
||||
}
|
||||
return res;
|
||||
}
|
Loading…
Reference in a new issue