diff --git a/CHANGELOG.md b/CHANGELOG.md index f2181a0..3c3132c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.0-pre.3] - 2026-03-07 + +### Added +- `STK_MOD_DEP_LOG_BUFFER` (2048) added to `stk.h` + +### Changed +- Dependency failure logging now emits a single line per module listing all unmet deps with their reason: `not found` or `requires , have ` + - `Deferring 'test_mod_dep': unmet deps: test_mod (not found)` + - `Unloading 'test_mod_dep': unmet deps: test_mod (not found), renderer (requires ^2.0.0, have 1.3.0)` +- Silent defer at `stk_init` and vague `"unmet dependencies"` cascade log in `stk_poll` replaced with `stk_log_dependency_failures()` +- Kahn topological sort refactored into generic `stk_kahn_sort()` accepting a `has_dep` callback and an `on_cycle` callback, decoupling it from `stk_modules`. `stk_topo_sort()` is now a thin wrapper. `stk_sort_load_order()` uses the same core via `stk_batch_has_dep()`, inspecting tmp files so simultaneous load events are processed dependency-first without a retry cycle +- `stk_module_load()` split into `stk_module_preload()`, `stk_module_activate()`, `stk_module_discard()`, and `stk_validate_dependencies_single()`. Init is not called if deps are unmet +- On UNLOAD events in `stk_poll()`, the unload set is expanded to include all transitively dependent modules via `stk_collect_dependents()`, sorted dependents-first via `stk_sort_unload_order()`. Modules unloaded due to expansion are queued to pending +- On LOAD events with unmet dependencies, the tmp path is added to the pending queue instead of being dropped +- `stk_pending_retry()` now skips already-loaded entries and prunes entries whose file no longer exists +- `free_poll` label moved above `stk_pending_retry()` so retry always runs regardless of exit path + ## [1.0.0-pre.2] - 2026-03-06 ### Fixed @@ -145,7 +162,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Dependency management and versioning not yet implemented - API is unstable and subject to change in future releases -[Unreleased]: https://github.com/anth64/stk/compare/v1.0.0-pre.2...HEAD +[Unreleased]: https://github.com/anth64/stk/compare/v1.0.0-pre.3...HEAD +[1.0.0-pre.3]: https://github.com/anth64/stk/compare/v1.0.0-pre.2...v1.0.0-pre.3 [1.0.0-pre.2]: https://github.com/anth64/stk/compare/v1.0.0-pre.1...v1.0.0-pre.2 [1.0.0-pre.1]: https://github.com/anth64/stk/compare/v0.1.3...v1.0.0-pre.1 [0.1.3]: https://github.com/anth64/stk/releases/tag/v0.1.3 diff --git a/README.md b/README.md index 14113da..2f48660 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,7 @@ stk_init(); ## Project Status -**Current Version:** 1.0.0-pre.1 +**Current Version:** 1.0.0-pre.3 ### What Works - Cross-platform module loading and hot-reloading @@ -244,6 +244,7 @@ stk_init(); - Runtime-configurable logging behavior - Optional module metadata (name, version, description) - Dependency declaration, validation, and versioning +- Detailed dependency failure logging (missing ids, version mismatches) - Cascade unload when dependencies are removed - Pending queue with automatic retry when deps become available - Topological sort with cycle detection diff --git a/src/module.c b/src/module.c index e106ac4..5637f24 100644 --- a/src/module.c +++ b/src/module.c @@ -206,18 +206,8 @@ unsigned char stk_validate_dependencies(size_t count) return result; } -/* - * has_dep(i, j, ctx): return 1 if node i depends on node j, 0 otherwise. - * Used by stk_kahn_sort to abstract over loaded modules vs incoming batch. - */ typedef int (*stk_dep_query_fn)(size_t i, size_t j, void *ctx); -/* - * Core Kahn's topological sort. Produces dependencies-first ordering. - * on_cycle is called for each node not reachable (i.e. in a cycle). - * Returns STK_MOD_INIT_SUCCESS or STK_MOD_DEP_CIRCULAR_ERROR / - * STK_MOD_REALLOC_FAILURE. - */ static unsigned char stk_kahn_sort(size_t count, size_t *order, stk_dep_query_fn has_dep, void *ctx, void (*on_cycle)(size_t i)) @@ -722,11 +712,6 @@ static int stk_batch_has_dep(size_t i, size_t j, void *ctx) return result; } -/* - * Sort file_indices[] (length n) so that within the batch, modules whose ids - * appear as dependencies of others come first. Uses already-copied tmp files - * to read dep symbols. Falls back to original order if sort fails. - */ void stk_sort_load_order(int *file_indices, size_t n, char (*file_names)[STK_PATH_MAX], const char *tmp_dir) { @@ -761,12 +746,6 @@ cleanup: free(result); } -/* - * Expand indices[0..]*in_count to include all loaded modules that transitively - * depend on any module already in the set. Writes the expanded set back into - * indices[] and updates *out_count. indices[] must have capacity module_count. - * Returns 1 if any new dependents were added, 0 otherwise. - */ void stk_collect_dependents(size_t *indices, size_t *count) { size_t i, d; @@ -775,7 +754,6 @@ void stk_collect_dependents(size_t *indices, size_t *count) do { changed = 0; for (i = 0; i < module_count; i++) { - /* skip if already in the set */ in_set = 0; { size_t k; @@ -789,17 +767,16 @@ void stk_collect_dependents(size_t *indices, size_t *count) if (in_set) continue; - /* check if any of its deps are in the set */ for (d = 0; d < stk_modules[i].dep_count; d++) { - int dep_idx = + int dep_index = is_mod_loaded(stk_modules[i].deps[d].id); - if (dep_idx < 0) + if (dep_index < 0) continue; { size_t k; for (k = 0; k < *count; k++) { if (indices[k] == - (size_t)dep_idx) { + (size_t)dep_index) { indices[(*count)++] = i; changed = 1; goto next_module; @@ -812,11 +789,6 @@ void stk_collect_dependents(size_t *indices, size_t *count) } while (changed); } -/* - * Sort indices[] (length n) so that dependents come before their dependencies. - * This is the reverse of topological order. indices[] is sorted in-place. - * Falls back to reverse-index order if topo sort fails or alloc fails. - */ void stk_sort_unload_order(size_t *indices, size_t n) { size_t *topo = NULL; @@ -836,10 +808,6 @@ void stk_sort_unload_order(size_t *indices, size_t n) if (stk_topo_sort(module_count, topo) != STK_MOD_INIT_SUCCESS) goto fallback; - /* - * topo[] is dependencies-first. Walk it in reverse to get - * dependents-first, picking only indices that are in our set. - */ k = 0; for (i = module_count; i > 0; --i) { size_t mod = topo[i - 1]; @@ -866,7 +834,6 @@ void stk_sort_unload_order(size_t *indices, size_t n) fallback: free(topo); free(result); - /* reverse index order: higher indices (dependents) first */ for (i = 0; i < n / 2; i++) { size_t tmp = indices[i]; indices[i] = indices[n - 1 - i]; @@ -891,7 +858,6 @@ void stk_module_unload_all(void) stk_module_unload(order[i]); free(order); } else { - /* fallback: reverse index order */ for (i = module_count; i > 0; --i) stk_module_unload(i - 1); } @@ -943,7 +909,6 @@ void stk_pending_add_batch(const char (*paths)[STK_PATH_MAX_OS], size_t count) if (!paths || count == 0) return; - /* First pass: overwrite existing entries and count truly new ones */ new_count = 0; for (i = 0; i < count; i++) { int found = 0; @@ -979,7 +944,6 @@ void stk_pending_add_batch(const char (*paths)[STK_PATH_MAX_OS], size_t count) free(stk_pending); stk_pending = new_pending; - /* Second pass: append only the truly new ones */ for (i = 0; i < count; i++) { int found = 0; extract_module_id(paths[i], incoming_id);