49 Commits

Author SHA1 Message Date
anth64 0ebeafd4bb docs: update changelog and readme for stk_module_load fix 2026-03-07 17:07:37 +01:00
anth64 03cce766cd fix(module): add missing logging for deferred module dependencies 2026-03-07 17:04:41 +01:00
anth64 5b252d2b4e docs: update README and CHANGELOG for v1.0.0-pre.6 2026-03-07 14:52:26 +01:00
anth64 08c846d641 perf: batch stk_pending_add calls in stk_poll load loops
Replace per-module stk_pending_add() calls in the load holes loop and
append_modules loop with a single shared load_batch[load_count] buffer,
flushed via stk_pending_add_batch() once after both loops complete.
2026-03-07 14:51:10 +01:00
anth64 6ca46fe79f docs: update README and CHANGELOG for v1.0.0-pre.5 2026-03-07 14:41:20 +01:00
anth64 ba3a9dd163 perf: batch stk_init deferred module pending adds 2026-03-07 14:35:21 +01:00
anth64 a28c4818ab docs: update README and CHANGELOG for v1.0.0-pre.4 2026-03-07 14:25:00 +01:00
anth64 49ecd8fde5 perf: batch pending queue allocations in poll and retry
Replace per-module malloc calls in stk_pending_retry and stk_poll
with bulk allocations.

- stk_pending_retry: single realloc to module_count + stk_pending_count
  before the retry loop instead of realloc + 1 per module at attempt_load
- stk_poll unload loop: collect cascade deps into a batch array, call
  stk_pending_add_batch once after instead of stk_pending_add per module
- stk_poll cascade loop: same pattern, batch collect and add once per
  cascade iteration
2026-03-07 14:21:15 +01:00
anth64 06d04cf92b docs: update CHANGELOG and README for v1.0.0-pre.3 2026-03-07 12:16:47 +01:00
anth64 4250f91969 feat(stk.h, module, stk): improve dependency failure logging
- Add STK_MOD_DEP_LOG_BUFFER (2048) to stk.h for the dep failure message
buffer size.

- Add stk_log_dependency_failures(index, action) to module.c. Walks all
deps for the given module, skips satisfied ones, and builds a single
log line listing every unmet dep with its reason: "not found" or
"requires <constraint>, have <version>". The action parameter
("Deferring" / "Unloading") lets call sites produce contextually
appropriate messages:

  - 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)

- Replace the silent defer at init and the vague "unmet dependencies"
cascade log in stk_poll() with calls to stk_log_dependency_failures().
2026-03-07 11:34:23 +01:00
anth64 42a96f2bc0 feat(module, stk): dependency ordering, cascade unload, and pending queue
- Refactor the Kahn topological sort into a generic stk_kahn_sort() that
accepts a has_dep callback and an on_cycle callback, eliminating direct
coupling to stk_modules. stk_topo_sort() becomes a thin wrapper using
stk_loaded_has_dep() and stk_log_cycle(). stk_sort_load_order() uses the
same core via stk_batch_has_dep(), which opens tmp files to inspect deps
so simultaneous load events are processed dependency-first without needing
a retry cycle.

- Split stk_module_load() into stk_module_preload(), stk_module_activate(),
stk_module_discard(), and stk_validate_dependencies_single(). preload
handles library loading and metadata only; activate calls init; discard
cleans up without calling shutdown; validate_dependencies_single checks a
single module's deps. stk_module_load() composes these in order and does
not call init if deps are unmet.

- On UNLOAD events in stk_poll(), expand the unload set to include all
transitively dependent loaded modules via stk_collect_dependents(), sort
dependents-first via stk_sort_unload_order(), and unload in that order.
Modules unloaded due to expansion are queued to pending so they reload
automatically when their dependency returns. Remove trim_arrays, now
handled by the compact pass after the unload block.

- On LOAD events with unmet dependencies, add the tmp path to the pending
queue instead of dropping the module. stk_pending_retry() skips already
loaded entries and prunes entries whose file no longer exists. Move the
free_poll label above stk_pending_retry() so retry always runs regardless
of which path exits the event processing block.
2026-03-07 09:38:46 +01:00
anth64 55fcc019e4 chore(CHANGELOG.md): add v1.0.0-pre.2 entry 2026-03-06 22:44:04 +01:00
anth64 5d758c2998 fix(module.c): validate dependencies before calling init in stk_module_load 2026-03-06 22:41:31 +01:00
anth64 2e2c3d5e9e feat: phase 1 complete - dependency system, cascade unload, pending queue (v1.0.0-pre.1) 2026-03-06 22:13:35 +01:00
anth64 6477cde367 feat(module.c, stk.c): implement pending queue and cascade dependency resolution 2026-03-06 07:52:34 +01:00
anth64 4f4ae80a14 test: add test_mod_dep build targets and update test_mod with metadata 2026-03-05 00:10:06 +01:00
anth64 8e9007fdfe refactor(module.c, stk.c): reorder stk_mod_t fields largest to smallest 2026-03-04 23:37:15 +01:00
anth64 6e8df4e0e1 feat(stk.c): add module metadata logging on init, load, reload, and unload 2026-03-04 07:46:29 +01:00
anth64 f4c76f7b5a feat(module.c): log all dependency validation failures before returning error 2026-03-03 23:10:46 +01:00
anth64 5e7b41ab68 feat(module.c): log offending modules on circular dependency detection 2026-03-03 23:03:46 +01:00
anth64 9a4b5ee9ef refactor(stk.h, module.c, stk.c): switch deps to exported array and clean up types
- Move stk_dep_t to public header
- Rename stk_set_module_dependencies_fn to stk_set_module_deps_sym
- Change default deps symbol name from stk_mod_dependencies to stk_mod_deps
- Read deps as direct exported stk_dep_t array instead of function call
- Update sentinel check to .id[0] != '\0'
- Default module version to 0.0.0 if not provided or invalid
- Remove deps_func from union in stk_module_load
2026-03-03 22:53:37 +01:00
anth64 ceb389de0d feat(module.c, stk.c): expose dependency functions and integrate into init and poll
- Make stk_validate_dependencies and stk_topo_sort non static
- Add extern declarations in stk.c
- Run stk_validate_dependencies after module loads in stk_init
- Validation failure is fatal; unload all modules and return error code
- Run stk_topo_sort after validation, sort failure is logged but not fatal
- Add validate_deps label in stk_poll so all load/unload paths run validation
- Add dep error codes to stk_error_string
2026-03-03 22:29:59 +01:00
anth64 6faf33d944 feat(module.c): add topological sort with cycle detection 2026-03-03 22:21:04 +01:00
anth64 46f0f31ed7 feat(module.c): add dependency validation pass 2026-03-03 22:09:24 +01:00
anth64 e48d5f0d5b feat(module.c): add internal version parsing and constraint validation 2026-03-03 21:59:45 +01:00
anth64 dc014292cb refactor(module.c, stk.c): replace parallel arrays with stk_mod_t struct
- Consolidate handles, function pointers, metadata, and deps into single stk_mod_t
- Remove all separate metadata arrays and index mappings
- Simplify init, free, realloc, load, and unload significantly
- stk.c externs reduced to stk_modules and module_count
2026-03-03 21:42:43 +01:00
anth64 cb7c43f8af feat(module.c): add stk_dep_t struct and dependency arrays 2026-03-03 21:23:57 +01:00
anth64 6bddc6a888 refactor(module.c): extract common setter logic into stk_set_fn_name helper 2026-03-03 21:06:37 +01:00
anth64 7d23ddbc11 feat(module.c): add stk_mod_dependencies setter and default symbol name 2026-03-03 20:50:52 +01:00
anth64 9aba1a9ad8 docs(readme): add missing module metadata function documentation 2026-03-02 22:02:39 +01:00
anth64 d344e9bbd3 feat(stk.h): add dependency management defines and callbacks
Add error codes for dependency failures, constraint operators,
and setter for custom dependencies symbol name.
2026-03-02 21:53:58 +01:00
anth64 33de5e981e docs(README): update project status to 0.1.3 2026-02-26 07:37:54 +01:00
anth64 aefd962baf chore: bump version to 0.1.3 2026-02-25 22:45:58 +01:00
anth64 983f9b7393 feat(module.c): add optional module metadata support
- Add tight-packed arrays with index mappings for name, version, and description
- Resolve optional metadata symbols at load time via platform_get_symbol
- Add setter functions for metadata symbol name overrides
2026-02-25 22:31:22 +01:00
anth64 13067902a8 feat: add buffer constants and setters for optional module metadata 2026-02-25 21:37:05 +01:00
anth64 345cd35f19 chore: bump version to 0.1.2 2026-02-15 23:05:18 +01:00
anth64 c2b9571c2d fix(platform,core,test): fix Windows compatibility issues
- platform_mkdir now checks if directory exists before creating
- Add FILE_ATTRIBUTE_HIDDEN for dot-prefixed directories on Windows
- Create parent mods directory before temp directory
- Move module unload before copy in reload sequence to fix issue on Windows
- Force CMD shell in test makefile on Windows

Test makefile now uses cmd.exe instead of bash on Windows, fixing
syntax errors when running via build.bat.
2026-02-15 22:53:12 +01:00
anth64 3f7f216c92 chore: bump version to 0.1.1 and update documentation 2026-02-14 17:25:20 +01:00
anth64 70d50dda92 fix(logging): correct log level severity order
Reverse enum so lower values = less severe, higher = more severe.
This makes the filter check (level < min_log_level) work correctly.

- DEBUG (0) - least severe, filtered by default
- INFO (1) - default minimum level
- WARN (2) - warnings and above
- ERROR (3) - most severe, always shown

Fixes incorrect filtering where ERROR/WARN were being blocked.
2026-02-14 17:22:39 +01:00
anth64 70e9ec2fc3 chore: bump version to 0.1.0 and update documentation
- Update stk_version.h: 0.0.4 → 0.1.0
- Add CHANGELOG entry for 0.1.0 release
  - C89 compliance fixes
  - Flags bitfield system
  - Enhanced logging system
- Update README project status and API reference
- Document new logging functions and configuration
2026-02-14 17:06:50 +01:00
anth64 110b7ffca8 feat(logging)!: add enhanced logging system
BREAKING CHANGE: stk_log() signature changed to require log level

- Add log levels (ERROR, WARN, INFO, DEBUG) with runtime filtering
- Add timestamps (yyyy-mm-dd HH:MM:SS.mmm format)
- Add stk_set_log_output(), stk_set_log_prefix(), stk_set_log_level()
- Platform-specific timestamp: GetLocalTime (Windows), gettimeofday (POSIX)
- Consolidate duplicate platform includes into POSIX common block
- Setting log output to NULL disables logging.

Default: INFO level, "stk" prefix, stdout output. All internal messages
use appropriate severity levels. Single byte flag controls enable/disable.
2026-02-14 16:59:56 +01:00
anth64 26fb19a7f5 feat(core): add flags bitfield system
- Replace stk_initialized with stk_flags bitfield
- Add STK_FLAG_INITIALIZED (0x01) and STK_FLAG_LOGGING_ENABLED (0x02)
- Add stk_set_logging_enabled() and stk_is_logging_enabled() API
- Single byte for all boolean state and settings

Logging enabled by default, packs all flags into one byte for efficiency.
2026-02-14 12:39:13 +01:00
anth64 bcb1795218 fix(core)!: enforce strict C89 compliance
BREAKING CHANGE: Public API now uses unsigned char instead of uint8_t

- Remove stdint.h dependency (C99 feature, not C89, I am a fucking idiot)
- Replace uint8_t with unsigned char throughout codebase
- Affects stk_init() return type and internal functions
- Corrects unintended C99 dependency, restoring intended C89 compliance
2026-02-14 11:41:41 +01:00
anth64 fb0d8adb8f chore: bump version to 0.0.4 2026-02-11 00:22:59 +01:00
anth64 2c4d27f915 fix(linux): prevent segfault from invalid module indices during rapid reloads
When spamming file changes rapidly, inotify can report stale UNLOAD/RELOAD
events for modules that were already unloaded by previous events in the same
poll cycle. This caused is_mod_loaded() to return -1, which was then cast to
size_t (18446744073709551615) and used as an array index, causing segfaults.

Additionally, event counts were calculated before validation, causing loops to
run more iterations than valid indices were populated, reading garbage values.

Changes:
- stk.c: Check if is_mod_loaded() returns valid index (>= 0) before adding
  to unload/reload lists
- stk.c: Reset and recalculate counts after populating arrays with only valid
  indices to prevent loop overrun
- Skip processing events for modules that are no longer loaded

This completes the Linux stability fixes started in v0.0.2.
2026-02-11 00:17:33 +01:00
anth64 96fb957991 chore: bump version to 0.0.3 2026-02-09 23:03:35 +01:00
anth64 0cbee45ad2 fix(platform): replace strncpy with memmove to silence -Wrestrict warning 2026-02-09 22:53:42 +01:00
anth64 142a61a843 chore: bump version to 0.0.2 2026-02-09 22:32:55 +01:00
anth64 f83f2051ef fix(linux): prevent segfaults during rapid module reloads
Fixes multiple issues causing segfaults when hot-reloading modules
on Linux, particularly when file changes are detected rapidly:

- Enable is_file_ready() check on Linux to prevent loading partially-written
  shared libraries (previously only used on Windows/BSD)
- Fix event deduplication on Linux to actually remove duplicate inotify events
  instead of just marking them, preventing double-free on same module
- Reorder reload operations to unload old module only after successfully
  copying new version, avoiding invalid state when copy fails

Changes:
- platform.c: Remove __linux__ guards around is_file_ready() function
- platform.c: Add compaction step after deduplication to remove -1 entries
- stk.c: Move module unload to after platform_copy_file() in reload loop

