Compare commits
16 Commits
4c654cd621
...
851788d562
Author | SHA1 | Date |
---|---|---|
Vftdan | 851788d562 | |
Vftdan | b64b7ebc39 | |
Vftdan | 99f78f16c5 | |
Vftdan | 184e7d8514 | |
Vftdan | bca67c9663 | |
Vftdan | 125e2ad5d8 | |
Vftdan | 399f0b278e | |
Vftdan | 0967e9eb85 | |
Vftdan | 91ec8ef423 | |
Vftdan | 95354cbf95 | |
Vftdan | 7e4e56d8e2 | |
Vftdan | 2d5dc40c19 | |
Vftdan | 7cdbcaf25e | |
Vftdan | f19732d364 | |
Vftdan | 12af4b35c7 | |
Vftdan | eaa64f5b4c |
2
Makefile
2
Makefile
|
@ -12,7 +12,7 @@ CPPFLAGS += $(shell pkg-config --cflags $(DEPS))
|
|||
LDLIBS += $(shell pkg-config --libs $(DEPS))
|
||||
INTERP ?=
|
||||
MAIN = main
|
||||
OBJS = main.o events.o processing.o graph.o config.o event_code_names.o hash_table.o module_registry.o event_predicate.o nodes/getchar.o nodes/print.o nodes/evdev.o nodes/tee.o nodes/router.o nodes/modifiers.o nodes/modify_predicate.o nodes/uinput.o nodes/assign.o
|
||||
OBJS = main.o events.o processing.o graph.o config.o event_code_names.o hash_table.o queue.o module_registry.o event_predicate.o nodes/getchar.o nodes/print.o nodes/evdev.o nodes/tee.o nodes/router.o nodes/modifiers.o nodes/modify_predicate.o nodes/uinput.o nodes/assign.o nodes/differentiate.o nodes/scale.o nodes/window.o
|
||||
|
||||
all: $(MAIN)
|
||||
|
||||
|
|
5
defs.h
5
defs.h
|
@ -12,8 +12,9 @@
|
|||
// So for structs it is usually actual downcast, but for unions it is an upcast
|
||||
#define DOWNCAST(contype, basename, ptr) containerof(ptr, contype, as_##basename)
|
||||
// Expects ptr to be of type srctype* or void*, returns (dsttype*)ptr
|
||||
#define IMPLICIT_CAST(dsttype, srctype, ptr) (((union { typeof(srctype) *src; typeof(dsttype) *dst; }){.src = ptr}).dst)
|
||||
#define T_ALLOC(count, T) ((T*)calloc(count, sizeof(T)))
|
||||
#define IMPLICIT_CAST(dsttype, srctype, ptr) (((union { typeof(srctype) *src; typeof(dsttype) *dst; }){.src = (ptr)}).dst)
|
||||
#define T_ALLOC(count, T) ((T*)calloc((count), sizeof(T)))
|
||||
#define T_REALLOC(ptr, count, T) ((T*)reallocarray(IMPLICIT_CAST(void, T, ptr), (count), sizeof(T)))
|
||||
|
||||
#define MODULE_CONSTRUCTOR(name) __attribute__((constructor)) static void name(void)
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ event_predicate_list_extend(EventPredicateList * lst)
|
|||
|
||||
EventPredicate *new_values;
|
||||
if (lst->values) {
|
||||
new_values = reallocarray(lst->values, capacity, sizeof(EventPredicate));
|
||||
new_values = T_REALLOC(lst->values, capacity, EventPredicate);
|
||||
} else {
|
||||
new_values = T_ALLOC(capacity, EventPredicate);
|
||||
}
|
||||
|
|
3
events.c
3
events.c
|
@ -35,6 +35,9 @@ EventNode *
|
|||
event_create(const EventData * content)
|
||||
{
|
||||
EventNode * event = T_ALLOC(1, EventNode);
|
||||
if (!event) {
|
||||
return NULL;
|
||||
}
|
||||
if (content) {
|
||||
event->data = *content;
|
||||
event->data.modifiers = modifier_set_copy(content->modifiers);
|
||||
|
|
4
graph.c
4
graph.c
|
@ -5,7 +5,7 @@ static void
|
|||
graph_channel_list_resize(GraphChannelList * lst, size_t target)
|
||||
{
|
||||
if (target > lst->length) {
|
||||
GraphChannel **new_elements = reallocarray(lst->elements, target, sizeof(GraphChannel*));
|
||||
GraphChannel **new_elements = T_REALLOC(lst->elements, target, GraphChannel*);
|
||||
if (!new_elements) {
|
||||
return;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ channel_handle_event(EventPositionBase * self, EventNode * event)
|
|||
event->position = &target->as_EventPositionBase;
|
||||
event->input_index = ch->idx_end;
|
||||
target->as_EventPositionBase.waiting_new_event = false;
|
||||
return false; // Continue processing events
|
||||
return true; // Changes were made
|
||||
}
|
||||
|
||||
void
|
||||
|
|
33
hash_table.c
33
hash_table.c
|
@ -325,3 +325,36 @@ hash_table_find_impl(const HashTableDynamicData * ht, const HashTableKey key)
|
|||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool
|
||||
hash_table_delete_at_index_impl(HashTableDynamicData * ht, const HashTableIndex index)
|
||||
{
|
||||
if (!ht) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index < 0 || (size_t) index > ht->capacity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HashTableKeyEntry *key_entry_ptr = ht->key_array + index;
|
||||
void *value_ptr = ht->value_array + (ht->value_size * index);
|
||||
|
||||
if (!key_entry_ptr->key.bytes) {
|
||||
// No entry at index
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO find base_index and reduce its max_collision_offset when provably justified
|
||||
|
||||
hash_table_key_deinit_copied(&key_entry_ptr->key);
|
||||
ht->length -= 1;
|
||||
|
||||
void (*value_deinit)(void*) = ht->value_deinit;
|
||||
if (value_deinit) {
|
||||
value_deinit(value_ptr);
|
||||
}
|
||||
memset(value_ptr, 0, ht->value_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -53,10 +53,19 @@ void hash_table_init_impl(HashTableDynamicData * dyndata, size_t value_size, voi
|
|||
void hash_table_deinit_impl(HashTableDynamicData * dyndata);
|
||||
HashTableIndex hash_table_insert_impl(HashTableDynamicData * dyndata, const HashTableKey key, const void * value_ptr);
|
||||
HashTableIndex hash_table_find_impl(const HashTableDynamicData * dyndata, const HashTableKey key);
|
||||
bool hash_table_delete_at_index_impl(HashTableDynamicData * dyndata, const HashTableIndex index);
|
||||
|
||||
__attribute__((unused)) inline static bool
|
||||
hash_table_delete_by_key_impl(HashTableDynamicData * dyndata, const HashTableKey key)
|
||||
{
|
||||
return hash_table_delete_at_index_impl(dyndata, hash_table_find_impl(dyndata, key));
|
||||
}
|
||||
|
||||
#define hash_table_init(ht, value_deinit) hash_table_init_impl(&(ht)->as_HashTableDynamicData, sizeof(*(ht)->value_array), IMPLICIT_CAST(void(void*), void(typeof((ht)->value_array)), value_deinit))
|
||||
#define hash_table_deinit(ht) hash_table_deinit_impl(&(ht)->as_HashTableDynamicData)
|
||||
#define hash_table_insert(ht, key, value_ptr) hash_table_insert_impl(&(ht)->as_HashTableDynamicData, key, IMPLICIT_CAST(const void, const typeof(*(ht)->value_array), value_ptr))
|
||||
#define hash_table_find(ht, key) hash_table_find_impl(&(ht)->as_HashTableDynamicData, key)
|
||||
#define hash_table_delete_at_index(ht, index) hash_table_delete_at_index_impl(&(ht)->as_HashTableDynamicData, index)
|
||||
#define hash_table_delete_by_key(ht, key) hash_table_delete_by_key_impl(&(ht)->as_HashTableDynamicData, key)
|
||||
|
||||
#endif /* end of include guard: HASH_TABLE_H_ */
|
||||
|
|
5
main.c
5
main.c
|
@ -99,7 +99,10 @@ main(int argc, char ** argv)
|
|||
config_t config_tree;
|
||||
FullConfig loaded_config;
|
||||
config_init(&config_tree);
|
||||
config_read_file(&config_tree, config_filename);
|
||||
if (config_read_file(&config_tree, config_filename) != CONFIG_TRUE) {
|
||||
fprintf(stderr, "Config syntax error: %s:%d: %s\n", config_error_file(&config_tree), config_error_line(&config_tree), config_error_text(&config_tree));
|
||||
exit(1);
|
||||
}
|
||||
config_set_auto_convert(&config_tree, CONFIG_TRUE);
|
||||
if (!load_config(config_root_setting(&config_tree), &loaded_config)) {
|
||||
perror("Failed to load config");
|
||||
|
|
|
@ -23,6 +23,9 @@ typedef enum {
|
|||
__attribute__((unused)) inline static ModifierSet
|
||||
modifier_set_copy(const ModifierSet old)
|
||||
{
|
||||
if (!old.byte_length) {
|
||||
return EMPTY_MODIFIER_SET;
|
||||
};
|
||||
ModifierSet result = old;
|
||||
result.bits = malloc(result.byte_length);
|
||||
if (!result.bits) {
|
||||
|
|
|
@ -35,6 +35,11 @@ handle_event(EventPositionBase * self, EventNode * event)
|
|||
}
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
event->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase;
|
||||
if (!event->position) {
|
||||
EventNode *orphaned = event;
|
||||
event = orphaned->prev;
|
||||
event_destroy(orphaned);
|
||||
}
|
||||
event = event->next;
|
||||
}
|
||||
return true;
|
||||
|
@ -58,21 +63,33 @@ create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEn
|
|||
has_payload = false;
|
||||
const config_setting_t *setting;
|
||||
|
||||
if ((setting = config_setting_get_member(config->options, "namespace"))) {
|
||||
has_ns = true;
|
||||
source.code.ns = env_resolve_constant(env, setting);
|
||||
}
|
||||
if ((setting = config_setting_get_member(config->options, "major"))) {
|
||||
has_maj = true;
|
||||
source.code.major = env_resolve_constant(env, setting);
|
||||
}
|
||||
if ((setting = config_setting_get_member(config->options, "minor"))) {
|
||||
has_min = true;
|
||||
source.code.minor = env_resolve_constant(env, setting);
|
||||
}
|
||||
if ((setting = config_setting_get_member(config->options, "payload"))) {
|
||||
has_payload = true;
|
||||
source.payload = env_resolve_constant(env, setting);
|
||||
if (config->options) {
|
||||
if ((setting = config_setting_get_member(config->options, "namespace"))) {
|
||||
has_ns = true;
|
||||
source.code.ns = env_resolve_constant(env, setting);
|
||||
}
|
||||
if ((setting = config_setting_get_member(config->options, "major"))) {
|
||||
has_maj = true;
|
||||
source.code.major = env_resolve_constant(env, setting);
|
||||
}
|
||||
if ((setting = config_setting_get_member(config->options, "minor"))) {
|
||||
has_min = true;
|
||||
source.code.minor = env_resolve_constant(env, setting);
|
||||
}
|
||||
if ((setting = config_setting_get_member(config->options, "payload"))) {
|
||||
has_payload = true;
|
||||
source.payload = env_resolve_constant(env, setting);
|
||||
}
|
||||
} else {
|
||||
// Prevent warning for uninitialized variable
|
||||
source = (EventData) {
|
||||
.code = {
|
||||
.ns = 0,
|
||||
.major = 0,
|
||||
.minor = 0,
|
||||
},
|
||||
.payload = 0,
|
||||
};
|
||||
}
|
||||
|
||||
*node = (AssignGraphNode) {
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
#include "../graph.h"
|
||||
#include "../module_registry.h"
|
||||
|
||||
typedef struct {
|
||||
GraphNode as_GraphNode;
|
||||
int64_t previous;
|
||||
} DifferentiateGraphNode;
|
||||
|
||||
static bool
|
||||
handle_event(EventPositionBase * self, EventNode * event)
|
||||
{
|
||||
DifferentiateGraphNode *node = DOWNCAST(DifferentiateGraphNode, GraphNode, DOWNCAST(GraphNode, EventPositionBase, self));
|
||||
size_t count = node->as_GraphNode.outputs.length;
|
||||
if (!count) {
|
||||
event_destroy(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t current = event->data.payload;
|
||||
event->data.payload = current - node->previous;
|
||||
node->previous = current;
|
||||
|
||||
if (count > 1) {
|
||||
count = event_replicate(event, count - 1) + 1;
|
||||
}
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
event->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase;
|
||||
if (!event->position) {
|
||||
EventNode *orphaned = event;
|
||||
event = orphaned->prev;
|
||||
event_destroy(orphaned);
|
||||
}
|
||||
event = event->next;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static GraphNode *
|
||||
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
|
||||
{
|
||||
(void) config;
|
||||
(void) env;
|
||||
DifferentiateGraphNode * node = T_ALLOC(1, DifferentiateGraphNode);
|
||||
if (!node) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int64_t initial = 0;
|
||||
if (config->options) {
|
||||
initial = env_resolve_constant(env, config_setting_get_member(config->options, "initial"));
|
||||
}
|
||||
|
||||
*node = (DifferentiateGraphNode) {
|
||||
.as_GraphNode = {
|
||||
.as_EventPositionBase = {
|
||||
.handle_event = &handle_event,
|
||||
.waiting_new_event = false,
|
||||
},
|
||||
.specification = spec,
|
||||
.inputs = EMPTY_GRAPH_CHANNEL_LIST,
|
||||
.outputs = EMPTY_GRAPH_CHANNEL_LIST,
|
||||
},
|
||||
.previous = initial,
|
||||
};
|
||||
return &node->as_GraphNode;
|
||||
}
|
||||
|
||||
static void destroy
|
||||
(GraphNodeSpecification * self, GraphNode * target)
|
||||
{
|
||||
(void) self;
|
||||
free(target);
|
||||
}
|
||||
|
||||
GraphNodeSpecification nodespec_differentiate = (GraphNodeSpecification) {
|
||||
.create = &create,
|
||||
.destroy = &destroy,
|
||||
.register_io = NULL,
|
||||
.name = "differentiate",
|
||||
.documentation = "Subtracts the previous event payload from the current one\nAccepts events on any connector\nSends events on all connectors"
|
||||
"\nOption 'initial' (optional): the value to subtract from the first event payload"
|
||||
,
|
||||
};
|
||||
|
||||
MODULE_CONSTRUCTOR(init)
|
||||
{
|
||||
register_graph_node_specification(&nodespec_differentiate);
|
||||
}
|
|
@ -77,7 +77,7 @@ create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEn
|
|||
}
|
||||
const char *filename = NULL;
|
||||
bool should_grab = false;
|
||||
if (config) {
|
||||
if (config->options) {
|
||||
node->namespace = env_resolve_constant(env, config_setting_get_member(config->options, "namespace"));
|
||||
should_grab = env_resolve_constant(env, config_setting_get_member(config->options, "grab")) != 0;
|
||||
config_setting_lookup_string(config->options, "file", &filename);
|
||||
|
|
|
@ -70,7 +70,7 @@ create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEn
|
|||
.handle_io = handle_io,
|
||||
.enabled = true,
|
||||
},
|
||||
.namespace = env_resolve_constant(env, config_setting_get_member(config->options, "namespace")),
|
||||
.namespace = config->options ? env_resolve_constant(env, config_setting_get_member(config->options, "namespace")) : 0,
|
||||
};
|
||||
return &node->as_GraphNode;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,11 @@ handle_event(EventPositionBase * self, EventNode * event)
|
|||
}
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
event->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase;
|
||||
if (!event->position) {
|
||||
EventNode *orphaned = event;
|
||||
event = orphaned->prev;
|
||||
event_destroy(orphaned);
|
||||
}
|
||||
event = event->next;
|
||||
}
|
||||
return true;
|
||||
|
@ -30,8 +35,10 @@ handle_event(EventPositionBase * self, EventNode * event)
|
|||
static GraphNode *
|
||||
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
|
||||
{
|
||||
(void) config;
|
||||
(void) env;
|
||||
if (!config->options) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ModifiersGraphNode * node = T_ALLOC(1, ModifiersGraphNode);
|
||||
if (!node) {
|
||||
return NULL;
|
||||
|
|
|
@ -50,8 +50,10 @@ handle_event(EventPositionBase * self, EventNode * event)
|
|||
static GraphNode *
|
||||
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
|
||||
{
|
||||
(void) config;
|
||||
(void) env;
|
||||
if (!config->options) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ModifyPredicateGraphNode * node = T_ALLOC(1, ModifyPredicateGraphNode);
|
||||
if (!node) {
|
||||
return NULL;
|
||||
|
|
|
@ -17,7 +17,11 @@ handle_event(EventPositionBase * self, EventNode * event)
|
|||
}
|
||||
if (event_predicate_apply(node->predicates[i], event) == EVPREDRES_ACCEPTED) {
|
||||
if (event_replicate(event, 1)) {
|
||||
event->next->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase;
|
||||
EventNode * replica = event->next;
|
||||
replica->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase;
|
||||
if (!replica->position) {
|
||||
event_destroy(replica);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +32,10 @@ handle_event(EventPositionBase * self, EventNode * event)
|
|||
static GraphNode *
|
||||
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
|
||||
{
|
||||
(void) config;
|
||||
(void) env;
|
||||
if (!config->options) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RouterGraphNode * node = T_ALLOC(1, RouterGraphNode);
|
||||
if (!node) {
|
||||
return NULL;
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
#include "../graph.h"
|
||||
#include "../module_registry.h"
|
||||
|
||||
typedef struct {
|
||||
GraphNode as_GraphNode;
|
||||
int64_t numerator;
|
||||
int64_t denominator;
|
||||
int64_t center;
|
||||
int64_t defect;
|
||||
bool amortize_rounding_error;
|
||||
} ScaleGraphNode;
|
||||
|
||||
static bool
|
||||
handle_event(EventPositionBase * self, EventNode * event)
|
||||
{
|
||||
ScaleGraphNode *node = DOWNCAST(ScaleGraphNode, GraphNode, DOWNCAST(GraphNode, EventPositionBase, self));
|
||||
size_t count = node->as_GraphNode.outputs.length;
|
||||
if (!count) {
|
||||
event_destroy(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t value = event->data.payload;
|
||||
value -= node->center;
|
||||
value *= node->numerator;
|
||||
if (node->amortize_rounding_error) {
|
||||
value += node->defect;
|
||||
}
|
||||
if (node->denominator) {
|
||||
uint64_t undivided = value;
|
||||
value /= node->denominator;
|
||||
node->defect = undivided - value * node->denominator;
|
||||
}
|
||||
value += node->center;
|
||||
event->data.payload = value;
|
||||
|
||||
if (count > 1) {
|
||||
count = event_replicate(event, count - 1) + 1;
|
||||
}
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
event->position = &node->as_GraphNode.outputs.elements[i]->as_EventPositionBase;
|
||||
if (!event->position) {
|
||||
EventNode *orphaned = event;
|
||||
event = orphaned->prev;
|
||||
event_destroy(orphaned);
|
||||
}
|
||||
event = event->next;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static GraphNode *
|
||||
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
|
||||
{
|
||||
(void) config;
|
||||
(void) env;
|
||||
ScaleGraphNode * node = T_ALLOC(1, ScaleGraphNode);
|
||||
if (!node) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int64_t
|
||||
numerator = 1,
|
||||
denominator = 1,
|
||||
center = 0;
|
||||
bool amortize_rounding_error = false;
|
||||
if (config->options) {
|
||||
numerator = env_resolve_constant_or(env, config_setting_get_member(config->options, "numerator"), numerator);
|
||||
denominator = env_resolve_constant_or(env, config_setting_get_member(config->options, "denominator"), denominator);
|
||||
center = env_resolve_constant_or(env, config_setting_get_member(config->options, "center"), center);
|
||||
amortize_rounding_error = env_resolve_constant(env, config_setting_get_member(config->options, "amortize_rounding_error")) != 0;
|
||||
}
|
||||
|
||||
*node = (ScaleGraphNode) {
|
||||
.as_GraphNode = {
|
||||
.as_EventPositionBase = {
|
||||
.handle_event = &handle_event,
|
||||
.waiting_new_event = false,
|
||||
},
|
||||
.specification = spec,
|
||||
.inputs = EMPTY_GRAPH_CHANNEL_LIST,
|
||||
.outputs = EMPTY_GRAPH_CHANNEL_LIST,
|
||||
},
|
||||
.numerator = numerator,
|
||||
.denominator = denominator,
|
||||
.center = center,
|
||||
.amortize_rounding_error = amortize_rounding_error,
|
||||
.defect = 0,
|
||||
};
|
||||
return &node->as_GraphNode;
|
||||
}
|
||||
|
||||
static void destroy
|
||||
(GraphNodeSpecification * self, GraphNode * target)
|
||||
{
|
||||
(void) self;
|
||||
free(target);
|
||||
}
|
||||
|
||||
GraphNodeSpecification nodespec_scale = (GraphNodeSpecification) {
|
||||
.create = &create,
|
||||
.destroy = &destroy,
|
||||
.register_io = NULL,
|
||||
.name = "scale",
|
||||
.documentation = "Multiplies event payload by a constant fraction\nAccepts events on any connector\nSends events on all connectors"
|
||||
"\nOption 'numerator' (optional): an integer to multiply by"
|
||||
"\nOption 'denominator' (optional): an integer to divide by"
|
||||
"\nOption 'center' (optional): an integer to scale around"
|
||||
"\nOption 'amortize_rounding_error' (optional): whether to adjust the new event value by the rounding error of the previous event value"
|
||||
,
|
||||
};
|
||||
|
||||
MODULE_CONSTRUCTOR(init)
|
||||
{
|
||||
register_graph_node_specification(&nodespec_scale);
|
||||
}
|
|
@ -15,6 +15,11 @@ handle_event(EventPositionBase * self, EventNode * event)
|
|||
}
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
event->position = &node->outputs.elements[i]->as_EventPositionBase;
|
||||
if (!event->position) {
|
||||
EventNode *orphaned = event;
|
||||
event = orphaned->prev;
|
||||
event_destroy(orphaned);
|
||||
}
|
||||
event = event->next;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -78,6 +78,10 @@ handle_event(EventPositionBase * self, EventNode * event)
|
|||
static GraphNode *
|
||||
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
|
||||
{
|
||||
if (!config->options) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
UinputGraphNode * node = T_ALLOC(1, UinputGraphNode);
|
||||
if (!node) {
|
||||
return NULL;
|
||||
|
@ -96,6 +100,12 @@ create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEn
|
|||
return NULL;
|
||||
}
|
||||
|
||||
size_t properties_length = 0;
|
||||
const config_setting_t *properties_setting = config_setting_get_member(config->options, "properties");
|
||||
if (properties_setting) {
|
||||
properties_length = config_setting_length(enabled_codes_setting);
|
||||
}
|
||||
|
||||
struct libevdev *dev = libevdev_new();
|
||||
if (!dev) {
|
||||
free(node);
|
||||
|
@ -107,6 +117,9 @@ create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEn
|
|||
for (size_t i = 0; i < codes_length; ++i) {
|
||||
load_enable_code(dev, config_setting_get_elem(enabled_codes_setting, i), env);
|
||||
}
|
||||
for (size_t i = 0; i < properties_length; ++i) {
|
||||
libevdev_enable_property(dev, (unsigned int) env_resolve_constant(env, config_setting_get_elem(properties_setting, i)));
|
||||
}
|
||||
|
||||
struct libevdev_uinput *uidev;
|
||||
int err;
|
||||
|
@ -158,6 +171,7 @@ GraphNodeSpecification nodespec_uinput = (GraphNodeSpecification) {
|
|||
.name = "uinput",
|
||||
.documentation = "Writes received events to a new uinput device\nAccepts events on any connector\nDoes not send events"
|
||||
"\nOption 'name' (required): device name provided to uinput"
|
||||
"\nOption 'properties' (optional): collection of integers --- input properties"
|
||||
"\nOption 'enabled_codes' (required): collection of event code specifications:"
|
||||
"\n\tField 'major' (required): evdev event type"
|
||||
"\n\tField 'minor' (required): evdev event code"
|
||||
|
|
|
@ -0,0 +1,329 @@
|
|||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include "../graph.h"
|
||||
#include "../module_registry.h"
|
||||
#include "../queue.h"
|
||||
#include "../hash_table.h"
|
||||
|
||||
typedef TYPED_HASH_TABLE(bool) EventSet;
|
||||
|
||||
typedef struct {
|
||||
GraphNode as_GraphNode;
|
||||
EventData terminator_prototype;
|
||||
bool has_terminator;
|
||||
bool is_jumping;
|
||||
bool has_max_time, has_max_length;
|
||||
RelativeTime max_time;
|
||||
size_t max_length;
|
||||
size_t additional_step;
|
||||
size_t skip_next;
|
||||
Queue buffer;
|
||||
EventSet buffered_set;
|
||||
} WindowGraphNode;
|
||||
|
||||
inline static HashTableKey
|
||||
hash_event_ptr(const EventNode * ptr) {
|
||||
return hash_table_key_from_bytes((void*)&ptr, sizeof(ptr));
|
||||
}
|
||||
|
||||
inline static bool
|
||||
event_set_has(const EventSet * set, const EventNode * element) {
|
||||
return hash_table_find(set, hash_event_ptr(element)) >= 0;
|
||||
}
|
||||
|
||||
inline static bool
|
||||
event_set_add(EventSet * set, const EventNode * element) {
|
||||
const bool value = true;
|
||||
return hash_table_insert(set, hash_event_ptr(element), &value) >= 0;
|
||||
}
|
||||
|
||||
inline static bool
|
||||
event_set_del(EventSet * set, const EventNode * element) {
|
||||
return hash_table_delete_by_key(set, hash_event_ptr(element));
|
||||
}
|
||||
|
||||
static void
|
||||
broadcast_event(GraphNode * source, EventNode * event)
|
||||
{
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
size_t count = source->outputs.length;
|
||||
if (!count) {
|
||||
event_destroy(event);
|
||||
return;
|
||||
}
|
||||
if (count > 1) {
|
||||
count = event_replicate(event, count - 1) + 1;
|
||||
}
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
event->position = &source->outputs.elements[i]->as_EventPositionBase;
|
||||
if (!event->position) {
|
||||
EventNode *orphaned = event;
|
||||
event = orphaned->prev;
|
||||
event_destroy(orphaned);
|
||||
}
|
||||
event = event->next;
|
||||
}
|
||||
}
|
||||
|
||||
static EventNode *
|
||||
replicate_and_advance(EventNode ** ptr)
|
||||
{
|
||||
EventNode * old = *ptr;
|
||||
if (!old) {
|
||||
return NULL;
|
||||
}
|
||||
if (event_replicate(old, 1)) {
|
||||
*ptr = old->next;
|
||||
return old;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
trigger_new_window(WindowGraphNode * node, EventNode * base)
|
||||
{
|
||||
if (!base) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->has_terminator) {
|
||||
EventNode * terminator = replicate_and_advance(&base);
|
||||
if (terminator) {
|
||||
terminator->data.code = node->terminator_prototype.code;
|
||||
terminator->data.modifiers = modifier_set_copy(node->terminator_prototype.modifiers);
|
||||
terminator->data.payload = node->terminator_prototype.payload;
|
||||
broadcast_event(&node->as_GraphNode, terminator);
|
||||
}
|
||||
}
|
||||
|
||||
size_t step = 1;
|
||||
if (node->is_jumping) {
|
||||
step = queue_length(&node->buffer);
|
||||
}
|
||||
step += node->additional_step;
|
||||
if (step < 1) {
|
||||
step = 1;
|
||||
}
|
||||
|
||||
while (step > 0) {
|
||||
QueueValue popped;
|
||||
if (!queue_try_pop(&node->buffer, &popped)) {
|
||||
break;
|
||||
}
|
||||
--step;
|
||||
EventNode * event = popped.as_ptr;
|
||||
event_set_del(&node->buffered_set, event);
|
||||
}
|
||||
node->skip_next += step;
|
||||
|
||||
QUEUE_FOREACH_INDEX(i, &node->buffer) {
|
||||
EventNode *orig = node->buffer.values[i].as_ptr;
|
||||
if (!orig) {
|
||||
continue;
|
||||
}
|
||||
EventNode *recipient = replicate_and_advance(&base);
|
||||
if (!recipient) {
|
||||
continue;
|
||||
}
|
||||
recipient->data = orig->data;
|
||||
recipient->data.modifiers = modifier_set_copy(orig->data.modifiers);
|
||||
broadcast_event(&node->as_GraphNode, recipient);
|
||||
}
|
||||
|
||||
event_destroy(base);
|
||||
}
|
||||
|
||||
static bool
|
||||
handle_event(EventPositionBase * self, EventNode * event)
|
||||
{
|
||||
WindowGraphNode *node = DOWNCAST(WindowGraphNode, GraphNode, DOWNCAST(GraphNode, EventPositionBase, self));
|
||||
|
||||
if (event_set_has(&node->buffered_set, event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EventNode *replacement;
|
||||
const AbsoluteTime new_time = event->data.time;
|
||||
if (node->has_max_time) {
|
||||
const RelativeTime threshold = node->max_time;
|
||||
while (queue_length(&node->buffer) > 0) {
|
||||
EventNode *first_event = queue_peek(&node->buffer).as_ptr;
|
||||
if (!first_event) {
|
||||
break;
|
||||
}
|
||||
RelativeTime delta = absolute_time_sub_absolute(new_time, first_event->data.time);
|
||||
if (relative_time_cmp(delta, threshold) <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Here replacement should occur simultaneously-before the event
|
||||
replacement = replicate_and_advance(&event);
|
||||
if (!replacement) {
|
||||
return false;
|
||||
}
|
||||
event->position = self;
|
||||
replacement->position = NULL;
|
||||
trigger_new_window(node, replacement);
|
||||
}
|
||||
}
|
||||
|
||||
if (node->skip_next > 0) {
|
||||
--(node->skip_next);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Replacement should occur after the forwarded replica
|
||||
replacement = NULL;
|
||||
if (event_replicate(event, 1)) {
|
||||
replacement = event->next;
|
||||
}
|
||||
|
||||
if (event_replicate(event, 1)) {
|
||||
broadcast_event(&node->as_GraphNode, event->next);
|
||||
}
|
||||
queue_put(&node->buffer, (QueueValue){.as_ptr = event});
|
||||
event_set_add(&node->buffered_set, event);
|
||||
|
||||
if (node->has_max_length) {
|
||||
const size_t threshold = node->max_length;
|
||||
while (replacement && queue_length(&node->buffer) >= threshold) {
|
||||
trigger_new_window(node, replicate_and_advance(&replacement));
|
||||
}
|
||||
}
|
||||
|
||||
if (replacement) {
|
||||
event_destroy(replacement);
|
||||
}
|
||||
self->waiting_new_event = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
load_event_prototype(InitializationEnvironment * env, const config_setting_t * setting, EventData * proto)
|
||||
{
|
||||
if (!setting) {
|
||||
return;
|
||||
}
|
||||
|
||||
proto->code.ns = env_resolve_constant(env, config_setting_get_member(setting, "namespace"));
|
||||
proto->code.major = env_resolve_constant(env, config_setting_get_member(setting, "major"));
|
||||
proto->code.minor = env_resolve_constant(env, config_setting_get_member(setting, "minor"));
|
||||
proto->payload = env_resolve_constant(env, config_setting_get_member(setting, "payload"));
|
||||
// TODO modifiers
|
||||
}
|
||||
|
||||
static GraphNode *
|
||||
create(GraphNodeSpecification * spec, GraphNodeConfig * config, InitializationEnvironment * env)
|
||||
{
|
||||
WindowGraphNode * node = T_ALLOC(1, WindowGraphNode);
|
||||
if (!node) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool is_jumping = false;
|
||||
ssize_t additional_step = 0;
|
||||
long long max_length = 0;
|
||||
long long max_milliseconds = 0;
|
||||
EventData terminator = {.code = {0, 0, 0}, .payload = 0, .priority = 10, .modifiers = EMPTY_MODIFIER_SET, .time = {}};
|
||||
bool
|
||||
has_max_length = false,
|
||||
has_max_time = false,
|
||||
has_terminator = false;
|
||||
|
||||
if (config->options) {
|
||||
is_jumping = env_resolve_constant(env, config_setting_get_member(config->options, "is_jumping")) != 0;
|
||||
|
||||
additional_step = (ssize_t) env_resolve_constant(env, config_setting_get_member(config->options, "additional_step"));
|
||||
if (additional_step < 0) {
|
||||
additional_step = 0;
|
||||
}
|
||||
|
||||
const config_setting_t *setting;
|
||||
|
||||
if ((setting = config_setting_get_member(config->options, "max_length"))) {
|
||||
has_max_length = true;
|
||||
max_length = env_resolve_constant(env, setting);
|
||||
if (max_length < 0) {
|
||||
max_length = 0;
|
||||
}
|
||||
if (max_length > INT32_MAX) {
|
||||
max_length = 0;
|
||||
has_max_length = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((setting = config_setting_get_member(config->options, "max_milliseconds"))) {
|
||||
has_max_time = true;
|
||||
max_milliseconds = env_resolve_constant(env, setting);
|
||||
if (max_milliseconds < 0) {
|
||||
max_milliseconds = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ((setting = config_setting_get_member(config->options, "terminator"))) {
|
||||
has_terminator = true;
|
||||
load_event_prototype(env, setting, &terminator);
|
||||
}
|
||||
}
|
||||
|
||||
*node = (WindowGraphNode) {
|
||||
.as_GraphNode = {
|
||||
.as_EventPositionBase = {
|
||||
.handle_event = &handle_event,
|
||||
.waiting_new_event = false,
|
||||
},
|
||||
.specification = spec,
|
||||
.inputs = EMPTY_GRAPH_CHANNEL_LIST,
|
||||
.outputs = EMPTY_GRAPH_CHANNEL_LIST,
|
||||
},
|
||||
.terminator_prototype = terminator,
|
||||
.has_terminator = has_terminator,
|
||||
.is_jumping = is_jumping,
|
||||
.has_max_time = has_max_time,
|
||||
.has_max_length = has_max_length,
|
||||
.max_time = relative_time_from_millisecond(max_milliseconds),
|
||||
.max_length = max_length,
|
||||
.additional_step = additional_step,
|
||||
.skip_next = 0,
|
||||
.buffer = EMPTY_QUEUE,
|
||||
.buffered_set = {},
|
||||
};
|
||||
hash_table_init(&node->buffered_set, NULL);
|
||||
return &node->as_GraphNode;
|
||||
}
|
||||
|
||||
static void destroy
|
||||
(GraphNodeSpecification * self, GraphNode * target)
|
||||
{
|
||||
(void) self;
|
||||
WindowGraphNode * node = DOWNCAST(WindowGraphNode, GraphNode, target);
|
||||
modifier_set_destruct(&node->terminator_prototype.modifiers);
|
||||
queue_deinit(&node->buffer);
|
||||
hash_table_deinit(&node->buffered_set);
|
||||
free(target);
|
||||
}
|
||||
|
||||
GraphNodeSpecification nodespec_window = (GraphNodeSpecification) {
|
||||
.create = &create,
|
||||
.destroy = &destroy,
|
||||
.register_io = NULL,
|
||||
.name = "window",
|
||||
.documentation = "Passes events through while copying them into an internal buffer, when buffer buffer.length or (buffer.last.time - buffer.first.time) thresholds are met optionally sends terminator event, rewinds events to buffer start, skips ((is_jumping ? buffer.length : 1) + additional_step) events, retransmits remaining buffered events and starts over\nAccepts events on any connector\nSends events on all connectors"
|
||||
"\nOption 'is_jumping' (optional): whether to send events at most once"
|
||||
"\nOption 'additional_step' (optional): natural number --- additional step relative to a regular sliding/jumping window"
|
||||
"\nOption 'max_length' (optional): natural number --- maximum number of events in a window"
|
||||
"\nOption 'max_milliseconds' (optional): natural number --- maximum number milliseconds between the first and the last event in a window"
|
||||
"\nOption 'terminator' (optional): event to send after the window fullness condition is met:"
|
||||
"\n\tField 'namespace' (optional): set generated event code namespace"
|
||||
"\n\tField 'major' (optional): set generated event code major"
|
||||
"\n\tField 'minor' (optional): set generated event code minor"
|
||||
"\n\tField 'payload' (optional): set generated event payload"
|
||||
,
|
||||
};
|
||||
|
||||
MODULE_CONSTRUCTOR(init)
|
||||
{
|
||||
register_graph_node_specification(&nodespec_window);
|
||||
}
|
|
@ -10,13 +10,13 @@ io_subscription_list_extend(IOSubscriptionList * lst)
|
|||
size_t capacity = lst->capacity;
|
||||
capacity = capacity + (capacity >> 1) + 1;
|
||||
|
||||
int * new_fds = reallocarray(lst->fds, capacity, sizeof(int));
|
||||
int * new_fds = T_REALLOC(lst->fds, capacity, int);
|
||||
if (!new_fds) {
|
||||
return false;
|
||||
}
|
||||
lst->fds = new_fds;
|
||||
|
||||
IOHandling ** new_subscribers = reallocarray(lst->subscribers, capacity, sizeof(IOHandling*));
|
||||
IOHandling ** new_subscribers = T_REALLOC(lst->subscribers, capacity, IOHandling*);
|
||||
if (!new_subscribers) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
constants = {
|
||||
sensitivity_num = 2;
|
||||
sensitivity_denom = 5;
|
||||
};
|
||||
|
||||
enums = {
|
||||
namespaces: ["clickpad"];
|
||||
namespaces: ["clickpad", "window_terminator"];
|
||||
};
|
||||
|
||||
predicates = {
|
||||
|
@ -30,6 +35,28 @@ predicates = {
|
|||
}
|
||||
);
|
||||
};
|
||||
abs_event = {
|
||||
type = "and";
|
||||
args = (
|
||||
"is_evdev",
|
||||
{
|
||||
type = "code_major";
|
||||
min = "event_type.ABS";
|
||||
max = "event_type.ABS";
|
||||
}
|
||||
);
|
||||
};
|
||||
misc_event = {
|
||||
type = "and";
|
||||
args = (
|
||||
"is_evdev",
|
||||
{
|
||||
type = "code_major";
|
||||
min = "event_type.MSC";
|
||||
max = "event_type.MSC";
|
||||
}
|
||||
);
|
||||
};
|
||||
btn_quadtap_event = {
|
||||
type = "and";
|
||||
args = (
|
||||
|
@ -41,6 +68,66 @@ predicates = {
|
|||
}
|
||||
);
|
||||
};
|
||||
misc_timestamp = {
|
||||
type = "and";
|
||||
args = (
|
||||
"misc_event",
|
||||
{
|
||||
type = "code_minor";
|
||||
min = "misc_event.TIMESTAMP";
|
||||
max = "misc_event.TIMESTAMP";
|
||||
}
|
||||
);
|
||||
};
|
||||
abs_x = {
|
||||
type = "and";
|
||||
args = (
|
||||
"abs_event",
|
||||
{
|
||||
type = "code_minor";
|
||||
min = "absolute_axis.X";
|
||||
max = "absolute_axis.X";
|
||||
}
|
||||
);
|
||||
};
|
||||
abs_y = {
|
||||
type = "and";
|
||||
args = (
|
||||
"abs_event",
|
||||
{
|
||||
type = "code_minor";
|
||||
min = "absolute_axis.Y";
|
||||
max = "absolute_axis.Y";
|
||||
}
|
||||
);
|
||||
};
|
||||
bypass = {
|
||||
type = "or";
|
||||
args = ["syn_event", "misc_timestamp"];
|
||||
};
|
||||
payload_zero = {
|
||||
type = "payload";
|
||||
min = 0;
|
||||
max = 0;
|
||||
};
|
||||
payload_one = {
|
||||
type = "payload";
|
||||
min = 1;
|
||||
max = 1;
|
||||
};
|
||||
is_window_terminator = {
|
||||
type = "code_ns";
|
||||
min = "namespaces.window_terminator";
|
||||
max = "namespaces.window_terminator";
|
||||
};
|
||||
inside_drag_window = {
|
||||
type = "accept";
|
||||
inverted = 1;
|
||||
};
|
||||
drag_held = {
|
||||
type = "accept";
|
||||
inverted = 1;
|
||||
};
|
||||
};
|
||||
|
||||
nodes = {
|
||||
|
@ -54,7 +141,7 @@ nodes = {
|
|||
select_interesting_events = {
|
||||
type = "router";
|
||||
options = {
|
||||
predicates = ["syn_event", "btn_quadtap_event"];
|
||||
predicates = ["bypass", "btn_quadtap_event", "abs_x", "abs_y"];
|
||||
};
|
||||
};
|
||||
quadtap_subst_seq = {
|
||||
|
@ -85,7 +172,75 @@ nodes = {
|
|||
{major: "event_type.KEY"; minor: "button_event.RIGHT"}
|
||||
,
|
||||
{major: "event_type.KEY"; minor: "button_event.MIDDLE"}
|
||||
,
|
||||
{major: "event_type.REL"; minor: "relative_axis.X"}
|
||||
,
|
||||
{major: "event_type.REL"; minor: "relative_axis.Y"}
|
||||
,
|
||||
{major: "event_type.MSC"; minor: "misc_event.TIMESTAMP"}
|
||||
);
|
||||
properties = ["input_property.POINTER", "input_property.BUTTONPAD"];
|
||||
};
|
||||
};
|
||||
differentiate_x = {
|
||||
type = "differentiate";
|
||||
};
|
||||
differentiate_y = {
|
||||
type = "differentiate";
|
||||
};
|
||||
morph_rel = {
|
||||
type = "assign";
|
||||
options = {
|
||||
major = "event_type.REL";
|
||||
// Preserve minor, because relative_axis.X = absolute_axis.X, relative_axis.Y = absolute_axis.Y
|
||||
};
|
||||
};
|
||||
while_drag_held = {
|
||||
type = "router";
|
||||
options = {
|
||||
predicates = ["drag_held"];
|
||||
};
|
||||
};
|
||||
sensitivity_x = {
|
||||
type = "scale";
|
||||
options = {
|
||||
numerator = "sensitivity_num";
|
||||
denominator = "sensitivity_denom";
|
||||
amortize_rounding_error = "true";
|
||||
};
|
||||
};
|
||||
sensitivity_y = {
|
||||
type = "scale";
|
||||
options = {
|
||||
numerator = "sensitivity_num";
|
||||
denominator = "sensitivity_denom";
|
||||
amortize_rounding_error = "true";
|
||||
};
|
||||
};
|
||||
drag_start_window = {
|
||||
type = "window";
|
||||
options = {
|
||||
max_length = 2;
|
||||
max_milliseconds = 500;
|
||||
terminator = {
|
||||
namespace = "namespaces.window_terminator";
|
||||
};
|
||||
};
|
||||
};
|
||||
update_inside_drag_window = {
|
||||
type = "modify_predicate";
|
||||
options = {
|
||||
target = "inside_drag_window";
|
||||
uninvert_on = {type: "and"; args: ["is_evdev", "payload_zero"]};
|
||||
invert_on = "is_window_terminator";
|
||||
};
|
||||
};
|
||||
update_drag_held = {
|
||||
type = "modify_predicate";
|
||||
options = {
|
||||
target = "drag_held";
|
||||
uninvert_on = {type: "and"; args: ["inside_drag_window", "is_evdev", "payload_one"]};
|
||||
invert_on = {type: "and"; args: ["is_evdev", "payload_zero"]};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -111,9 +266,42 @@ channels = ({
|
|||
}, {
|
||||
from: ("morph_right_click", 0);
|
||||
to: ("merge", 2);
|
||||
}, {
|
||||
from: ("select_interesting_events", 2); // ABS_X
|
||||
to: ("differentiate_x", 0);
|
||||
}, {
|
||||
from: ("select_interesting_events", 3); // ABS_Y
|
||||
to: ("differentiate_y", 0);
|
||||
}, {
|
||||
from: ("differentiate_x", 0);
|
||||
to: ("sensitivity_x", 0);
|
||||
}, {
|
||||
from: ("differentiate_y", 0);
|
||||
to: ("sensitivity_y", 0);
|
||||
}, {
|
||||
from: ("sensitivity_x", 0);
|
||||
to: ("while_drag_held", 0);
|
||||
}, {
|
||||
from: ("sensitivity_y", 0);
|
||||
to: ("while_drag_held", 1);
|
||||
}, {
|
||||
from: ("while_drag_held", 0);
|
||||
to: ("morph_rel", 0);
|
||||
}, {
|
||||
from: ("morph_rel", 0);
|
||||
to: ("merge", 3);
|
||||
}, {
|
||||
from: ("merge", 0);
|
||||
to: ("virtual_out", 0);
|
||||
}, {
|
||||
from: ("quadtap_subst_seq", 4);
|
||||
to: ("drag_start_window", 0);
|
||||
}, {
|
||||
from: ("drag_start_window", 0);
|
||||
to: ("update_inside_drag_window", 0);
|
||||
}, {
|
||||
from: ("drag_start_window", 1);
|
||||
to: ("update_drag_held", 0);
|
||||
});
|
||||
|
||||
// vim: ft=libconfig
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "queue.h"
|
||||
|
||||
void
|
||||
queue_deinit_with_destructor(Queue * q, void (*value_destructor)(QueueValue value, void * destructor_closure), void * destructor_closure)
|
||||
{
|
||||
if (q->values) {
|
||||
if (value_destructor) {
|
||||
QUEUE_FOREACH_INDEX(i, q) {
|
||||
value_destructor(q->values[i], destructor_closure);
|
||||
}
|
||||
}
|
||||
free(q->values);
|
||||
q->values = NULL;
|
||||
}
|
||||
q->capacity = 0;
|
||||
q->after_tail_idx = 0;
|
||||
q->head_idx = 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
queue_grow(Queue * queue)
|
||||
{
|
||||
size_t old_capacity = queue->capacity;
|
||||
size_t new_capacity = old_capacity + (old_capacity >> 1) + 2;
|
||||
QueueValue *values = queue->values;
|
||||
if (values) {
|
||||
values = T_REALLOC(values, new_capacity, QueueValue);
|
||||
if (!values) {
|
||||
return false;
|
||||
}
|
||||
if (queue->head_idx > queue->after_tail_idx) {
|
||||
size_t old_head_idx = queue->head_idx;
|
||||
size_t head_shift = new_capacity - old_capacity;
|
||||
size_t shifted_count = old_capacity - old_head_idx;
|
||||
QueueValue *old_head_ptr = &values[old_head_idx];
|
||||
memmove(old_head_ptr + head_shift, old_head_ptr, shifted_count * sizeof(QueueValue));
|
||||
queue->head_idx = old_head_idx + head_shift;
|
||||
}
|
||||
} else {
|
||||
values = T_ALLOC(new_capacity, QueueValue);
|
||||
if (!values) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
queue->values = values;
|
||||
queue->capacity = new_capacity;
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
queue_put(Queue * queue, QueueValue value)
|
||||
{
|
||||
if (queue_is_full(queue)) {
|
||||
if (!queue_grow(queue)) {
|
||||
return -1;
|
||||
}
|
||||
assert(!queue_is_full(queue));
|
||||
}
|
||||
size_t capacity = queue->capacity;
|
||||
assert(capacity > 0);
|
||||
size_t index = queue->after_tail_idx;
|
||||
queue->values[index] = value;
|
||||
queue->after_tail_idx = (index + 1) % capacity;
|
||||
return index;
|
||||
}
|
||||
|
||||
bool
|
||||
queue_try_pop(Queue * queue, QueueValue * ret)
|
||||
{
|
||||
if (!queue_try_peek(queue, ret)) {
|
||||
return false;
|
||||
}
|
||||
queue->head_idx = (queue->head_idx + 1) % queue->capacity;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
queue_try_peek(const Queue * queue, QueueValue * ret)
|
||||
{
|
||||
if (queue_is_empty(queue)) {
|
||||
return false;
|
||||
}
|
||||
assert(queue->capacity > 0);
|
||||
if (ret) {
|
||||
*ret = queue->values[queue->head_idx];
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
#ifndef QUEUE_H_
|
||||
#define QUEUE_H_
|
||||
|
||||
#include "defs.h"
|
||||
|
||||
typedef union {
|
||||
void *as_ptr;
|
||||
intptr_t as_intptr_t;
|
||||
intptr_t as_signed;
|
||||
uintptr_t as_uintptr_t;
|
||||
uintptr_t as_unsigned;
|
||||
uint8_t as_bytes[sizeof(void*)];
|
||||
} QueueValue;
|
||||
|
||||
#define ZERO_QUEUE_VALUE ((QueueValue) {.as_ptr = NULL})
|
||||
|
||||
typedef struct {
|
||||
// Always keep one unused slot to distinguish empty and full queue
|
||||
size_t capacity;
|
||||
size_t after_tail_idx;
|
||||
size_t head_idx;
|
||||
QueueValue *values;
|
||||
} Queue;
|
||||
|
||||
#define EMPTY_QUEUE ((Queue) {.capacity = 0, .after_tail_idx = 0, .head_idx = 0, .values = NULL})
|
||||
|
||||
void queue_deinit_with_destructor(Queue * queue, void (*value_destructor)(QueueValue value, void * destructor_closure), void * destructor_closure);
|
||||
ssize_t queue_put(Queue * queue, QueueValue value);
|
||||
bool queue_try_pop(Queue * queue, QueueValue * ret);
|
||||
bool queue_try_peek(const Queue * queue, QueueValue * ret);
|
||||
|
||||
__attribute__((unused)) inline static size_t
|
||||
queue_length(const Queue * queue)
|
||||
{
|
||||
ssize_t n = queue->after_tail_idx - queue->head_idx;
|
||||
if (n < 0) {
|
||||
n += queue->capacity;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
__attribute__((unused)) inline static bool
|
||||
queue_is_empty(const Queue * queue)
|
||||
{
|
||||
return queue->head_idx == queue->after_tail_idx;
|
||||
}
|
||||
|
||||
__attribute__((unused)) inline static bool
|
||||
queue_is_full(const Queue * queue)
|
||||
{
|
||||
size_t capacity = queue->capacity;
|
||||
if (!capacity) {
|
||||
return true;
|
||||
}
|
||||
return (queue->after_tail_idx + 1) % capacity == queue->head_idx;
|
||||
}
|
||||
|
||||
__attribute__((unused)) inline static void
|
||||
queue_deinit(Queue * queue)
|
||||
{
|
||||
queue_deinit_with_destructor(queue, NULL, NULL);
|
||||
}
|
||||
|
||||
__attribute__((unused)) inline static QueueValue
|
||||
queue_pop(Queue * queue)
|
||||
{
|
||||
QueueValue value = ZERO_QUEUE_VALUE;
|
||||
queue_try_pop(queue, &value);
|
||||
return value;
|
||||
}
|
||||
|
||||
__attribute__((unused)) inline static QueueValue
|
||||
queue_peek(const Queue * queue)
|
||||
{
|
||||
QueueValue value = ZERO_QUEUE_VALUE;
|
||||
queue_try_peek(queue, &value);
|
||||
return value;
|
||||
}
|
||||
|
||||
// As long as head and tail indices are correct, division by zero should not happen
|
||||
#define QUEUE_FOREACH_INDEX(i, q) for (size_t i = (q)->head_idx; i != (q)->after_tail_idx; i = (i + 1) % (q)->capacity)
|
||||
|
||||
#endif /* end of include guard: QUEUE_H_ */
|
40
time.h
40
time.h
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <time.h>
|
||||
#define NANOSECONDS_IN_SECOND 1000000000
|
||||
#define MILLISECONDS_IN_SECOND 1000
|
||||
|
||||
typedef struct {
|
||||
struct timespec absolute;
|
||||
|
@ -12,6 +13,29 @@ typedef struct {
|
|||
struct timespec relative;
|
||||
} RelativeTime;
|
||||
|
||||
__attribute__((unused)) inline static void
|
||||
timespec_assign_nanosecond(struct timespec *target, long long ns)
|
||||
{
|
||||
time_t sec = ns / NANOSECONDS_IN_SECOND;
|
||||
if (ns < 0) {
|
||||
sec -= 1;
|
||||
}
|
||||
target->tv_sec = sec;
|
||||
target->tv_nsec = ns - sec * NANOSECONDS_IN_SECOND;
|
||||
}
|
||||
|
||||
__attribute__((unused)) inline static void
|
||||
timespec_assign_millisecond(struct timespec *target, long long ms)
|
||||
{
|
||||
time_t sec = ms / MILLISECONDS_IN_SECOND;
|
||||
if (ms < 0) {
|
||||
sec -= 1;
|
||||
}
|
||||
ms -= sec * MILLISECONDS_IN_SECOND;
|
||||
target->tv_sec = sec;
|
||||
target->tv_nsec = ms * (NANOSECONDS_IN_SECOND / MILLISECONDS_IN_SECOND);
|
||||
}
|
||||
|
||||
__attribute__((unused)) inline static void
|
||||
timespec_add(struct timespec *accum, const struct timespec *item)
|
||||
{
|
||||
|
@ -87,6 +111,22 @@ absolute_time_sub_absolute(AbsoluteTime lhs, AbsoluteTime rhs)
|
|||
return result;
|
||||
}
|
||||
|
||||
__attribute__((unused)) inline static RelativeTime
|
||||
relative_time_from_nanosecond(long long ns)
|
||||
{
|
||||
RelativeTime result;
|
||||
timespec_assign_nanosecond(&result.relative, ns);
|
||||
return result;
|
||||
}
|
||||
|
||||
__attribute__((unused)) inline static RelativeTime
|
||||
relative_time_from_millisecond(long long ms)
|
||||
{
|
||||
RelativeTime result;
|
||||
timespec_assign_millisecond(&result.relative, ms);
|
||||
return result;
|
||||
}
|
||||
|
||||
__attribute__((unused)) inline static RelativeTime
|
||||
relative_time_add(RelativeTime lhs, RelativeTime rhs)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue