|
| 1 | +/* |
| 2 | + * This Source Code Form is subject to the terms of the Mozilla Public |
| 3 | + * License, v. 2.0. If a copy of the MPL was not distributed with this |
| 4 | + * file, You can obtain one at https://mozilla.org/MPL/2.0/. |
| 5 | + * |
| 6 | + * Copyright (c) 2025 Jonathan "Geenz" Goodman |
| 7 | + * This file is part of the Entropy Core project. |
| 8 | + */ |
| 9 | + |
| 10 | +/** |
| 11 | + * @file ConcurrencyAPIExample.c |
| 12 | + * @brief Example demonstrating the C API for EntropyCore concurrency primitives |
| 13 | + * |
| 14 | + * This example shows how to use the C API to create work groups, submit tasks, |
| 15 | + * and execute them using a work service (thread pool), along with the C logging API. |
| 16 | + */ |
| 17 | + |
| 18 | +#include <entropy/entropy_concurrency_types.h> |
| 19 | +#include <entropy/entropy_work_contract_group.h> |
| 20 | +#include <entropy/entropy_work_contract_handle.h> |
| 21 | +#include <entropy/entropy_work_service.h> |
| 22 | +#include <Logging/CLogger.h> |
| 23 | +#include <stdio.h> |
| 24 | +#include <stdlib.h> |
| 25 | +#include <unistd.h> |
| 26 | + |
| 27 | +// Example work context |
| 28 | +typedef struct { |
| 29 | + int task_id; |
| 30 | + const char* message; |
| 31 | +} TaskContext; |
| 32 | + |
| 33 | +// Re-entrant work context (task spawns children) |
| 34 | +typedef struct { |
| 35 | + entropy_WorkContractGroup group; // Need group reference to schedule from within |
| 36 | + int depth; // Recursion depth |
| 37 | + int max_depth; // Maximum depth |
| 38 | + int task_id; // Task identifier |
| 39 | +} ReentrantContext; |
| 40 | + |
| 41 | +// Example work callback |
| 42 | +void example_work(void* user_data) { |
| 43 | + TaskContext* ctx = (TaskContext*)user_data; |
| 44 | + ENTROPY_LOG_INFO_CAT_F("WorkerThread", |
| 45 | + "Task %d: %s (executing on worker thread)", |
| 46 | + ctx->task_id, ctx->message); |
| 47 | + |
| 48 | + // Simulate some work |
| 49 | + usleep(10000); // 10ms |
| 50 | + |
| 51 | + ENTROPY_LOG_DEBUG_CAT_F("WorkerThread", |
| 52 | + "Task %d completed", ctx->task_id); |
| 53 | +} |
| 54 | + |
| 55 | +// Main thread work callback |
| 56 | +void main_thread_work(void* user_data) { |
| 57 | + TaskContext* ctx = (TaskContext*)user_data; |
| 58 | + ENTROPY_LOG_INFO_CAT_F("MainThread", |
| 59 | + "Task %d: %s (executing on MAIN THREAD)", |
| 60 | + ctx->task_id, ctx->message); |
| 61 | +} |
| 62 | + |
| 63 | +// Re-entrant work callback (spawns child tasks) |
| 64 | +void reentrant_work(void* user_data) { |
| 65 | + ReentrantContext* ctx = (ReentrantContext*)user_data; |
| 66 | + |
| 67 | + ENTROPY_LOG_INFO_CAT_F("Reentrant", |
| 68 | + "Task %d at depth %d (max: %d)", |
| 69 | + ctx->task_id, ctx->depth, ctx->max_depth); |
| 70 | + |
| 71 | + // If we haven't reached max depth, spawn two child tasks |
| 72 | + if (ctx->depth < ctx->max_depth) { |
| 73 | + EntropyStatus status = ENTROPY_OK; |
| 74 | + |
| 75 | + // Allocate contexts for child tasks (will be freed by children or at end) |
| 76 | + ReentrantContext* left_child = malloc(sizeof(ReentrantContext)); |
| 77 | + ReentrantContext* right_child = malloc(sizeof(ReentrantContext)); |
| 78 | + |
| 79 | + if (left_child && right_child) { |
| 80 | + // Setup left child |
| 81 | + left_child->group = ctx->group; |
| 82 | + left_child->depth = ctx->depth + 1; |
| 83 | + left_child->max_depth = ctx->max_depth; |
| 84 | + left_child->task_id = ctx->task_id * 2; |
| 85 | + |
| 86 | + // Setup right child |
| 87 | + right_child->group = ctx->group; |
| 88 | + right_child->depth = ctx->depth + 1; |
| 89 | + right_child->max_depth = ctx->max_depth; |
| 90 | + right_child->task_id = ctx->task_id * 2 + 1; |
| 91 | + |
| 92 | + ENTROPY_LOG_DEBUG_CAT_F("Reentrant", |
| 93 | + "Task %d spawning children %d and %d", |
| 94 | + ctx->task_id, left_child->task_id, right_child->task_id); |
| 95 | + |
| 96 | + // RE-ENTRANT SCHEDULING: Create and schedule child tasks from within this task |
| 97 | + entropy_WorkContractHandle left = entropy_work_contract_group_create_contract( |
| 98 | + ctx->group, reentrant_work, left_child, ENTROPY_EXEC_ANY_THREAD, &status |
| 99 | + ); |
| 100 | + |
| 101 | + if (status == ENTROPY_OK) { |
| 102 | + entropy_work_contract_schedule(left, &status); |
| 103 | + } |
| 104 | + |
| 105 | + entropy_WorkContractHandle right = entropy_work_contract_group_create_contract( |
| 106 | + ctx->group, reentrant_work, right_child, ENTROPY_EXEC_ANY_THREAD, &status |
| 107 | + ); |
| 108 | + |
| 109 | + if (status == ENTROPY_OK) { |
| 110 | + entropy_work_contract_schedule(right, &status); |
| 111 | + } |
| 112 | + } |
| 113 | + } else { |
| 114 | + ENTROPY_LOG_DEBUG_CAT_F("Reentrant", |
| 115 | + "Task %d reached max depth, completing as leaf", |
| 116 | + ctx->task_id); |
| 117 | + } |
| 118 | + |
| 119 | + // Free our own context (we're done with it) |
| 120 | + free(ctx); |
| 121 | +} |
| 122 | + |
| 123 | +int main(void) { |
| 124 | + EntropyStatus status = ENTROPY_OK; |
| 125 | + |
| 126 | + ENTROPY_LOG_INFO_CAT_F("Example", "=== EntropyCore C API Example ==="); |
| 127 | + ENTROPY_LOG_INFO_CAT_F("Example", "Demonstrating C API for concurrency + logging"); |
| 128 | + |
| 129 | + // 1. Create a work contract group |
| 130 | + ENTROPY_LOG_INFO_CAT_F("Example", "Step 1: Creating work contract group (capacity: 1024)..."); |
| 131 | + entropy_WorkContractGroup group = entropy_work_contract_group_create( |
| 132 | + 1024, "ExampleGroup", &status |
| 133 | + ); |
| 134 | + if (status != ENTROPY_OK) { |
| 135 | + ENTROPY_LOG_ERROR_CAT_F("Example", |
| 136 | + "Failed to create group: %s", entropy_status_to_string(status)); |
| 137 | + return 1; |
| 138 | + } |
| 139 | + ENTROPY_LOG_INFO_CAT_F("Example", "Group created successfully"); |
| 140 | + |
| 141 | + // 2. Create a work service (thread pool) |
| 142 | + ENTROPY_LOG_INFO_CAT_F("Example", "Step 2: Creating work service (4 threads)..."); |
| 143 | + EntropyWorkServiceConfig config; |
| 144 | + entropy_work_service_config_init(&config); |
| 145 | + config.thread_count = 4; |
| 146 | + |
| 147 | + entropy_WorkService service = entropy_work_service_create(&config, &status); |
| 148 | + if (status != ENTROPY_OK) { |
| 149 | + ENTROPY_LOG_ERROR_CAT_F("Example", |
| 150 | + "Failed to create service: %s", entropy_status_to_string(status)); |
| 151 | + entropy_work_contract_group_destroy(group); |
| 152 | + return 1; |
| 153 | + } |
| 154 | + ENTROPY_LOG_INFO_CAT_F("Example", "Service created with %zu threads", |
| 155 | + entropy_work_service_get_thread_count(service)); |
| 156 | + |
| 157 | + // 3. Register the group with the service |
| 158 | + ENTROPY_LOG_INFO_CAT_F("Example", "Step 3: Registering group with service..."); |
| 159 | + entropy_work_service_add_group(service, group, &status); |
| 160 | + if (status != ENTROPY_OK) { |
| 161 | + ENTROPY_LOG_ERROR_CAT_F("Example", |
| 162 | + "Failed to add group: %s", entropy_status_to_string(status)); |
| 163 | + entropy_work_service_destroy(service); |
| 164 | + entropy_work_contract_group_destroy(group); |
| 165 | + return 1; |
| 166 | + } |
| 167 | + ENTROPY_LOG_INFO_CAT_F("Example", "Group registered"); |
| 168 | + |
| 169 | + // 4. Start the service |
| 170 | + ENTROPY_LOG_INFO_CAT_F("Example", "Step 4: Starting work service..."); |
| 171 | + entropy_work_service_start(service, &status); |
| 172 | + if (status != ENTROPY_OK) { |
| 173 | + ENTROPY_LOG_ERROR_CAT_F("Example", |
| 174 | + "Failed to start service: %s", entropy_status_to_string(status)); |
| 175 | + entropy_work_service_destroy(service); |
| 176 | + entropy_work_contract_group_destroy(group); |
| 177 | + return 1; |
| 178 | + } |
| 179 | + ENTROPY_LOG_INFO_CAT_F("Example", "Service started"); |
| 180 | + |
| 181 | + // 5. Create and schedule background work |
| 182 | + ENTROPY_LOG_INFO_CAT_F("Example", "Step 5: Creating and scheduling 10 background tasks..."); |
| 183 | + TaskContext* contexts = malloc(sizeof(TaskContext) * 10); |
| 184 | + entropy_WorkContractHandle* handles = malloc(sizeof(entropy_WorkContractHandle) * 10); |
| 185 | + |
| 186 | + for (int i = 0; i < 10; i++) { |
| 187 | + contexts[i].task_id = i; |
| 188 | + contexts[i].message = "Processing background data"; |
| 189 | + |
| 190 | + handles[i] = entropy_work_contract_group_create_contract( |
| 191 | + group, example_work, &contexts[i], ENTROPY_EXEC_ANY_THREAD, &status |
| 192 | + ); |
| 193 | + |
| 194 | + if (status != ENTROPY_OK) { |
| 195 | + ENTROPY_LOG_WARNING_CAT_F("Example", |
| 196 | + "Failed to create contract %d: %s", i, entropy_status_to_string(status)); |
| 197 | + continue; |
| 198 | + } |
| 199 | + |
| 200 | + // Schedule the contract |
| 201 | + EntropyScheduleResult result = entropy_work_contract_schedule(handles[i], &status); |
| 202 | + if (result == ENTROPY_SCHEDULE_SCHEDULED) { |
| 203 | + ENTROPY_LOG_DEBUG_CAT_F("Example", "Task %d scheduled", i); |
| 204 | + } |
| 205 | + } |
| 206 | + |
| 207 | + // 6. Create main thread work |
| 208 | + ENTROPY_LOG_INFO_CAT_F("Example", "Step 6: Creating main thread tasks..."); |
| 209 | + TaskContext main_contexts[3]; |
| 210 | + entropy_WorkContractHandle main_handles[3]; |
| 211 | + |
| 212 | + for (int i = 0; i < 3; i++) { |
| 213 | + main_contexts[i].task_id = 100 + i; |
| 214 | + main_contexts[i].message = "Updating UI"; |
| 215 | + |
| 216 | + main_handles[i] = entropy_work_contract_group_create_contract( |
| 217 | + group, main_thread_work, &main_contexts[i], |
| 218 | + ENTROPY_EXEC_MAIN_THREAD, &status |
| 219 | + ); |
| 220 | + |
| 221 | + if (status == ENTROPY_OK) { |
| 222 | + entropy_work_contract_schedule(main_handles[i], &status); |
| 223 | + ENTROPY_LOG_DEBUG_CAT_F("Example", "Main thread task %d scheduled", 100 + i); |
| 224 | + } |
| 225 | + } |
| 226 | + |
| 227 | + // 7. Execute main thread work |
| 228 | + ENTROPY_LOG_INFO_CAT_F("Example", "Step 7: Executing main thread work..."); |
| 229 | + if (entropy_work_service_has_main_thread_work(service)) { |
| 230 | + EntropyMainThreadWorkResult result; |
| 231 | + entropy_work_service_execute_main_thread_work(service, 0, &result, &status); |
| 232 | + ENTROPY_LOG_INFO_CAT_F("Example", |
| 233 | + "Executed %zu contracts from %zu groups", |
| 234 | + result.contracts_executed, result.groups_with_work); |
| 235 | + } |
| 236 | + |
| 237 | + // Wait for initial work to complete before starting re-entrant example |
| 238 | + entropy_work_contract_group_wait(group, &status); |
| 239 | + ENTROPY_LOG_INFO_CAT_F("Example", "Initial work completed"); |
| 240 | + |
| 241 | + // 8. Re-entrant work contracts (tasks spawning tasks) |
| 242 | + ENTROPY_LOG_INFO_CAT_F("Example", "Step 8: Re-entrant work contracts (recursive task spawning)..."); |
| 243 | + ENTROPY_LOG_INFO_CAT_F("Example", "Creating binary tree of tasks (depth 3 = 15 total tasks)"); |
| 244 | + |
| 245 | + // Create root task that will spawn children recursively |
| 246 | + ReentrantContext* root = malloc(sizeof(ReentrantContext)); |
| 247 | + if (root) { |
| 248 | + root->group = group; |
| 249 | + root->depth = 0; |
| 250 | + root->max_depth = 3; // Creates 2^3 - 1 = 7 tasks at depth 3, 15 total |
| 251 | + root->task_id = 1; // Root is task 1 |
| 252 | + |
| 253 | + entropy_WorkContractHandle root_handle = entropy_work_contract_group_create_contract( |
| 254 | + group, reentrant_work, root, ENTROPY_EXEC_ANY_THREAD, &status |
| 255 | + ); |
| 256 | + |
| 257 | + if (status == ENTROPY_OK) { |
| 258 | + entropy_work_contract_schedule(root_handle, &status); |
| 259 | + ENTROPY_LOG_INFO_CAT_F("Example", "Root task scheduled - it will spawn children recursively"); |
| 260 | + } |
| 261 | + } |
| 262 | + |
| 263 | + // Wait for the recursive tree to complete |
| 264 | + ENTROPY_LOG_INFO_CAT_F("Example", "Waiting for re-entrant task tree to complete..."); |
| 265 | + entropy_work_contract_group_wait(group, &status); |
| 266 | + ENTROPY_LOG_INFO_CAT_F("Example", "Re-entrant task tree completed"); |
| 267 | + |
| 268 | + // 9. Print statistics |
| 269 | + ENTROPY_LOG_INFO_CAT_F("Example", "Step 9: Final statistics:"); |
| 270 | + ENTROPY_LOG_INFO_CAT_F("Example", " Capacity: %zu", |
| 271 | + entropy_work_contract_group_capacity(group)); |
| 272 | + ENTROPY_LOG_INFO_CAT_F("Example", " Active contracts: %zu", |
| 273 | + entropy_work_contract_group_active_count(group)); |
| 274 | + ENTROPY_LOG_INFO_CAT_F("Example", " Scheduled contracts: %zu", |
| 275 | + entropy_work_contract_group_scheduled_count(group)); |
| 276 | + ENTROPY_LOG_INFO_CAT_F("Example", " Executing contracts: %zu", |
| 277 | + entropy_work_contract_group_executing_count(group)); |
| 278 | + |
| 279 | + // 10. Cleanup |
| 280 | + ENTROPY_LOG_INFO_CAT_F("Example", "Step 10: Cleaning up..."); |
| 281 | + entropy_work_service_stop(service, &status); |
| 282 | + ENTROPY_LOG_DEBUG_CAT_F("Example", "Service stopped"); |
| 283 | + |
| 284 | + entropy_work_service_destroy(service); |
| 285 | + ENTROPY_LOG_DEBUG_CAT_F("Example", "Service destroyed"); |
| 286 | + |
| 287 | + entropy_work_contract_group_destroy(group); |
| 288 | + ENTROPY_LOG_DEBUG_CAT_F("Example", "Group destroyed"); |
| 289 | + |
| 290 | + free(contexts); |
| 291 | + free(handles); |
| 292 | + ENTROPY_LOG_DEBUG_CAT_F("Example", "Memory freed"); |
| 293 | + |
| 294 | + ENTROPY_LOG_INFO_CAT_F("Example", "=== Example completed successfully ==="); |
| 295 | + return 0; |
| 296 | +} |
0 commit comments