These changes make Linux hot-reload as robust as Windows/BSD implementations.
2026-02-09 22:25:02 +01:00
14 changed files with 2003 additions and 372 deletions
+174 -1
View File
@@ -7,6 +7,164 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [1.0.0-pre.7] - 2026-03-07
### Fixed
- `stk_module_load()`: added missing dependency failure logging when a module is deferred to the pending queue.
## [1.0.0-pre.6] - 2026-03-07
### Changed
- `stk_poll()`: replaced per-module `stk_pending_add()` calls inside the load holes and `append_modules` loops with batch collection followed by a single `stk_pending_add_batch()` call after both loops complete.
## [1.0.0-pre.5] - 2026-03-07
### Changed
- `stk_init()`: replaced per-module `stk_pending_add()` calls inside the deferred module loop with batch collection followed by a single `stk_pending_add_batch()` call after the loop.
## [1.0.0-pre.4] - 2026-03-07
### Changed
- `stk_pending_retry()`: replaced per-module `realloc` at `attempt_load` with a single bulk allocation upfront (`module_count + stk_pending_count`) before the retry loop.
- `stk_poll()`: replaced per-module `stk_pending_add()` calls inside the unload and cascade loops with batch collection followed by a single `stk_pending_add_batch()` call after each loop.
## [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 <constraint>, have <version>`
- `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
- Module `init` is no longer called if dependencies are not satisfied. The library handle is unloaded and a dep error is returned.
## [1.0.0-pre.1] - 2026-03-06
### Added
- **Dependency System**: Full module dependency declaration and resolution
- Modules declare dependencies via an exported `stk_mod_deps` symbol, no stk headers required in modules, only the memory layout contract must be respected (`{ char[64], char[32] }` sentinel-terminated array)
- Version constraint operators: `=` (exact), `>=` (minimum, default), `^` (compatible — same major, greater or equal minor/patch)
- Version defaults to `0.0.0` if not provided or unparseable
- Dependency validation on `stk_init` and every `stk_poll` cycle
- Topological sort with cycle detection, load and unload ordering enforced
- All dependency failures logged before returning
- **Cascade Unload**: When a module is removed, all dependents cascade unload automatically and are added to the pending queue. Cascades repeat until the loaded set is stable
- **Pending Queue**: Modules that fail dependency validation are deferred. When their dependencies become available they are retried and loaded automatically. Entries are removed if their file is deleted
### Changed
- `stk_set_module_dependencies_fn` renamed to `stk_set_module_deps_sym`. Deps are now an exported array symbol, not a function
- Default deps symbol name changed from `stk_mod_dependencies` to `stk_mod_deps`
- `stk_dep_t` added to public header `stk.h`
- `stk_mod_t` field order changed to largest-to-smallest for correct memory alignment with zero padding waste
- Dependency validation failure during `stk_init` no longer fatal, affected modules are deferred to the pending queue
- Poll cycle now logs the full loaded module list as a single summary after any event that changes the loaded set, instead of logging each module individually
### Notes
- This release marks Phase 1 complete: hot-reloading foundation with dependency management
- Memory layout contract for `stk_mod_deps`: sentinel-terminated array of `{ char[64], char[32] }`. Modules do not need to include `stk.h`, define the struct inline or via your engine's own header as long as the layout matches
## [0.1.3] - 2026-02-25
### Added
- **Module Metadata**: Optional metadata support for modules
- `stk_mod_name` - human-readable module name
- `stk_mod_version` - semantic version string
- `stk_mod_description` - module description
- All three are optional, missing symbols are not errors
- Metadata stored in tight-packed arrays with index mappings
- Setter functions to override default symbol names before `stk_init()`
## [0.1.2] - 2026-02-15
### Fixed
- **Windows**
- Module reload now works correctly, idk how it was broken
- Temp directory creation fixed
- `.tmp` directory now hidden via FILE_ATTRIBUTE_HIDDEN
- Test build fixed
- Force cmd.exe shell in gmake.mk
- Fix bash syntax errors when running build.bat
- **All Platforms**: platform_mkdir now checks if directory exists before creating
## [0.1.1] - 2026-02-14
### Fixed
- **Logging**: Corrected log level severity order in enum
- Reversed order so DEBUG (0) < INFO (1) < WARN (2) < ERROR (3)
- Fixes filtering logic where ERROR/WARN were incorrectly blocked
- Default INFO level now properly shows INFO, WARN, and ERROR while filtering DEBUG
## [0.1.0] - 2026-02-14
### Fixed
- **C89 Compliance**: Removed stdint.h dependency (C99 feature)
- Replaced all uint8_t with unsigned char throughout codebase
- Ensures strict C89 compliance for maximum portability
### Added
- **Flags system**: Centralized bitfield for boolean state and settings
- Replaces stk_initialized with stk_flags for efficient memory usage
- Single byte packs all boolean flags (STK_FLAG_INITIALIZED, STK_FLAG_LOGGING_ENABLED)
- Runtime-changeable logging control via stk_set_logging_enabled()
- **Enhanced logging system**: Complete rewrite with modern features
- Log levels: ERROR, WARN, INFO, DEBUG with runtime filtering
- Timestamps: yyyy-mm-dd HH:MM:SS.mmm format (platform-specific implementation)
- Configurable log output stream (stk_set_log_output)
- Configurable log prefix (stk_set_log_prefix, defaults to "stk")
- Configurable minimum log level (stk_set_log_level, defaults to INFO)
- Platform abstraction for timestamps (GetLocalTime on Windows, gettimeofday on POSIX)
### Changed
- **BREAKING**: stk_log() signature changed from stk_log(FILE *fp, ...) to stk_log(stk_log_level_t level, ...)
- **BREAKING**: All uint8_t types replaced with unsigned char
- All internal STK messages now use appropriate log levels
- Setting log output to NULL disabls logging
### Notes
- This release completes Phase 1 logging improvements
- Dependency management still in progress for Phase 1 completion
## [0.0.4] - 2026-02-11
### Fixed
- **Linux**: Fixed segfault from invalid module indices during extremely rapid file changes
- Added validation check to skip stale UNLOAD/RELOAD events for already-unloaded modules
- Prevents is_mod_loaded() returning -1 from being used as array index (SIZE_MAX)
- Fixed event count mismatch where loops would run more iterations than valid indices populated
- Completes the Linux hot-reload stability fixes from v0.0.2
## [0.0.3] - 2026-02-10
### Fixed
- **Compilation**: Fixed GCC `-Wrestrict` warning in Linux directory watching code by replacing `strncpy` with `memmove` for overlapping memory operations
- Ensures clean compilation without warnings while maintaining identical runtime behavior
- Uses semantically correct function for moving memory within the same buffer
## [0.0.2] - 2026-02-09
### Fixed
- **Linux**: Fixed segfaults during rapid module reloads when file changes are detected in quick succession
- Enabled file readiness checks on Linux (previously only used on Windows/BSD) to prevent loading partially-written shared libraries
- Fixed inotify event deduplication to actually remove duplicate events instead of just marking them
- Reordered reload operations to only unload old module after successfully copying new version, preventing invalid state when copy fails
- **All platforms**: Improved reload safety by deferring module unload until after successful file copy
### Changed
- Made `is_file_ready()` check available on all Unix platforms (was previously excluded on Linux)
## [0.0.1] - 2026-02-01 ## [0.0.1] - 2026-02-01
### Added ### Added
@@ -25,5 +183,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Dependency management and versioning not yet implemented - Dependency management and versioning not yet implemented
- API is unstable and subject to change in future releases - API is unstable and subject to change in future releases
[Unreleased]: https://github.com/anth64/stk/compare/v0.0.1...HEAD [Unreleased]: https://github.com/anth64/stk/compare/v1.0.0-pre.7...HEAD
[1.0.0-pre.7]: https://github.com/anth64/stk/compare/v1.0.0-pre.6...v1.0.0-pre.7
[1.0.0-pre.6]: https://github.com/anth64/stk/compare/v1.0.0-pre.5...v1.0.0-pre.6
[1.0.0-pre.5]: https://github.com/anth64/stk/compare/v1.0.0-pre.4...v1.0.0-pre.5
[1.0.0-pre.4]: https://github.com/anth64/stk/compare/v1.0.0-pre.3...v1.0.0-pre.4
[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
[0.1.2]: https://github.com/anth64/stk/releases/tag/v0.1.2
[0.1.1]: https://github.com/anth64/stk/releases/tag/v0.1.1
[0.1.1]: https://github.com/anth64/stk/releases/tag/v0.1.1
[0.1.0]: https://github.com/anth64/stk/releases/tag/v0.1.0
[0.0.4]: https://github.com/anth64/stk/releases/tag/v0.0.4
[0.0.3]: https://github.com/anth64/stk/releases/tag/v0.0.3
[0.0.2]: https://github.com/anth64/stk/releases/tag/v0.0.2
[0.0.1]: https://github.com/anth64/stk/releases/tag/v0.0.1 [0.0.1]: https://github.com/anth64/stk/releases/tag/v0.0.1
+74 -9
View File
@@ -131,6 +131,42 @@ cc -shared -o my_module.dll my_module.c
Place the compiled module in the `mods/` directory (default), and `stk` will automatically load it and watch for changes. Place the compiled module in the `mods/` directory (default), and `stk` will automatically load it and watch for changes.
### Module Metadata
Modules can optionally export metadata functions:
```c
const char *stk_mod_name(void) { return "My Module"; }
const char *stk_mod_version(void) { return "1.2.0"; }
const char *stk_mod_description(void) { return "Does something useful"; }
```
All three are optional. Version defaults to `0.0.0` if not exported or unparseable.
### Declaring Dependencies
Modules declare dependencies via an exported sentinel-terminated array. No stk headers are required in the module. The only requirement is that the memory layout matches: `{ char[64], char[32] }`.
```c
typedef struct {
char id[64];
char version[32];
} dep_t;
dep_t stk_mod_deps[] = {
{ "physics", ">=2.0.0" },
{ "renderer", "^1.0.0" },
{ "", "" } /* sentinel */
};
```
Version constraint operators:
- `=1.0.0` exact match
- `>=2.0.0` minimum version, also the default if no operator is specified
- `^1.0.0` same major, greater or equal minor/patch
If a dependency is removed at runtime, all affected modules are unloaded and queued. When the dependency comes back, they load automatically.
### Configuration ### Configuration
```c ```c
@@ -146,6 +182,18 @@ stk_set_module_init_fn("my_init");
/* Set custom shutdown function name (default: "stk_mod_shutdown") */ /* Set custom shutdown function name (default: "stk_mod_shutdown") */
stk_set_module_shutdown_fn("my_shutdown"); stk_set_module_shutdown_fn("my_shutdown");
/* Set function name to get module name (default: "stk_mod_name") */
stk_set_module_name_fn("my_mod_name");
/* Set function name to get module version (default: "stk_mod_version") */
stk_set_module_version_fn("my_mod_version");
/* Set function name to get module description (default: "stk_mod_description") */
stk_set_module_description_fn("my_mod_description");
/* Set deps array symbol name (default: "stk_mod_deps") */
stk_set_module_deps_sym("my_mod_deps");
/* /*
* All the above functions must be called before stk_init() * All the above functions must be called before stk_init()
* if the defaults need to be changed. * if the defaults need to be changed.
@@ -156,7 +204,7 @@ stk_init();
### API Reference ### API Reference
#### Initialization #### Initialization
- `uint8_t stk_init(void)` - Initialize stk, returns `STK_INIT_SUCCESS` on success - `unsigned char stk_init(void)` - Initialize stk, returns `STK_INIT_SUCCESS` on success
- `void stk_shutdown(void)` - Shutdown and cleanup all modules - `void stk_shutdown(void)` - Shutdown and cleanup all modules
#### Runtime #### Runtime
@@ -168,24 +216,41 @@ stk_init();
- `void stk_set_tmp_dir_name(const char *name)` - Set temp directory name - `void stk_set_tmp_dir_name(const char *name)` - Set temp directory name
- `void stk_set_module_init_fn(const char *name)` - Set module init function name - `void stk_set_module_init_fn(const char *name)` - Set module init function name
- `void stk_set_module_shutdown_fn(const char *name)` - Set module shutdown function name - `void stk_set_module_shutdown_fn(const char *name)` - Set module shutdown function name
- `void stk_set_module_name_fn(const char *name);` - Set module name function name
- `void stk_set_module_version_fn(const char *name);` - Set module version function name
- `void stk_set_module_description_fn(const char *name);` - Set module description function name
- `void stk_set_module_deps_sym(const char *name)` - Set module deps array symbol name (default: `stk_mod_deps`)
#### Logging
- `void stk_set_logging_enabled(unsigned char enabled)` - Enable/disable all logging
- `unsigned char stk_is_logging_enabled(void)` - Query logging state
- `void stk_set_log_output(FILE *fp)` - Set log output stream (default: stdout, NULL disables)
- `void stk_set_log_prefix(const char *prefix)` - Set log prefix (default: "stk")
- `void stk_set_log_level(stk_log_level_t level)` - Set minimum log level (default: INFO)
**Log Levels:** `STK_LOG_ERROR`, `STK_LOG_WARN`, `STK_LOG_INFO`, `STK_LOG_DEBUG`
--- ---
## Project Status ## Project Status
**Current Version:** 0.0.1 (Pre-release) **Current Version:** 1.0.0-pre.7
This is an early release proving the core hot-reload foundation. Phase 1 is still in progress.
### What Works ### What Works
- Cross-platform module loading and hot-reloading - Cross-platform module loading and hot-reloading
- File watching (inotify/kqueue/FindFirstFile) - File watching (inotify/kqueue/FindFirstFile)
- Basic error handling - Robust hot-reload even during extremely rapid file changes
- Enhanced logging with levels, timestamps, and filtering
- 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
### In Progress (Phase 1) ### Phase 2
- Complete logging system (log levels, verbosity, output configuration) - WASM module support
- Module metadata (name, version, description)
- Dependency management and versioning
See [CHANGELOG.md](CHANGELOG.md) for detailed release notes. See [CHANGELOG.md](CHANGELOG.md) for detailed release notes.
+30 -2
View File
@@ -2,12 +2,17 @@
#define STK_H #define STK_H
#include "stk_version.h" #include "stk_version.h"
#include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
/* Buffers */ /* Buffers */
#define STK_LOG_PREFIX_BUFFER 64
#define STK_MOD_DEP_OPERATOR_BUFFER 3
#define STK_MOD_DEP_LOG_BUFFER 2048
#define STK_MOD_DESC_BUFFER 256
#define STK_MOD_DIR_BUFFER 256 #define STK_MOD_DIR_BUFFER 256
#define STK_MOD_ID_BUFFER 64 #define STK_MOD_ID_BUFFER 64
#define STK_MOD_NAME_BUFFER 128
#define STK_MOD_VERSION_BUFFER 32
#define STK_PATH_MAX 256 #define STK_PATH_MAX 256
#define STK_PATH_MAX_OS 4096 #define STK_PATH_MAX_OS 4096
@@ -23,6 +28,9 @@
#define STK_MOD_LIBRARY_LOAD_ERROR 2 #define STK_MOD_LIBRARY_LOAD_ERROR 2
#define STK_MOD_SYMBOL_NOT_FOUND_ERROR 3 #define STK_MOD_SYMBOL_NOT_FOUND_ERROR 3
#define STK_MOD_REALLOC_FAILURE 4 #define STK_MOD_REALLOC_FAILURE 4
#define STK_MOD_DEP_NOT_FOUND_ERROR 5
#define STK_MOD_DEP_VERSION_MISMATCH_ERROR 6
#define STK_MOD_DEP_CIRCULAR_ERROR 7
/* Platform return codes */ /* Platform return codes */
#define STK_PLATFORM_OPERATION_SUCCESS 0 #define STK_PLATFORM_OPERATION_SUCCESS 0
@@ -31,6 +39,15 @@
#define STK_PLATFORM_REMOVE_DIR_ERROR 3 #define STK_PLATFORM_REMOVE_DIR_ERROR 3
#define STK_PLATFORM_REMOVE_FILE_ERROR 4 #define STK_PLATFORM_REMOVE_FILE_ERROR 4
/* Settings flags */
#define STK_FLAG_INITIALIZED 0x01
#define STK_FLAG_LOGGING_ENABLED 0x02
/* Dependency constraint operators */
#define STK_DEP_MIN 0
#define STK_DEP_EXACT 1
#define STK_DEP_COMPAT 2
#if defined(__linux__) || defined(_WIN32) #if defined(__linux__) || defined(_WIN32)
#define STK_EVENT_BUFFER 4096 #define STK_EVENT_BUFFER 4096
#endif #endif
@@ -55,7 +72,12 @@ typedef enum {
STK_MOD_RELOAD STK_MOD_RELOAD
} stk_module_event_t; } stk_module_event_t;
uint8_t stk_init(void); typedef struct {
char id[STK_MOD_ID_BUFFER];
char version[STK_MOD_VERSION_BUFFER];
} stk_dep_t;
unsigned char stk_init(void);
void stk_shutdown(void); void stk_shutdown(void);
size_t stk_module_count(void); size_t stk_module_count(void);
size_t stk_poll(void); size_t stk_poll(void);
@@ -63,6 +85,12 @@ void stk_set_mod_dir(const char *path);
void stk_set_tmp_dir_name(const char *name); void stk_set_tmp_dir_name(const char *name);
void stk_set_module_init_fn(const char *name); void stk_set_module_init_fn(const char *name);
void stk_set_module_shutdown_fn(const char *name); void stk_set_module_shutdown_fn(const char *name);
void stk_set_logging_enabled(unsigned char enabled);
void stk_set_module_name_fn(const char *name);
void stk_set_module_version_fn(const char *name);
void stk_set_module_description_fn(const char *name);
void stk_set_module_deps_sym(const char *name);
unsigned char stk_is_logging_enabled(void);
#ifdef __cplusplus #ifdef __cplusplus
} }
+12 -1
View File
@@ -7,7 +7,18 @@
extern "C" { extern "C" {
#endif #endif
void stk_log(FILE *fp, const char *fmt, ...); typedef enum {
STK_LOG_DEBUG,
STK_LOG_INFO,
STK_LOG_WARN,
STK_LOG_ERROR
} stk_log_level_t;
void stk_set_log_output(FILE *fp);
void stk_set_log_prefix(const char *prefix);
void stk_set_log_level(stk_log_level_t min_level);
void stk_log(stk_log_level_t level, const char *fmt, ...);
#ifdef __cplusplus #ifdef __cplusplus
} }
+2 -2
View File
@@ -1,9 +1,9 @@
#ifndef STK_VERSION_H #ifndef STK_VERSION_H
#define STK_VERSION_H #define STK_VERSION_H
#define STK_VERSION_MAJOR 0 #define STK_VERSION_MAJOR 1
#define STK_VERSION_MINOR 0 #define STK_VERSION_MINOR 0
#define STK_VERSION_PATCH 1 #define STK_VERSION_PATCH 0
#define STK_STRINGIFY_HELPER(x) #x #define STK_STRINGIFY_HELPER(x) #x
#define STK_STRINGIFY(x) STK_STRINGIFY_HELPER(x) #define STK_STRINGIFY(x) STK_STRINGIFY_HELPER(x)
+976 -111
View File
File diff suppressed because it is too large Load Diff
+165 -117
View File
@@ -1,39 +1,34 @@
#include "stk.h" #include "stk.h"
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#ifndef _WIN32 #ifndef _WIN32
#include <dirent.h>
#include <dlfcn.h>
#include <sys/file.h> #include <sys/file.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#endif #endif
#if defined(__linux__) #if defined(__linux__)
#include <dirent.h>
#include <dlfcn.h>
#include <sys/inotify.h> #include <sys/inotify.h>
#include <sys/stat.h>
#include <unistd.h>
#elif defined(_WIN32) #elif defined(_WIN32)
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
#else #else
#include <dirent.h>
#include <dlfcn.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/event.h> #include <sys/event.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h>
#endif #endif
int is_mod_loaded(const char *module_name); int is_mod_loaded(const char *module_name);
uint8_t is_valid_module_file(const char *filename); unsigned char is_valid_module_file(const char *filename);
void extract_module_id(const char *path, char *out_id); void extract_module_id(const char *path, char *out_id);
#ifndef __linux__ static unsigned char is_file_ready(const char *dir_path, const char *filename)
static uint8_t is_file_ready(const char *dir_path, const char *filename)
{ {
char full_path[STK_PATH_MAX_OS]; char full_path[STK_PATH_MAX_OS];
#ifdef _WIN32 #ifdef _WIN32
@@ -80,6 +75,7 @@ static uint8_t is_file_ready(const char *dir_path, const char *filename)
#endif #endif
} }
#ifndef __linux__
typedef struct { typedef struct {
char filename[STK_PATH_MAX]; char filename[STK_PATH_MAX];
#ifdef _WIN32 #ifdef _WIN32
@@ -108,12 +104,32 @@ typedef struct {
} platform_watch_context_t; } platform_watch_context_t;
#endif #endif
uint8_t platform_mkdir(const char *path) unsigned char platform_mkdir(const char *path)
{ {
#ifdef _WIN32 #ifdef _WIN32
return CreateDirectoryA(path, NULL) ? STK_PLATFORM_OPERATION_SUCCESS DWORD attrib;
: STK_PLATFORM_MKDIR_ERROR;
attrib = GetFileAttributesA(path);
if (attrib != INVALID_FILE_ATTRIBUTES &&
(attrib & FILE_ATTRIBUTE_DIRECTORY))
return STK_PLATFORM_OPERATION_SUCCESS;
if (!CreateDirectoryA(path, NULL))
return STK_PLATFORM_MKDIR_ERROR;
if (strrchr(path, '\\') && *(strrchr(path, '\\') + 1) == '.')
SetFileAttributesA(path, FILE_ATTRIBUTE_HIDDEN);
else if (!strrchr(path, '\\') && path[0] == '.')
SetFileAttributesA(path, FILE_ATTRIBUTE_HIDDEN);
return STK_PLATFORM_OPERATION_SUCCESS;
#else #else
struct stat st;
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
return STK_PLATFORM_OPERATION_SUCCESS;
return mkdir(path, 0755) == 0 ? STK_PLATFORM_OPERATION_SUCCESS return mkdir(path, 0755) == 0 ? STK_PLATFORM_OPERATION_SUCCESS
: STK_PLATFORM_MKDIR_ERROR; : STK_PLATFORM_MKDIR_ERROR;
#endif #endif
@@ -130,7 +146,7 @@ int platform_remove_file(const char *path)
#endif #endif
} }
uint8_t platform_copy_file(const char *from, const char *to) unsigned char platform_copy_file(const char *from, const char *to)
{ {
char buf[STK_PATH_MAX_OS]; char buf[STK_PATH_MAX_OS];
int ret = STK_PLATFORM_FILE_COPY_ERROR; int ret = STK_PLATFORM_FILE_COPY_ERROR;
@@ -146,7 +162,6 @@ uint8_t platform_copy_file(const char *from, const char *to)
FILE *src = NULL, *dst = NULL; FILE *src = NULL, *dst = NULL;
char tmp_path[STK_PATH_MAX_OS]; char tmp_path[STK_PATH_MAX_OS];
size_t n; size_t n;
#ifndef __linux__
char dir_path[STK_PATH_MAX_OS]; char dir_path[STK_PATH_MAX_OS];
const char *filename; const char *filename;
@@ -163,7 +178,6 @@ uint8_t platform_copy_file(const char *from, const char *to)
if (!is_file_ready(dir_path, filename)) if (!is_file_ready(dir_path, filename))
goto done; goto done;
#endif
sprintf(tmp_path, "%s.tmp", to); sprintf(tmp_path, "%s.tmp", to);
@@ -205,7 +219,7 @@ done:
return ret; return ret;
} }
uint8_t platform_remove_dir(const char *path) unsigned char platform_remove_dir(const char *path)
{ {
#ifdef _WIN32 #ifdef _WIN32
WIN32_FIND_DATAA fd; WIN32_FIND_DATAA fd;
@@ -286,128 +300,127 @@ void *platform_get_symbol(void *h, const char *s)
#endif #endif
} }
char (*platform_directory_init_scan(const char *dir_path, char (*platform_directory_init_scan(const char *dir_path, size_t *out_count))
size_t *out_count))[STK_PATH_MAX] [STK_PATH_MAX] {
{ size_t count = 0, i = 0, name_len;
size_t count = 0, i = 0, name_len; char (*list)[STK_PATH_MAX] = NULL;
char(*list)[STK_PATH_MAX] = NULL;
#ifdef _WIN32 #ifdef _WIN32
WIN32_FIND_DATAA fd; WIN32_FIND_DATAA fd;
HANDLE h; HANDLE h;
char s[STK_PATH_MAX_OS]; char s[STK_PATH_MAX_OS];
sprintf(s, "%s\\*", dir_path); sprintf(s, "%s\\*", dir_path);
h = FindFirstFileA(s, &fd); h = FindFirstFileA(s, &fd);
if (h == INVALID_HANDLE_VALUE) if (h == INVALID_HANDLE_VALUE)
goto create_and_exit; goto create_and_exit;
do { do {
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
continue; continue;
if (is_valid_module_file(fd.cFileName)) if (is_valid_module_file(fd.cFileName))
count++; count++;
} while (FindNextFileA(h, &fd)); } while (FindNextFileA(h, &fd));
FindClose(h); FindClose(h);
if (count == 0) if (count == 0)
goto exit; goto exit;
list = malloc(count * sizeof(*list)); list = malloc(count * sizeof(*list));
if (!list) if (!list)
goto exit; goto exit;
h = FindFirstFileA(s, &fd); h = FindFirstFileA(s, &fd);
if (h == INVALID_HANDLE_VALUE) if (h == INVALID_HANDLE_VALUE)
goto exit; goto exit;
do { do {
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
continue; continue;
if (is_valid_module_file(fd.cFileName) && i < count) { if (is_valid_module_file(fd.cFileName) && i < count) {
name_len = strlen(fd.cFileName); name_len = strlen(fd.cFileName);
if (name_len >= STK_PATH_MAX) if (name_len >= STK_PATH_MAX)
name_len = STK_PATH_MAX - 1; name_len = STK_PATH_MAX - 1;
memcpy(list[i], fd.cFileName, name_len); memcpy(list[i], fd.cFileName, name_len);
list[i][name_len] = '\0'; list[i][name_len] = '\0';
i++; i++;
} }
} while (FindNextFileA(h, &fd)); } while (FindNextFileA(h, &fd));
FindClose(h); FindClose(h);
goto exit; goto exit;
create_and_exit: create_and_exit:
platform_mkdir(dir_path); platform_mkdir(dir_path);
exit: exit:
*out_count = i; *out_count = i;
return list; return list;
#else #else
DIR *d; DIR *d;
struct dirent *e; struct dirent *e;
struct stat st; struct stat st;
char f[STK_PATH_MAX_OS]; char f[STK_PATH_MAX_OS];
d = opendir(dir_path); d = opendir(dir_path);
if (!d) if (!d)
goto create_and_exit; goto create_and_exit;
count_loop: count_loop:
e = readdir(d); e = readdir(d);
if (!e) if (!e)
goto count_done; goto count_done;
sprintf(f, "%s/%s", dir_path, e->d_name); sprintf(f, "%s/%s", dir_path, e->d_name);
if (!is_valid_module_file(e->d_name)) if (!is_valid_module_file(e->d_name))
goto count_loop; goto count_loop;
if (stat(f, &st) != 0 || !S_ISREG(st.st_mode)) if (stat(f, &st) != 0 || !S_ISREG(st.st_mode))
goto count_loop; goto count_loop;
count++; count++;
goto count_loop; goto count_loop;
count_done: count_done:
if (count == 0) if (count == 0)
goto close_and_exit; goto close_and_exit;
rewinddir(d); rewinddir(d);
list = malloc(count * sizeof(*list)); list = malloc(count * sizeof(*list));
if (!list) if (!list)
goto close_and_exit; goto close_and_exit;
fill_loop: fill_loop:
e = readdir(d); e = readdir(d);
if (!e || i >= count) if (!e || i >= count)
goto close_and_exit; goto close_and_exit;
sprintf(f, "%s/%s", dir_path, e->d_name); sprintf(f, "%s/%s", dir_path, e->d_name);
if (!is_valid_module_file(e->d_name)) if (!is_valid_module_file(e->d_name))
goto fill_loop; goto fill_loop;
if (stat(f, &st) != 0 || !S_ISREG(st.st_mode)) if (stat(f, &st) != 0 || !S_ISREG(st.st_mode))
goto fill_loop; goto fill_loop;
name_len = strlen(e->d_name); name_len = strlen(e->d_name);
if (name_len >= STK_PATH_MAX) { if (name_len >= STK_PATH_MAX) {
name_len = STK_PATH_MAX - 1; name_len = STK_PATH_MAX - 1;
} }
memcpy(list[i++], e->d_name, name_len); memcpy(list[i++], e->d_name, name_len);
list[i - 1][name_len] = '\0'; list[i - 1][name_len] = '\0';
goto fill_loop; goto fill_loop;
create_and_exit: create_and_exit:
platform_mkdir(dir_path); platform_mkdir(dir_path);
*out_count = 0; *out_count = 0;
return NULL; return NULL;
close_and_exit: close_and_exit:
closedir(d); closedir(d);
*out_count = i; *out_count = i;
return list; return list;
#endif #endif
} }
#if !defined(__linux__) && !defined(_WIN32) #if !defined(__linux__) && !defined(_WIN32)
static void update_watches(platform_watch_context_t *ctx) static void update_watches(platform_watch_context_t *ctx)
@@ -665,7 +678,7 @@ stk_module_event_t *platform_directory_watch_check(
int fd = (int)(long)handle; int fd = (int)(long)handle;
char buf[STK_EVENT_BUFFER]; char buf[STK_EVENT_BUFFER];
ssize_t len; ssize_t len;
size_t index = 0, count = 0, i; size_t index = 0, count = 0, i, write_index;
stk_module_event_t *evs; stk_module_event_t *evs;
char *ptr, *end; char *ptr, *end;
struct inotify_event *e; struct inotify_event *e;
@@ -763,6 +776,19 @@ stk_module_event_t *platform_directory_watch_check(
} }
} }
write_index = 0;
for (i = 0; i < index; ++i) {
if (evs[i] != -1) {
if (write_index != i) {
evs[write_index] = evs[i];
memmove((*file_list)[write_index],
(*file_list)[i], STK_PATH_MAX);
}
write_index++;
}
}
index = write_index;
*out_count = index; *out_count = index;
return evs; return evs;
@@ -1000,3 +1026,25 @@ no_change:
return NULL; return NULL;
#endif #endif
} }
void platform_get_timestamp(char *buffer, size_t size)
{
#ifdef _WIN32
SYSTEMTIME st;
GetLocalTime(&st);
sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d.%03d", st.wYear,
st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond,
st.wMilliseconds);
#else
struct timeval tv;
struct tm *tm_info;
gettimeofday(&tv, NULL);
tm_info = localtime(&tv.tv_sec);
sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d.%03d",
tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec,
(int)(tv.tv_usec / 1000));
#endif
}
+445 -110
View File
@@ -1,7 +1,6 @@
#include "stk.h" #include "stk.h"
#include "platform.h" #include "platform.h"
#include "stk_log.h" #include "stk_log.h"
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -9,18 +8,26 @@
typedef int (*stk_init_mod_func)(void); typedef int (*stk_init_mod_func)(void);
typedef void (*stk_shutdown_mod_func)(void); typedef void (*stk_shutdown_mod_func)(void);
extern void **stk_handles; typedef struct {
extern stk_init_mod_func *stk_inits; char desc[STK_MOD_DESC_BUFFER];
extern stk_shutdown_mod_func *stk_shutdowns; char name[STK_MOD_NAME_BUFFER];
extern char (*stk_module_ids)[STK_MOD_ID_BUFFER]; char id[STK_MOD_ID_BUFFER];
char version[STK_MOD_VERSION_BUFFER];
void *handle;
stk_init_mod_func init;
stk_shutdown_mod_func shutdown;
stk_dep_t *deps;
size_t dep_count;
} stk_mod_t;
extern stk_mod_t *stk_modules;
extern size_t module_count; extern size_t module_count;
uint8_t stk_initialized = 0; unsigned char stk_flags = STK_FLAG_LOGGING_ENABLED;
static char stk_mod_dir[STK_PATH_MAX_OS] = "mods"; static char stk_mod_dir[STK_PATH_MAX_OS] = "mods";
static char stk_tmp_name[STK_MOD_ID_BUFFER] = ".tmp"; static char stk_tmp_name[STK_MOD_ID_BUFFER] = ".tmp";
static char stk_tmp_dir[STK_PATH_MAX_OS] = "mods/.tmp"; static char stk_tmp_dir[STK_PATH_MAX_OS] = "";
static void *watch_handle = NULL; static void *watch_handle = NULL;
char (*platform_directory_init_scan(const char *path, char (*platform_directory_init_scan(const char *path,
@@ -30,20 +37,35 @@ void platform_directory_watch_stop(void *handle);
stk_module_event_t *platform_directory_watch_check( stk_module_event_t *platform_directory_watch_check(
void *handle, char (**file_list)[STK_PATH_MAX], size_t *out_count, void *handle, char (**file_list)[STK_PATH_MAX], size_t *out_count,
char (*loaded_module_ids)[STK_MOD_ID_BUFFER], const size_t loaded_count); char (*loaded_module_ids)[STK_MOD_ID_BUFFER], const size_t loaded_count);
uint8_t platform_mkdir(const char *path); unsigned char platform_mkdir(const char *path);
uint8_t platform_copy_file(const char *from, const char *to); unsigned char platform_copy_file(const char *from, const char *to);
uint8_t platform_remove_dir(const char *path); unsigned char platform_remove_dir(const char *path);
void extract_module_id(const char *path, char *out_id); void extract_module_id(const char *path, char *out_id);
int is_mod_loaded(const char *module_id); int is_mod_loaded(const char *module_id);
size_t stk_module_count(void); size_t stk_module_count(void);
uint8_t stk_module_load(const char *path, int index); unsigned char stk_module_preload(const char *path, int index);
uint8_t stk_module_load_init(const char *path, int index); unsigned char stk_module_activate(size_t index);
uint8_t stk_module_init_memory(size_t capacity); unsigned char stk_validate_dependencies_single(size_t index);
uint8_t stk_module_realloc_memory(size_t new_capacity); void stk_log_dependency_failures(size_t index, const char *action);
void stk_module_discard(size_t index);
unsigned char stk_module_load(const char *path, int index);
unsigned char stk_module_load_init(const char *path, int index);
unsigned char stk_module_init_memory(size_t capacity);
unsigned char stk_module_realloc_memory(size_t new_capacity);
void stk_module_unload(size_t index); void stk_module_unload(size_t index);
void stk_module_unload_all(void); void stk_module_unload_all(void);
unsigned char stk_validate_dependencies(size_t count);
unsigned char stk_topo_sort(size_t count, size_t *order);
void stk_pending_add(const char *path);
void stk_pending_add_batch(const char (*paths)[STK_PATH_MAX_OS], size_t count);
void stk_pending_remove(const char *id);
size_t stk_pending_retry(void);
void stk_sort_unload_order(size_t *indices, size_t n);
void stk_collect_dependents(size_t *indices, size_t *count);
void stk_sort_load_order(int *file_indices, size_t n,
char (*file_names)[STK_PATH_MAX], const char *tmp_dir);
static void build_path(char *dest, size_t dest_size, const char *dir, static void build_path(char *dest, size_t dest_size, const char *dir,
const char *file) const char *file)
@@ -65,29 +87,70 @@ static const char *stk_error_string(int error_code)
return "init failure"; return "init failure";
case STK_MOD_REALLOC_FAILURE: case STK_MOD_REALLOC_FAILURE:
return "memory reallocation failed"; return "memory reallocation failed";
case STK_MOD_DEP_NOT_FOUND_ERROR:
return "dependency not found";
case STK_MOD_DEP_VERSION_MISMATCH_ERROR:
return "dependency version mismatch";
case STK_MOD_DEP_CIRCULAR_ERROR:
return "circular dependency detected";
default: default:
return "unknown error"; return "unknown error";
} }
} }
uint8_t stk_init(void) static void stk_log_module(size_t index)
{
const char *name =
stk_modules[index].name[0] ? stk_modules[index].name : NULL;
const char *desc =
stk_modules[index].desc[0] ? stk_modules[index].desc : NULL;
if (name && desc)
stk_log(STK_LOG_INFO, " %s v%s - %s (%s)",
stk_modules[index].id, stk_modules[index].version, desc,
name);
else if (name)
stk_log(STK_LOG_INFO, " %s v%s (%s)", stk_modules[index].id,
stk_modules[index].version, name);
else if (desc)
stk_log(STK_LOG_INFO, " %s v%s - %s", stk_modules[index].id,
stk_modules[index].version, desc);
else
stk_log(STK_LOG_INFO, " %s v%s", stk_modules[index].id,
stk_modules[index].version);
}
static void stk_log_modules(void)
{
size_t i;
stk_log(STK_LOG_INFO, "Loaded modules (%lu):", module_count);
for (i = 0; i < module_count; i++)
stk_log_module(i);
}
unsigned char stk_init(void)
{ {
char (*files)[STK_PATH_MAX] = NULL; char (*files)[STK_PATH_MAX] = NULL;
size_t file_count, i, successful_loads = 0; char (*test_scan)[STK_PATH_MAX];
size_t file_count, i, j, write, successful_loads = 0;
size_t index, test_count;
char full_path[STK_PATH_MAX_OS]; char full_path[STK_PATH_MAX_OS];
char tmp_path[STK_PATH_MAX_OS]; char tmp_path[STK_PATH_MAX_OS];
int load_result; int load_result;
unsigned char dep_result;
size_t *order = NULL;
char (*init_batch)[STK_PATH_MAX_OS] = NULL;
size_t init_batch_count = 0;
platform_mkdir(stk_mod_dir);
build_path(stk_tmp_dir, sizeof(stk_tmp_dir), stk_mod_dir, stk_tmp_name);
if (platform_mkdir(stk_tmp_dir) != STK_PLATFORM_OPERATION_SUCCESS) { if (platform_mkdir(stk_tmp_dir) != STK_PLATFORM_OPERATION_SUCCESS) {
char (*test_scan)[STK_PATH_MAX];
size_t test_count;
test_scan = test_scan =
platform_directory_init_scan(stk_tmp_dir, &test_count); platform_directory_init_scan(stk_tmp_dir, &test_count);
if (test_scan) if (test_scan)
free(test_scan); free(test_scan);
if (!test_scan && test_count == 0) { if (!test_scan && test_count == 0) {
stk_log(stderr, stk_log(STK_LOG_ERROR,
"FATAL: Cannot create temp directory: %s", "FATAL: Cannot create temp directory: %s",
stk_tmp_dir); stk_tmp_dir);
return STK_INIT_TMPDIR_ERROR; return STK_INIT_TMPDIR_ERROR;
@@ -97,7 +160,7 @@ uint8_t stk_init(void)
files = platform_directory_init_scan(stk_mod_dir, &file_count); files = platform_directory_init_scan(stk_mod_dir, &file_count);
if (file_count > 0 && stk_module_init_memory(file_count) != 0) { if (file_count > 0 && stk_module_init_memory(file_count) != 0) {
stk_log(stderr, "FATAL: Memory allocation failed"); stk_log(STK_LOG_ERROR, "FATAL: Memory allocation failed");
return STK_INIT_MEMORY_ERROR; return STK_INIT_MEMORY_ERROR;
} }
@@ -110,41 +173,110 @@ uint8_t stk_init(void)
if (platform_copy_file(full_path, tmp_path) != if (platform_copy_file(full_path, tmp_path) !=
STK_PLATFORM_OPERATION_SUCCESS) { STK_PLATFORM_OPERATION_SUCCESS) {
stk_log(stderr, "Failed to copy %s to temp directory", stk_log(STK_LOG_ERROR,
"Failed to copy %s to temp directory",
files[i]); files[i]);
continue; continue;
} }
load_result = stk_module_load_init(tmp_path, successful_loads); load_result = stk_module_preload(tmp_path, successful_loads);
if (load_result != STK_MOD_INIT_SUCCESS) { if (load_result != STK_MOD_INIT_SUCCESS) {
stk_log(stderr, "Failed to load module %s: %s", stk_log(STK_LOG_ERROR,
files[i], stk_error_string(load_result)); "Failed to preload module %s: %s", files[i],
stk_error_string(load_result));
} else { } else {
successful_loads++; successful_loads++;
module_count++;
} }
} }
if (successful_loads < file_count) { if (successful_loads < file_count)
stk_module_realloc_memory(successful_loads); stk_module_realloc_memory(successful_loads);
}
free(files); free(files);
if (module_count == 0)
goto scanned;
order = malloc(module_count * sizeof(size_t));
if (order) {
dep_result = stk_topo_sort(module_count, order);
if (dep_result != STK_MOD_INIT_SUCCESS)
stk_log(STK_LOG_ERROR, "Dependency sort failed: %s",
stk_error_string(dep_result));
}
init_batch = malloc(module_count * sizeof(*init_batch));
for (j = 0; j < module_count; j++) {
index = order ? order[j] : j;
dep_result = stk_validate_dependencies_single(index);
if (dep_result != STK_MOD_INIT_SUCCESS) {
stk_log_dependency_failures(index, "Deferring");
if (init_batch) {
build_path(init_batch[init_batch_count],
sizeof(init_batch[init_batch_count]),
stk_tmp_dir, stk_modules[index].id);
strncat(
init_batch[init_batch_count],
STK_MODULE_EXT,
sizeof(init_batch[init_batch_count]) -
strlen(init_batch[init_batch_count]) -
1);
init_batch_count++;
}
stk_module_discard(index);
continue;
}
if (stk_module_activate(index) != STK_MOD_INIT_SUCCESS) {
stk_log(STK_LOG_ERROR, "Failed to init module %s",
stk_modules[index].id);
stk_module_discard(index);
}
}
if (init_batch_count > 0)
stk_pending_add_batch(
(const char (*)[STK_PATH_MAX_OS])init_batch,
init_batch_count);
free(init_batch);
init_batch = NULL;
if (order) {
free(order);
order = NULL;
}
write = 0;
for (j = 0; j < module_count; j++) {
if (stk_modules[j].handle != NULL) {
if (write != j)
stk_modules[write] = stk_modules[j];
write++;
}
}
module_count = write;
scanned: scanned:
watch_handle = platform_directory_watch_start(stk_mod_dir); watch_handle = platform_directory_watch_start(stk_mod_dir);
if (!watch_handle) { if (!watch_handle) {
stk_log(stderr, "FATAL: Cannot start directory watch on %s", stk_log(STK_LOG_ERROR,
"FATAL: Cannot start directory watch on %s",
stk_mod_dir); stk_mod_dir);
stk_module_unload_all(); stk_module_unload_all();
return STK_INIT_WATCH_ERROR; return STK_INIT_WATCH_ERROR;
} }
stk_log(stdout, "stk v%s initialized! Loaded %lu mod%s from %s/", stk_pending_retry();
STK_VERSION_STRING, module_count, module_count != 1 ? "s" : "",
stk_mod_dir);
stk_initialized = 1; stk_log(STK_LOG_INFO, "stk v%s initialized, watching %s/",
STK_VERSION_STRING, stk_mod_dir);
if (module_count > 0)
stk_log_modules();
stk_flags |= STK_FLAG_INITIALIZED;
return STK_INIT_SUCCESS; return STK_INIT_SUCCESS;
} }
@@ -159,12 +291,13 @@ void stk_shutdown(void)
if (platform_remove_dir(stk_tmp_dir) != if (platform_remove_dir(stk_tmp_dir) !=
STK_PLATFORM_OPERATION_SUCCESS) { STK_PLATFORM_OPERATION_SUCCESS) {
stk_log(stderr, "Warning: failed to remove temp directory %s", stk_log(STK_LOG_WARN,
"Warning: failed to remove temp directory %s",
stk_tmp_dir); stk_tmp_dir);
} }
stk_initialized = 0; stk_flags &= ~STK_FLAG_INITIALIZED;
stk_log(stdout, "stk shutdown"); stk_log(STK_LOG_INFO, "stk shutdown");
} }
size_t stk_poll(void) size_t stk_poll(void)
@@ -172,20 +305,52 @@ size_t stk_poll(void)
char (*file_list)[STK_PATH_MAX] = NULL; char (*file_list)[STK_PATH_MAX] = NULL;
stk_module_event_t *events = NULL; stk_module_event_t *events = NULL;
size_t i, file_count = 0, reload_count = 0, load_count = 0, size_t i, file_count = 0, reload_count = 0, load_count = 0,
unload_count = 0, reload_index = 0, load_index = 0, unload_count = 0;
unload_index = 0;
int *reloaded_mod_indices = NULL, *reloaded_mod_file_indices = NULL, int *reloaded_mod_indices = NULL, *reloaded_mod_file_indices = NULL,
*unloaded_mod_indices = NULL, *loaded_mod_indices = NULL; *unloaded_mod_indices = NULL, *loaded_mod_indices = NULL;
size_t remaining_loads, new_capacity, holes_to_fill; size_t remaining_loads, new_capacity, holes_to_fill;
size_t write_pos, read_pos;
char full_path[STK_PATH_MAX_OS], tmp_path[STK_PATH_MAX_OS]; char full_path[STK_PATH_MAX_OS], tmp_path[STK_PATH_MAX_OS];
char mod_id[STK_MOD_ID_BUFFER]; char mod_id[STK_MOD_ID_BUFFER];
int load_result; int load_result;
size_t successful_appends = 0; size_t successful_appends = 0;
char (*module_ids)[STK_MOD_ID_BUFFER] = NULL;
unsigned char dep_result;
size_t *order = NULL;
size_t *unload_order = NULL;
size_t expanded_count;
size_t index, oi;
int is_orig;
size_t write;
size_t li;
int fi;
int file_index, mod_index, target_index;
size_t cascade_indices[STK_PATH_MAX];
size_t cascade_count;
size_t j, k, cascade_write;
char (*dep_batch)[STK_PATH_MAX_OS] = NULL;
size_t dep_batch_count = 0;
char (*cascade_batch)[STK_PATH_MAX_OS] = NULL;
size_t cascade_batch_count = 0;
char (*load_batch)[STK_PATH_MAX_OS] = NULL;
size_t load_batch_count = 0;
if (module_count > 0) {
module_ids = malloc(module_count * sizeof(*module_ids));
if (module_ids) {
for (i = 0; i < module_count; i++) {
strncpy(module_ids[i], stk_modules[i].id,
STK_MOD_ID_BUFFER - 1);
module_ids[i][STK_MOD_ID_BUFFER - 1] = '\0';
}
}
}
events = platform_directory_watch_check(
watch_handle, &file_list, &file_count, module_ids, module_count);
if (module_ids)
free(module_ids);
events = platform_directory_watch_check(watch_handle, &file_list,
&file_count, stk_module_ids,
module_count);
if (!events) if (!events)
goto finish_poll; goto finish_poll;
@@ -208,20 +373,31 @@ size_t stk_poll(void)
unloaded_mod_indices = malloc(unload_count * sizeof(int)); unloaded_mod_indices = malloc(unload_count * sizeof(int));
loaded_mod_indices = malloc(load_count * sizeof(int)); loaded_mod_indices = malloc(load_count * sizeof(int));
reload_count = 0;
unload_count = 0;
load_count = 0;
for (i = 0; i < file_count; ++i) { for (i = 0; i < file_count; ++i) {
int mod_index;
extract_module_id(file_list[i], mod_id); extract_module_id(file_list[i], mod_id);
switch (events[i]) { switch (events[i]) {
case STK_MOD_LOAD: case STK_MOD_LOAD:
loaded_mod_indices[load_index++] = i; loaded_mod_indices[load_count++] = i;
break; break;
case STK_MOD_RELOAD: case STK_MOD_RELOAD:
reloaded_mod_file_indices[reload_index] = i; mod_index = is_mod_loaded(mod_id);
reloaded_mod_indices[reload_index++] = if (mod_index >= 0) {
is_mod_loaded(mod_id); reloaded_mod_file_indices[reload_count] = i;
reloaded_mod_indices[reload_count] = mod_index;
reload_count++;
}
break; break;
case STK_MOD_UNLOAD: case STK_MOD_UNLOAD:
unloaded_mod_indices[unload_index++] = mod_index = is_mod_loaded(mod_id);
is_mod_loaded(mod_id); if (mod_index >= 0) {
unloaded_mod_indices[unload_count] = mod_index;
unload_count++;
}
break; break;
} }
} }
@@ -234,94 +410,170 @@ size_t stk_poll(void)
handle_grow: handle_grow:
remaining_loads = load_count - unload_count; remaining_loads = load_count - unload_count;
new_capacity = module_count + remaining_loads; new_capacity = module_count + remaining_loads;
if (stk_module_realloc_memory(new_capacity) != STK_MOD_INIT_SUCCESS) { if (stk_module_realloc_memory(new_capacity) != STK_MOD_INIT_SUCCESS)
goto free_poll; goto free_poll;
}
begin_operations: begin_operations:
for (i = 0; i < unload_count; ++i) unload_order = malloc(module_count * sizeof(size_t));
stk_module_unload(unloaded_mod_indices[i]); if (unload_order) {
expanded_count = unload_count;
for (i = 0; i < reload_count; ++i) for (i = 0; i < unload_count; i++)
stk_module_unload(reloaded_mod_indices[i]); unload_order[i] = (size_t)unloaded_mod_indices[i];
stk_collect_dependents(unload_order, &expanded_count);
stk_sort_unload_order(unload_order, expanded_count);
dep_batch = malloc(expanded_count * sizeof(*dep_batch));
dep_batch_count = 0;
for (i = 0; i < expanded_count; i++) {
index = unload_order[i];
stk_log(STK_LOG_INFO, "Unloaded module: %s",
stk_modules[index].id);
stk_pending_remove(stk_modules[index].id);
is_orig = 0;
for (oi = 0; oi < unload_count; oi++) {
if ((size_t)unloaded_mod_indices[oi] == index) {
is_orig = 1;
break;
}
}
if (!is_orig && dep_batch) {
build_path(dep_batch[dep_batch_count],
sizeof(dep_batch[dep_batch_count]),
stk_tmp_dir, stk_modules[index].id);
strncat(
dep_batch[dep_batch_count], STK_MODULE_EXT,
sizeof(dep_batch[dep_batch_count]) -
strlen(dep_batch[dep_batch_count]) - 1);
dep_batch_count++;
}
stk_module_unload(index);
}
if (dep_batch_count > 0)
stk_pending_add_batch(
(const char (*)[STK_PATH_MAX_OS])dep_batch,
dep_batch_count);
free(dep_batch);
dep_batch = NULL;
free(unload_order);
unload_order = NULL;
} else {
for (i = 0; i < unload_count; ++i) {
stk_log(STK_LOG_INFO, "Unloaded module: %s",
stk_modules[unloaded_mod_indices[i]].id);
stk_pending_remove(
stk_modules[unloaded_mod_indices[i]].id);
stk_module_unload(unloaded_mod_indices[i]);
}
}
if (unload_count > 0) {
write = 0;
for (i = 0; i < module_count; i++) {
if (stk_modules[i].handle != NULL) {
if (write != i)
stk_modules[write] = stk_modules[i];
write++;
}
}
module_count = write;
if (module_count > 0)
stk_module_realloc_memory(module_count);
}
for (i = 0; i < reload_count; ++i) { for (i = 0; i < reload_count; ++i) {
int file_index = reloaded_mod_file_indices[i]; file_index = reloaded_mod_file_indices[i];
int mod_index = reloaded_mod_indices[i]; mod_index = reloaded_mod_indices[i];
build_path(full_path, sizeof(full_path), stk_mod_dir, build_path(full_path, sizeof(full_path), stk_mod_dir,
file_list[file_index]); file_list[file_index]);
build_path(tmp_path, sizeof(tmp_path), stk_tmp_dir, build_path(tmp_path, sizeof(tmp_path), stk_tmp_dir,
file_list[file_index]); file_list[file_index]);
stk_module_unload(mod_index);
if (platform_copy_file(full_path, tmp_path) != if (platform_copy_file(full_path, tmp_path) !=
STK_PLATFORM_OPERATION_SUCCESS) { STK_PLATFORM_OPERATION_SUCCESS) {
stk_log(stderr, "Failed to copy %s for reload", stk_log(STK_LOG_ERROR, "Failed to copy %s for reload",
file_list[file_index]); file_list[file_index]);
continue; continue;
} }
load_result = stk_module_load(tmp_path, mod_index); load_result = stk_module_load(tmp_path, mod_index);
if (load_result != STK_MOD_INIT_SUCCESS) { if (load_result != STK_MOD_INIT_SUCCESS)
stk_log(stderr, "Failed to reload module %s: %s", stk_log(STK_LOG_ERROR, "Failed to reload module %s: %s",
file_list[file_index], file_list[file_index],
stk_error_string(load_result)); stk_error_string(load_result));
}
} }
holes_to_fill = (load_count < unload_count) ? load_count : unload_count; holes_to_fill = (load_count < unload_count) ? load_count : unload_count;
for (i = 0; i < holes_to_fill; ++i) {
int target_index = unloaded_mod_indices[i];
int file_index = loaded_mod_indices[i];
for (li = 0; li < load_count; li++) {
fi = loaded_mod_indices[li];
build_path(full_path, sizeof(full_path), stk_mod_dir, build_path(full_path, sizeof(full_path), stk_mod_dir,
file_list[file_index]); file_list[fi]);
build_path(tmp_path, sizeof(tmp_path), stk_tmp_dir,
file_list[fi]);
platform_copy_file(full_path, tmp_path);
}
if (load_count > 1)
stk_sort_load_order(loaded_mod_indices, load_count, file_list,
stk_tmp_dir);
load_batch = malloc(load_count * sizeof(*load_batch));
load_batch_count = 0;
for (i = 0; i < holes_to_fill; ++i) {
target_index = unloaded_mod_indices[i];
file_index = loaded_mod_indices[i];
build_path(tmp_path, sizeof(tmp_path), stk_tmp_dir, build_path(tmp_path, sizeof(tmp_path), stk_tmp_dir,
file_list[file_index]); file_list[file_index]);
if (platform_copy_file(full_path, tmp_path) !=
STK_PLATFORM_OPERATION_SUCCESS) {
stk_log(stderr, "Failed to copy %s for loading",
file_list[file_index]);
continue;
}
load_result = stk_module_load(tmp_path, target_index); load_result = stk_module_load(tmp_path, target_index);
if (load_result != STK_MOD_INIT_SUCCESS) { if (load_result == STK_MOD_DEP_NOT_FOUND_ERROR ||
stk_log(stderr, "Failed to load module %s: %s", load_result == STK_MOD_DEP_VERSION_MISMATCH_ERROR) {
if (load_batch)
memcpy(load_batch[load_batch_count++], tmp_path,
STK_PATH_MAX_OS);
} else if (load_result != STK_MOD_INIT_SUCCESS) {
stk_log(STK_LOG_ERROR, "Failed to load module %s: %s",
file_list[file_index], file_list[file_index],
stk_error_string(load_result)); stk_error_string(load_result));
} else {
module_count++;
} }
} }
if (load_count > unload_count) if (load_count > unload_count)
goto append_modules; goto append_modules;
if (unload_count > load_count) goto finish_loads;
goto trim_arrays;
goto free_poll;
append_modules: append_modules:
for (; i < load_count; ++i) { for (; i < load_count; ++i) {
int file_index = loaded_mod_indices[i]; file_index = loaded_mod_indices[i];
build_path(full_path, sizeof(full_path), stk_mod_dir,
file_list[file_index]);
build_path(tmp_path, sizeof(tmp_path), stk_tmp_dir, build_path(tmp_path, sizeof(tmp_path), stk_tmp_dir,
file_list[file_index]); file_list[file_index]);
if (platform_copy_file(full_path, tmp_path) !=
STK_PLATFORM_OPERATION_SUCCESS) {
stk_log(stderr, "Failed to copy %s for loading",
file_list[file_index]);
continue;
}
load_result = stk_module_load(tmp_path, module_count + load_result = stk_module_load(tmp_path, module_count +
successful_appends); successful_appends);
if (load_result != STK_MOD_INIT_SUCCESS) { if (load_result == STK_MOD_DEP_NOT_FOUND_ERROR ||
stk_log(stderr, "Failed to load module %s: %s", load_result == STK_MOD_DEP_VERSION_MISMATCH_ERROR) {
if (load_batch)
memcpy(load_batch[load_batch_count++], tmp_path,
STK_PATH_MAX_OS);
} else if (load_result != STK_MOD_INIT_SUCCESS) {
stk_log(STK_LOG_ERROR, "Failed to load module %s: %s",
file_list[file_index], file_list[file_index],
stk_error_string(load_result)); stk_error_string(load_result));
} else { } else {
@@ -331,34 +583,104 @@ append_modules:
module_count += successful_appends; module_count += successful_appends;
if (successful_appends < (load_count - holes_to_fill)) { if (successful_appends < (load_count - holes_to_fill))
stk_module_realloc_memory(module_count); stk_module_realloc_memory(module_count);
}
goto free_poll; finish_loads:
if (load_batch_count > 0)
stk_pending_add_batch(
(const char (*)[STK_PATH_MAX_OS])load_batch,
load_batch_count);
trim_arrays: free(load_batch);
write_pos = unloaded_mod_indices[holes_to_fill]; load_batch = NULL;
for (i = holes_to_fill + 1; i < unload_count; ++i) {
if (unloaded_mod_indices[i] < write_pos)
write_pos = unloaded_mod_indices[i];
}
for (read_pos = write_pos + 1; read_pos < module_count; ++read_pos) { goto validate_deps;
if (stk_handles[read_pos] != NULL) {
stk_handles[write_pos] = stk_handles[read_pos]; validate_deps:
stk_inits[write_pos] = stk_inits[read_pos]; if (module_count == 0)
stk_shutdowns[write_pos] = stk_shutdowns[read_pos]; goto free_poll;
memcpy(stk_module_ids[write_pos],
stk_module_ids[read_pos], STK_MOD_ID_BUFFER); do {
++write_pos; cascade_count = 0;
for (j = 0; j < module_count; j++) {
if (stk_modules[j].dep_count == 0)
continue;
for (k = 0; k < stk_modules[j].dep_count; k++) {
if (is_mod_loaded(stk_modules[j].deps[k].id) <
0) {
cascade_indices[cascade_count++] = j;
break;
}
}
} }
}
module_count = write_pos; if (cascade_count == 0)
stk_module_realloc_memory(module_count); break;
cascade_batch = malloc(cascade_count * sizeof(*cascade_batch));
cascade_batch_count = 0;
for (j = 0; j < cascade_count; j++) {
index = cascade_indices[j];
stk_log_dependency_failures(index, "Unloading");
if (cascade_batch) {
build_path(
cascade_batch[cascade_batch_count],
sizeof(cascade_batch[cascade_batch_count]),
stk_tmp_dir, stk_modules[index].id);
strncat(
cascade_batch[cascade_batch_count],
STK_MODULE_EXT,
sizeof(cascade_batch[cascade_batch_count]) -
strlen(cascade_batch
[cascade_batch_count]) -
1);
cascade_batch_count++;
}
stk_module_unload(index);
}
if (cascade_batch_count > 0)
stk_pending_add_batch(
(const char (*)[STK_PATH_MAX_OS])cascade_batch,
cascade_batch_count);
free(cascade_batch);
cascade_batch = NULL;
cascade_write = 0;
for (j = 0; j < module_count; j++) {
if (stk_modules[j].handle != NULL) {
if (cascade_write != j)
stk_modules[cascade_write] =
stk_modules[j];
cascade_write++;
}
}
module_count = cascade_write;
} while (cascade_count > 0);
if (module_count > 0)
stk_module_realloc_memory(module_count);
order = malloc(module_count * sizeof(size_t));
if (order) {
dep_result = stk_topo_sort(module_count, order);
if (dep_result != STK_MOD_INIT_SUCCESS)
stk_log(STK_LOG_ERROR, "Dependency sort failed: %s",
stk_error_string(dep_result));
free(order);
}
free_poll: free_poll:
stk_pending_retry();
if (module_count > 0)
stk_log_modules();
free(reloaded_mod_indices); free(reloaded_mod_indices);
free(reloaded_mod_file_indices); free(reloaded_mod_file_indices);
free(unloaded_mod_indices); free(unloaded_mod_indices);
@@ -372,7 +694,7 @@ finish_poll:
void stk_set_mod_dir(const char *path) void stk_set_mod_dir(const char *path)
{ {
if (!path || stk_initialized) if (!path || (stk_flags & STK_FLAG_INITIALIZED))
return; return;
strncpy(stk_mod_dir, path, STK_PATH_MAX_OS - 1); strncpy(stk_mod_dir, path, STK_PATH_MAX_OS - 1);
@@ -390,7 +712,7 @@ void stk_set_mod_dir(const char *path)
void stk_set_tmp_dir_name(const char *name) void stk_set_tmp_dir_name(const char *name)
{ {
if (!name || stk_initialized) if (!name || (stk_flags & STK_FLAG_INITIALIZED))
return; return;
strncpy(stk_tmp_name, name, STK_MOD_ID_BUFFER - 1); strncpy(stk_tmp_name, name, STK_MOD_ID_BUFFER - 1);
@@ -403,3 +725,16 @@ void stk_set_tmp_dir_name(const char *name)
strncat(stk_tmp_dir, stk_tmp_name, strncat(stk_tmp_dir, stk_tmp_name,
STK_PATH_MAX_OS - strlen(stk_tmp_dir) - 1); STK_PATH_MAX_OS - strlen(stk_tmp_dir) - 1);
} }
void stk_set_logging_enabled(unsigned char enabled)
{
if (enabled)
stk_flags |= STK_FLAG_LOGGING_ENABLED;
else
stk_flags &= ~STK_FLAG_LOGGING_ENABLED;
}
unsigned char stk_is_logging_enabled(void)
{
return (stk_flags & STK_FLAG_LOGGING_ENABLED) != 0;
}
+77 -5
View File
@@ -1,14 +1,86 @@
#include "stk_log.h" #include "stk_log.h"
#include "stk.h"
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <string.h>
void stk_log(FILE *fp, const char *fmt, ...) #define STK_LOG_TIMESTAMP_BUFFER 32
extern unsigned char stk_flags;
void platform_get_timestamp(char *buffer, size_t size);
static FILE *log_output = NULL;
static char log_prefix[STK_LOG_PREFIX_BUFFER] = "stk";
static stk_log_level_t min_log_level = STK_LOG_INFO;
static const char *get_level_string(stk_log_level_t level)
{ {
va_list args; char *level_str = "";
va_start(args, fmt); switch (level) {
case STK_LOG_ERROR:
level_str = "ERROR";
break;
case STK_LOG_WARN:
level_str = "WARN";
break;
case STK_LOG_INFO:
level_str = "INFO";
break;
case STK_LOG_DEBUG:
level_str = "DEBUG";
break;
}
vfprintf(fp, fmt, args); return level_str;
fputc('\n', fp); }
void stk_set_log_output(FILE *fp)
{
if (fp == NULL)
stk_flags &= ~STK_FLAG_LOGGING_ENABLED;
log_output = fp;
}
void stk_set_log_prefix(const char *prefix)
{
if (!prefix) {
log_prefix[0] = '\0';
return;
}
strncpy(log_prefix, prefix, STK_LOG_PREFIX_BUFFER - 1);
log_prefix[STK_LOG_PREFIX_BUFFER - 1] = '\0';
}
void stk_set_log_level(stk_log_level_t level) { min_log_level = level; }
void stk_log(stk_log_level_t level, const char *fmt, ...)
{
FILE *output;
const char *level_str;
char timestamp[STK_LOG_TIMESTAMP_BUFFER];
va_list args;
if (!(stk_flags & STK_FLAG_LOGGING_ENABLED))
return;
if (level < min_log_level)
return;
output = log_output ? log_output : stdout;
level_str = get_level_string(level);
platform_get_timestamp(timestamp, sizeof(timestamp));
if (log_prefix[0] != '\0')
fprintf(output, "%s [%s] [%s] ", timestamp, log_prefix,
level_str);
else
fprintf(output, "%s [%s] ", timestamp, level_str);
va_start(args, fmt);
vfprintf(output, fmt, args);
va_end(args); va_end(args);
fputc('\n', output);
} }
+9 -5
View File
@@ -22,23 +22,27 @@ test_program: test.c
test_mod$(MODULE_EXT): test_mod.c test_mod$(MODULE_EXT): test_mod.c
$(CC) $(CFLAGS) -fPIC -shared -o $@ test_mod.c $(CC) $(CFLAGS) -fPIC -shared -o $@ test_mod.c
test_mod_dep$(MODULE_EXT): test_mod_dep.c
$(CC) $(CFLAGS) -fPIC -shared -o $@ test_mod_dep.c
setup: setup:
@mkdir -p mods @mkdir -p mods
@cp -f test_mod$(MODULE_EXT) mods/ 2>/dev/null || true @cp -f test_mod$(MODULE_EXT) mods/ 2>/dev/null || true
@echo "Test environment ready: mods/ directory with test_mod$(MODULE_EXT)" @cp -f test_mod_dep$(MODULE_EXT) mods/ 2>/dev/null || true
@echo "Test environment ready: mods/ directory with test modules"
run: test_program test_mod$(MODULE_EXT) setup run: test_program test_mod$(MODULE_EXT) test_mod_dep$(MODULE_EXT) setup
@echo "Running integration test (CTRL+C to exit)..." @echo "Running integration test (CTRL+C to exit)..."
@./test_program @./test_program
test: test_program test_mod$(MODULE_EXT) setup test: test_program test_mod$(MODULE_EXT) test_mod_dep$(MODULE_EXT) setup
@echo "=== stk Integration Test ===" @echo "=== stk Integration Test ==="
@echo "1. Starting test program" @echo "1. Starting test program"
@echo "2. Will load test_mod$(MODULE_EXT) from mods/" @echo "2. Will load test_mod and test_mod_dep from mods/"
@echo "3. Press CTRL+C to exit" @echo "3. Press CTRL+C to exit"
@echo "=============================" @echo "============================="
@./test_program || echo "Test completed." @./test_program || echo "Test completed."
clean: clean:
rm -f test_program test_mod$(MODULE_EXT) rm -f test_program test_mod$(MODULE_EXT) test_mod_dep$(MODULE_EXT)
rm -rf mods/ rm -rf mods/
+10 -3
View File
@@ -3,6 +3,8 @@ CFLAGS = -Wall -Wpedantic -I../include -std=c89
LDFLAGS = -L../bin/debug -lstk LDFLAGS = -L../bin/debug -lstk
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
SHELL := cmd.exe
.SHELLFLAGS := /c
MODULE_EXT = .dll MODULE_EXT = .dll
EXE_EXT = .exe EXE_EXT = .exe
else else
@@ -21,16 +23,21 @@ test_program$(EXE_EXT): test.c
test_mod$(MODULE_EXT): test_mod.c test_mod$(MODULE_EXT): test_mod.c
$(CC) $(CFLAGS) -fPIC -shared -o $@ test_mod.c $(CC) $(CFLAGS) -fPIC -shared -o $@ test_mod.c
test_mod_dep$(MODULE_EXT): test_mod_dep.c
$(CC) $(CFLAGS) -fPIC -shared -o $@ test_mod_dep.c
setup: setup:
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
@if not exist mods mkdir mods @if not exist mods mkdir mods
@if exist test_mod.dll copy /Y test_mod.dll mods\ >nul 2>&1 @if exist test_mod.dll copy /Y test_mod.dll mods\ >nul 2>&1
@if exist test_mod_dep.dll copy /Y test_mod_dep.dll mods\ >nul 2>&1
else else
@mkdir -p mods @mkdir -p mods
@cp -f test_mod.so mods/ 2>/dev/null || true @cp -f test_mod.so mods/ 2>/dev/null || true
@cp -f test_mod_dep.so mods/ 2>/dev/null || true
endif endif
test: test_program$(EXE_EXT) test_mod$(MODULE_EXT) setup test: test_program$(EXE_EXT) test_mod$(MODULE_EXT) test_mod_dep$(MODULE_EXT) setup
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
@set PATH=../bin/debug;%PATH% && cmd /C "test_program.exe" @set PATH=../bin/debug;%PATH% && cmd /C "test_program.exe"
else else
@@ -39,9 +46,9 @@ endif
clean: clean:
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
@del /Q test_program.exe test_mod.dll 2>nul || true @del /Q test_program.exe test_mod.dll test_mod_dep.dll 2>nul || true
@rmdir /S /Q mods 2>nul || true @rmdir /S /Q mods 2>nul || true
else else
@rm -f test_program test_mod.so @rm -f test_program test_mod.so test_mod_dep.so
@rm -rf mods @rm -rf mods
endif endif
+5 -4
View File
@@ -31,8 +31,8 @@ void inthand(int signum)
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
uint8_t init_result; unsigned char init_result;
uint64_t iterations = 0; size_t iterations = 0;
printf("stk test - CTRL+C to exit\n"); printf("stk test - CTRL+C to exit\n");
@@ -55,12 +55,13 @@ int main(int argc, char **argv)
while (!stop) { while (!stop) {
size_t events = stk_poll(); size_t events = stk_poll();
if (events > 0) if (events > 0)
printf("Poll: %lu module event(s) detected\n", (unsigned long) events); printf("Poll: %lu module event(s) detected\n",
(unsigned long)events);
iterations++; iterations++;
if (iterations % 5 == 0) { if (iterations % 5 == 0) {
printf("Still running... (iteration %lu)\n", printf("Still running... (iteration %lu)\n",
(unsigned long) iterations); (unsigned long)iterations);
} }
#ifdef _WIN32 #ifdef _WIN32
Sleep(1000); Sleep(1000);
+5 -2
View File
@@ -2,8 +2,11 @@
int stk_mod_init(void) int stk_mod_init(void)
{ {
printf("test mod initialized!\n"); printf("test_mod initialized!\n");
return 0; return 0;
} }
void stk_mod_shutdown(void) { printf("test mod shut down.\n"); } void stk_mod_shutdown(void) { printf("test_mod shut down.\n"); }
const char *stk_mod_name(void) { return "Test Module"; }
const char *stk_mod_version(void) { return "1.0.0"; }
+19
View File
@@ -0,0 +1,19 @@
#include <stdio.h>
typedef struct {
char id[64];
char version[32];
} dep_t;
int stk_mod_init(void)
{
printf("test_mod_dep initialized!\n");
return 0;
}
void stk_mod_shutdown(void) { printf("test_mod_dep shut down.\n"); }
const char *stk_mod_name(void) { return "Dependent Test Module"; }
const char *stk_mod_version(void) { return "1.0.0"; }
dep_t stk_mod_deps[] = {{"test_mod", ">=1.0.0"}, {"", ""}};