28 Commits

Author SHA1 Message Date
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
12 changed files with 959 additions and 158 deletions
+69 -2
View File
@@ -7,7 +7,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.1.0] - 2026-02-15 ## [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 ### Fixed
- **C89 Compliance**: Removed stdint.h dependency (C99 feature) - **C89 Compliance**: Removed stdint.h dependency (C99 feature)
@@ -84,7 +145,13 @@ 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.1.0...HEAD [Unreleased]: https://github.com/anth64/stk/compare/v1.0.0-pre.2...HEAD
[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.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.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.3]: https://github.com/anth64/stk/releases/tag/v0.0.3
+60 -6
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.
@@ -168,6 +216,10 @@ 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 #### Logging
- `void stk_set_logging_enabled(unsigned char enabled)` - Enable/disable all logging - `void stk_set_logging_enabled(unsigned char enabled)` - Enable/disable all logging
@@ -182,9 +234,7 @@ stk_init();
## Project Status ## Project Status
**Current Version:** 0.1.0 (Pre-release) **Current Version:** 1.0.0-pre.1
This release brings C89 compliance fixes and a complete logging system rewrite with levels, timestamps, and runtime configuration.
### What Works ### What Works
- Cross-platform module loading and hot-reloading - Cross-platform module loading and hot-reloading
@@ -192,10 +242,14 @@ This release brings C89 compliance fixes and a complete logging system rewrite w
- Robust hot-reload even during extremely rapid file changes - Robust hot-reload even during extremely rapid file changes
- Enhanced logging with levels, timestamps, and filtering - Enhanced logging with levels, timestamps, and filtering
- Runtime-configurable logging behavior - Runtime-configurable logging behavior
- Optional module metadata (name, version, description)
- Dependency declaration, validation, and versioning
- 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
- Module metadata (name, version, description) - WASM module support
- Dependency management and versioning
See [CHANGELOG.md](CHANGELOG.md) for detailed release notes. See [CHANGELOG.md](CHANGELOG.md) for detailed release notes.
+21
View File
@@ -6,8 +6,12 @@
/* Buffers */ /* Buffers */
#define STK_LOG_PREFIX_BUFFER 64 #define STK_LOG_PREFIX_BUFFER 64
#define STK_MOD_DEP_OPERATOR_BUFFER 3
#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 +27,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
@@ -35,6 +42,11 @@
#define STK_FLAG_INITIALIZED 0x01 #define STK_FLAG_INITIALIZED 0x01
#define STK_FLAG_LOGGING_ENABLED 0x02 #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
@@ -59,6 +71,11 @@ typedef enum {
STK_MOD_RELOAD STK_MOD_RELOAD
} stk_module_event_t; } stk_module_event_t;
typedef struct {
char id[STK_MOD_ID_BUFFER];
char version[STK_MOD_VERSION_BUFFER];
} stk_dep_t;
unsigned char stk_init(void); 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);
@@ -68,6 +85,10 @@ 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_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); unsigned char stk_is_logging_enabled(void);
#ifdef __cplusplus #ifdef __cplusplus
+3 -3
View File
@@ -8,10 +8,10 @@ extern "C" {
#endif #endif
typedef enum { typedef enum {
STK_LOG_ERROR, STK_LOG_DEBUG,
STK_LOG_WARN,
STK_LOG_INFO, STK_LOG_INFO,
STK_LOG_DEBUG STK_LOG_WARN,
STK_LOG_ERROR
} stk_log_level_t; } stk_log_level_t;
void stk_set_log_output(FILE *fp); void stk_set_log_output(FILE *fp);
+2 -2
View File
@@ -1,8 +1,8 @@
#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 1 #define STK_VERSION_MINOR 0
#define STK_VERSION_PATCH 0 #define STK_VERSION_PATCH 0
#define STK_STRINGIFY_HELPER(x) #x #define STK_STRINGIFY_HELPER(x) #x
+534 -101
View File
@@ -1,5 +1,6 @@
#include "platform.h" #include "platform.h"
#include "stk.h" #include "stk.h"
#include "stk_log.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -8,23 +9,115 @@
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);
typedef struct {
unsigned char major;
unsigned char minor;
unsigned char patch;
char op;
} stk_version_t;
typedef struct {
char desc[STK_MOD_DESC_BUFFER];
char name[STK_MOD_NAME_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;
void *platform_load_library(const char *path); void *platform_load_library(const char *path);
void platform_unload_library(void *handle); void platform_unload_library(void *handle);
void *platform_get_symbol(void *handle, const char *symbol); void *platform_get_symbol(void *handle, const char *symbol);
char (*stk_module_ids)[STK_MOD_ID_BUFFER] = NULL; stk_mod_t *stk_modules = NULL;
void **stk_handles = NULL;
stk_init_mod_func *stk_inits = NULL;
stk_shutdown_mod_func *stk_shutdowns = NULL;
extern unsigned char stk_flags; extern unsigned char stk_flags;
static char stk_mod_init_name[STK_MOD_FUNC_NAME_BUFFER] = "stk_mod_init"; static char stk_mod_init_name[STK_MOD_FUNC_NAME_BUFFER] = "stk_mod_init";
static char stk_mod_shutdown_name[STK_MOD_FUNC_NAME_BUFFER] = static char stk_mod_shutdown_name[STK_MOD_FUNC_NAME_BUFFER] =
"stk_mod_shutdown"; "stk_mod_shutdown";
static char stk_mod_name_fn[STK_MOD_FUNC_NAME_BUFFER] = "stk_mod_name";
static char stk_mod_version_fn[STK_MOD_FUNC_NAME_BUFFER] = "stk_mod_version";
static char stk_mod_description_fn[STK_MOD_FUNC_NAME_BUFFER] =
"stk_mod_description";
static char stk_mod_deps_sym[STK_MOD_FUNC_NAME_BUFFER] = "stk_mod_deps";
size_t module_count = 0; size_t module_count = 0;
static char (*stk_pending)[STK_PATH_MAX_OS] = NULL;
static size_t stk_pending_count = 0;
void stk_pending_free(void)
{
if (stk_pending) {
free(stk_pending);
stk_pending = NULL;
}
stk_pending_count = 0;
}
static stk_version_t stk_parse_version(const char *str)
{
stk_version_t v;
v.major = 0;
v.minor = 0;
v.patch = 0;
v.op = '>';
if (!str || !*str)
return v;
if (*str == '=' && *(str + 1) != '=') {
v.op = '=';
str++;
} else if (*str == '>' && *(str + 1) == '=') {
v.op = '>';
str += 2;
} else if (*str == '^') {
v.op = '^';
str++;
}
v.major = (unsigned char)strtol(str, (char **)&str, 10);
if (*str == '.')
str++;
v.minor = (unsigned char)strtol(str, (char **)&str, 10);
if (*str == '.')
str++;
v.patch = (unsigned char)strtol(str, NULL, 10);
return v;
}
static int stk_compare_version(stk_version_t a, stk_version_t b)
{
if (a.major != b.major)
return a.major - b.major;
if (a.minor != b.minor)
return a.minor - b.minor;
return a.patch - b.patch;
}
static int stk_validate_constraint(const char *constraint, const char *loaded)
{
stk_version_t req = stk_parse_version(constraint);
stk_version_t have = stk_parse_version(loaded);
int cmp = stk_compare_version(have, req);
switch (req.op) {
case '=':
return cmp == 0;
case '^':
return have.major == req.major && cmp >= 0;
default:
return cmp >= 0;
}
}
size_t stk_module_count(void) { return module_count; } size_t stk_module_count(void) { return module_count; }
void extract_module_id(const char *path, char *out_id) void extract_module_id(const char *path, char *out_id)
@@ -64,59 +157,290 @@ int is_mod_loaded(const char *module_name)
size_t i; size_t i;
for (i = 0; i < module_count; i++) for (i = 0; i < module_count; i++)
if (strncmp(stk_module_ids[i], module_name, if (strncmp(stk_modules[i].id, module_name,
STK_MOD_ID_BUFFER) == 0) STK_MOD_ID_BUFFER) == 0)
return i; return i;
return -1; return -1;
} }
unsigned char stk_validate_dependencies(size_t count)
{
size_t i, d;
int found;
unsigned char result = STK_MOD_INIT_SUCCESS;
for (i = 0; i < count; i++) {
if (stk_modules[i].dep_count == 0)
continue;
for (d = 0; d < stk_modules[i].dep_count; d++) {
found = is_mod_loaded(stk_modules[i].deps[d].id);
if (found < 0) {
stk_log(STK_LOG_ERROR,
"Module '%s' requires '%s'",
stk_modules[i].id,
stk_modules[i].deps[d].id);
result = STK_MOD_DEP_NOT_FOUND_ERROR;
continue;
}
if (!stk_modules[i].deps[d].version[0])
continue;
if (!stk_validate_constraint(
stk_modules[i].deps[d].version,
stk_modules[found].version)) {
stk_log(
STK_LOG_ERROR,
"Module '%s' requires '%s' %s but has %s",
stk_modules[i].id,
stk_modules[i].deps[d].id,
stk_modules[i].deps[d].version,
stk_modules[found].version);
result = STK_MOD_DEP_VERSION_MISMATCH_ERROR;
}
}
}
return result;
}
unsigned char stk_topo_sort(size_t count, size_t *order)
{
size_t *in_degree = NULL;
size_t *queue = NULL;
size_t head, tail, sorted, i, d;
int dep_index;
unsigned char result = STK_MOD_INIT_SUCCESS;
if (count == 0)
goto done;
in_degree = malloc(count * sizeof(size_t));
queue = malloc(count * sizeof(size_t));
if (!in_degree || !queue) {
result = STK_MOD_REALLOC_FAILURE;
goto done;
}
for (i = 0; i < count; i++)
in_degree[i] = 0;
for (i = 0; i < count; i++)
for (d = 0; d < stk_modules[i].dep_count; d++) {
dep_index = is_mod_loaded(stk_modules[i].deps[d].id);
if (dep_index >= 0)
in_degree[i]++;
}
head = tail = sorted = 0;
for (i = 0; i < count; i++)
if (in_degree[i] == 0)
queue[tail++] = i;
while (head < tail) {
size_t mod = queue[head++];
order[sorted++] = mod;
for (i = 0; i < count; i++) {
for (d = 0; d < stk_modules[i].dep_count; d++) {
dep_index =
is_mod_loaded(stk_modules[i].deps[d].id);
if (dep_index != (int)mod)
continue;
if (--in_degree[i] == 0)
queue[tail++] = i;
break;
}
}
}
if (sorted != count) {
size_t j;
int in_order;
for (i = 0; i < count; i++) {
in_order = 0;
for (j = 0; j < sorted; j++) {
if (order[j] == i) {
in_order = 1;
break;
}
}
if (!in_order)
stk_log(STK_LOG_ERROR,
"Circular dependency detected with %s",
stk_modules[i].id);
}
result = STK_MOD_DEP_CIRCULAR_ERROR;
}
done:
if (in_degree)
free(in_degree);
if (queue)
free(queue);
return result;
}
unsigned char stk_module_load(const char *path, int index) unsigned char stk_module_load(const char *path, int index)
{ {
void *handle; void *handle;
stk_init_mod_func init_func;
stk_shutdown_mod_func shutdown_func;
char module_id[STK_MOD_ID_BUFFER]; char module_id[STK_MOD_ID_BUFFER];
size_t len;
union { union {
void *obj; void *obj;
stk_init_mod_func init_func; stk_init_mod_func init_func;
stk_shutdown_mod_func shutdown_func; stk_shutdown_mod_func shutdown_func;
const char *(*meta_func)(void);
} u; } u;
size_t len; const char *meta_str;
const stk_dep_t *deps;
size_t dep_count;
stk_dep_t *dep_arr;
handle = platform_load_library(path); handle = platform_load_library(path);
if (!handle) if (!handle)
return STK_MOD_LIBRARY_LOAD_ERROR; return STK_MOD_LIBRARY_LOAD_ERROR;
u.obj = platform_get_symbol(handle, stk_mod_init_name); u.obj = platform_get_symbol(handle, stk_mod_init_name);
init_func = u.init_func; if (!u.obj) {
u.obj = platform_get_symbol(handle, stk_mod_shutdown_name);
shutdown_func = u.shutdown_func;
if (!init_func || !shutdown_func) {
platform_unload_library(handle); platform_unload_library(handle);
return STK_MOD_SYMBOL_NOT_FOUND_ERROR; return STK_MOD_SYMBOL_NOT_FOUND_ERROR;
} }
stk_modules[index].init = u.init_func;
u.obj = platform_get_symbol(handle, stk_mod_shutdown_name);
if (!u.obj) {
platform_unload_library(handle);
return STK_MOD_SYMBOL_NOT_FOUND_ERROR;
}
stk_modules[index].shutdown = u.shutdown_func;
extract_module_id(path, module_id); extract_module_id(path, module_id);
if (init_func() != STK_MOD_INIT_SUCCESS) { u.obj = platform_get_symbol(handle, stk_mod_deps_sym);
if (u.obj) {
const stk_dep_t *early_deps = (const stk_dep_t *)u.obj;
size_t early_dep_count = 0;
size_t di;
int found;
while (early_deps[early_dep_count].id[0] != '\0')
early_dep_count++;
for (di = 0; di < early_dep_count; di++) {
found = is_mod_loaded(early_deps[di].id);
if (found < 0) {
platform_unload_library(handle);
return STK_MOD_DEP_NOT_FOUND_ERROR;
}
if (early_deps[di].version[0] &&
!stk_validate_constraint(
early_deps[di].version,
stk_modules[found].version)) {
platform_unload_library(handle);
return STK_MOD_DEP_VERSION_MISMATCH_ERROR;
}
}
}
if (stk_modules[index].init() != STK_MOD_INIT_SUCCESS) {
platform_unload_library(handle); platform_unload_library(handle);
return STK_MOD_INIT_FAILURE; return STK_MOD_INIT_FAILURE;
} }
stk_modules[index].handle = handle;
len = strlen(module_id); len = strlen(module_id);
if (len >= STK_MOD_ID_BUFFER) if (len >= STK_MOD_ID_BUFFER)
len = STK_MOD_ID_BUFFER - 1; len = STK_MOD_ID_BUFFER - 1;
memcpy(stk_modules[index].id, module_id, len);
stk_modules[index].id[len] = '\0';
memcpy(stk_module_ids[index], module_id, len); stk_modules[index].name[0] = '\0';
stk_module_ids[index][len] = '\0'; u.obj = platform_get_symbol(handle, stk_mod_name_fn);
if (u.obj) {
meta_str = u.meta_func();
if (meta_str) {
strncpy(stk_modules[index].name, meta_str,
STK_MOD_NAME_BUFFER - 1);
stk_modules[index].name[STK_MOD_NAME_BUFFER - 1] = '\0';
}
}
stk_handles[index] = handle; stk_modules[index].version[0] = '\0';
stk_inits[index] = init_func; u.obj = platform_get_symbol(handle, stk_mod_version_fn);
stk_shutdowns[index] = shutdown_func; if (u.obj) {
meta_str = u.meta_func();
if (meta_str) {
stk_version_t v = stk_parse_version(meta_str);
if (v.major == 0 && v.minor == 0 && v.patch == 0 &&
meta_str[0] != '0') {
strncpy(stk_modules[index].version, "0.0.0",
STK_MOD_VERSION_BUFFER - 1);
} else {
strncpy(stk_modules[index].version, meta_str,
STK_MOD_VERSION_BUFFER - 1);
}
stk_modules[index].version[STK_MOD_VERSION_BUFFER - 1] =
'\0';
}
}
if (!stk_modules[index].version[0])
strncpy(stk_modules[index].version, "0.0.0",
STK_MOD_VERSION_BUFFER - 1);
stk_modules[index].desc[0] = '\0';
u.obj = platform_get_symbol(handle, stk_mod_description_fn);
if (u.obj) {
meta_str = u.meta_func();
if (meta_str) {
strncpy(stk_modules[index].desc, meta_str,
STK_MOD_DESC_BUFFER - 1);
stk_modules[index].desc[STK_MOD_DESC_BUFFER - 1] = '\0';
}
}
stk_modules[index].deps = NULL;
stk_modules[index].dep_count = 0;
u.obj = platform_get_symbol(handle, stk_mod_deps_sym);
if (!u.obj)
goto skip_deps;
deps = (const stk_dep_t *)u.obj;
dep_count = 0;
while (deps[dep_count].id[0] != '\0')
dep_count++;
if (dep_count == 0)
goto skip_deps;
dep_arr = malloc(dep_count * sizeof(stk_dep_t));
if (!dep_arr)
goto skip_deps;
{
size_t d;
for (d = 0; d < dep_count; d++) {
strncpy(dep_arr[d].id, deps[d].id,
STK_MOD_ID_BUFFER - 1);
dep_arr[d].id[STK_MOD_ID_BUFFER - 1] = '\0';
strncpy(dep_arr[d].version, deps[d].version,
STK_MOD_VERSION_BUFFER - 1);
dep_arr[d].version[STK_MOD_VERSION_BUFFER - 1] = '\0';
}
}
stk_modules[index].deps = dep_arr;
stk_modules[index].dep_count = dep_count;
skip_deps:
return STK_MOD_INIT_SUCCESS; return STK_MOD_INIT_SUCCESS;
} }
@@ -133,48 +457,51 @@ unsigned char stk_module_load_init(const char *path, int index)
void stk_module_unload(size_t index) void stk_module_unload(size_t index)
{ {
stk_shutdowns[index](); stk_modules[index].shutdown();
platform_unload_library(stk_handles[index]); platform_unload_library(stk_modules[index].handle);
stk_handles[index] = NULL;
stk_inits[index] = NULL; stk_modules[index].handle = NULL;
stk_shutdowns[index] = NULL; stk_modules[index].init = NULL;
stk_module_ids[index][0] = '\0'; stk_modules[index].shutdown = NULL;
stk_modules[index].id[0] = '\0';
stk_modules[index].name[0] = '\0';
stk_modules[index].version[0] = '\0';
stk_modules[index].desc[0] = '\0';
if (stk_modules[index].deps) {
free(stk_modules[index].deps);
stk_modules[index].deps = NULL;
}
stk_modules[index].dep_count = 0;
} }
void stk_module_free_memory(void) void stk_module_free_memory(void)
{ {
free(stk_module_ids); if (stk_modules) {
free(stk_handles); size_t i;
free(stk_inits); for (i = 0; i < module_count; i++) {
free(stk_shutdowns); if (stk_modules[i].deps)
free(stk_modules[i].deps);
stk_module_ids = NULL; }
stk_handles = NULL; free(stk_modules);
stk_inits = NULL; stk_modules = NULL;
stk_shutdowns = NULL; }
module_count = 0;
stk_pending_free();
} }
unsigned char stk_module_init_memory(size_t capacity) unsigned char stk_module_init_memory(size_t capacity)
{ {
stk_module_ids = malloc(capacity * sizeof(*stk_module_ids)); stk_modules = malloc(capacity * sizeof(stk_mod_t));
stk_handles = malloc(capacity * sizeof(void *)); if (!stk_modules)
stk_inits = malloc(capacity * sizeof(stk_init_mod_func));
stk_shutdowns = malloc(capacity * sizeof(stk_shutdown_mod_func));
if (!stk_module_ids || !stk_handles || !stk_inits || !stk_shutdowns) {
stk_module_free_memory();
return STK_INIT_MEMORY_ERROR; return STK_INIT_MEMORY_ERROR;
}
return STK_INIT_SUCCESS; return STK_INIT_SUCCESS;
} }
unsigned char stk_module_realloc_memory(size_t new_capacity) unsigned char stk_module_realloc_memory(size_t new_capacity)
{ {
char (*new_module_ids)[STK_MOD_ID_BUFFER] = NULL; stk_mod_t *new_modules;
void **new_handles = NULL;
stk_init_mod_func *new_inits = NULL;
stk_shutdown_mod_func *new_shutdowns = NULL;
size_t i, copy_count; size_t i, copy_count;
if (new_capacity == 0) { if (new_capacity == 0) {
@@ -182,62 +509,30 @@ unsigned char stk_module_realloc_memory(size_t new_capacity)
return 0; return 0;
} }
new_module_ids = malloc(new_capacity * sizeof(*stk_module_ids)); new_modules = malloc(new_capacity * sizeof(stk_mod_t));
new_handles = malloc(new_capacity * sizeof(*new_handles)); if (!new_modules)
new_inits = malloc(new_capacity * sizeof(stk_init_mod_func));
new_shutdowns = malloc(new_capacity * sizeof(stk_shutdown_mod_func));
if (!new_module_ids || !new_handles || !new_inits || !new_shutdowns) {
if (new_module_ids)
free(new_module_ids);
if (new_handles)
free(new_handles);
if (new_inits)
free(new_inits);
if (new_shutdowns)
free(new_shutdowns);
return STK_MOD_REALLOC_FAILURE; return STK_MOD_REALLOC_FAILURE;
}
copy_count = copy_count =
(module_count < new_capacity) ? module_count : new_capacity; (module_count < new_capacity) ? module_count : new_capacity;
if (stk_module_ids) { for (i = 0; i < copy_count; i++)
for (i = 0; i < copy_count; i++) { new_modules[i] = stk_modules[i];
strncpy(new_module_ids[i], stk_module_ids[i],
STK_MOD_ID_BUFFER - 1);
new_module_ids[i][STK_MOD_ID_BUFFER - 1] = '\0';
}
}
if (stk_handles)
memcpy(new_handles, stk_handles, copy_count * sizeof(void *));
if (stk_inits)
memcpy(new_inits, stk_inits,
copy_count * sizeof(stk_init_mod_func));
if (stk_shutdowns)
memcpy(new_shutdowns, stk_shutdowns,
copy_count * sizeof(stk_shutdown_mod_func));
for (i = copy_count; i < new_capacity; i++) { for (i = copy_count; i < new_capacity; i++) {
new_module_ids[i][0] = '\0'; new_modules[i].handle = NULL;
new_handles[i] = NULL; new_modules[i].init = NULL;
new_inits[i] = NULL; new_modules[i].shutdown = NULL;
new_shutdowns[i] = NULL; new_modules[i].id[0] = '\0';
new_modules[i].name[0] = '\0';
new_modules[i].version[0] = '\0';
new_modules[i].desc[0] = '\0';
new_modules[i].deps = NULL;
new_modules[i].dep_count = 0;
} }
stk_module_free_memory(); free(stk_modules);
stk_modules = new_modules;
stk_module_ids = new_module_ids;
stk_handles = new_handles;
stk_inits = new_inits;
stk_shutdowns = new_shutdowns;
return 0; return 0;
} }
@@ -251,20 +546,158 @@ void stk_module_unload_all(void)
stk_module_free_memory(); stk_module_free_memory();
} }
void stk_set_module_init_fn(const char *name) void stk_pending_add(const char *path)
{
char (*new_pending)[STK_PATH_MAX_OS];
size_t i;
new_pending = malloc((stk_pending_count + 1) * sizeof(*stk_pending));
if (!new_pending)
return;
for (i = 0; i < stk_pending_count; i++)
memcpy(new_pending[i], stk_pending[i], STK_PATH_MAX_OS);
free(stk_pending);
stk_pending = new_pending;
strncpy(stk_pending[stk_pending_count], path, STK_PATH_MAX_OS - 1);
stk_pending[stk_pending_count][STK_PATH_MAX_OS - 1] = '\0';
stk_pending_count++;
}
void stk_pending_remove(const char *id)
{
size_t i, write;
char pending_id[STK_MOD_ID_BUFFER];
if (!stk_pending_count)
return;
write = 0;
for (i = 0; i < stk_pending_count; i++) {
extract_module_id(stk_pending[i], pending_id);
if (strncmp(pending_id, id, STK_MOD_ID_BUFFER) == 0)
continue;
if (write != i)
memcpy(stk_pending[write], stk_pending[i],
STK_PATH_MAX_OS);
write++;
}
stk_pending_count = write;
}
size_t stk_pending_retry(void)
{
size_t i, d, loaded = 0;
unsigned char deps_satisfied;
unsigned char result;
void *handle;
union {
void *obj;
const char *(*meta_func)(void);
} u;
const stk_dep_t *deps;
size_t dep_count;
int found;
if (!stk_pending_count)
return 0;
for (i = 0; i < stk_pending_count; i++) {
handle = platform_load_library(stk_pending[i]);
if (!handle)
continue;
u.obj = platform_get_symbol(handle, stk_mod_deps_sym);
if (!u.obj) {
platform_unload_library(handle);
goto attempt_load;
}
deps = (const stk_dep_t *)u.obj;
dep_count = 0;
while (deps[dep_count].id[0] != '\0')
dep_count++;
deps_satisfied = 1;
for (d = 0; d < dep_count; d++) {
found = is_mod_loaded(deps[d].id);
if (found < 0) {
deps_satisfied = 0;
break;
}
if (deps[d].version[0] &&
!stk_validate_constraint(
deps[d].version, stk_modules[found].version)) {
deps_satisfied = 0;
break;
}
}
platform_unload_library(handle);
if (!deps_satisfied)
continue;
attempt_load:
if (stk_module_realloc_memory(module_count + 1) != 0)
continue;
result = stk_module_load(stk_pending[i], module_count);
if (result != STK_MOD_INIT_SUCCESS)
continue;
module_count++;
loaded++;
memcpy(stk_pending[i], stk_pending[stk_pending_count - 1],
STK_PATH_MAX_OS);
stk_pending_count--;
i--;
}
if (stk_pending_count == 0)
stk_pending_free();
return loaded;
}
static void stk_set_fn_name(char *dst, const char *name)
{ {
if (!name || (stk_flags & STK_FLAG_INITIALIZED)) if (!name || (stk_flags & STK_FLAG_INITIALIZED))
return; return;
strncpy(stk_mod_init_name, name, STK_MOD_FUNC_NAME_BUFFER - 1); strncpy(dst, name, STK_MOD_FUNC_NAME_BUFFER - 1);
stk_mod_init_name[STK_MOD_FUNC_NAME_BUFFER - 1] = '\0'; dst[STK_MOD_FUNC_NAME_BUFFER - 1] = '\0';
}
void stk_set_module_init_fn(const char *name)
{
stk_set_fn_name(stk_mod_init_name, name);
} }
void stk_set_module_shutdown_fn(const char *name) void stk_set_module_shutdown_fn(const char *name)
{ {
if (!name || (stk_flags & STK_FLAG_INITIALIZED)) stk_set_fn_name(stk_mod_shutdown_name, name);
return; }
strncpy(stk_mod_shutdown_name, name, STK_MOD_FUNC_NAME_BUFFER - 1); void stk_set_module_name_fn(const char *name)
stk_mod_shutdown_name[STK_MOD_FUNC_NAME_BUFFER - 1] = '\0'; {
stk_set_fn_name(stk_mod_name_fn, name);
}
void stk_set_module_version_fn(const char *name)
{
stk_set_fn_name(stk_mod_version_fn, name);
}
void stk_set_module_description_fn(const char *name)
{
stk_set_fn_name(stk_mod_description_fn, name);
}
void stk_set_module_deps_sym(const char *name)
{
stk_set_fn_name(stk_mod_deps_sym, name);
} }
+22 -2
View File
@@ -107,9 +107,29 @@ typedef struct {
unsigned char 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
+205 -32
View File
@@ -8,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;
unsigned char stk_flags = STK_FLAG_LOGGING_ENABLED; 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,
@@ -43,6 +51,11 @@ unsigned char stk_module_init_memory(size_t capacity);
unsigned char stk_module_realloc_memory(size_t new_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_remove(const char *id);
size_t stk_pending_retry(void);
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)
@@ -64,11 +77,47 @@ 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";
} }
} }
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) unsigned char stk_init(void)
{ {
char (*files)[STK_PATH_MAX] = NULL; char (*files)[STK_PATH_MAX] = NULL;
@@ -76,7 +125,11 @@ unsigned char stk_init(void)
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;
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]; char (*test_scan)[STK_PATH_MAX];
size_t test_count; size_t test_count;
@@ -125,12 +178,42 @@ unsigned char stk_init(void)
} }
} }
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;
dep_result = stk_validate_dependencies(module_count);
if (dep_result != STK_MOD_INIT_SUCCESS) {
size_t j;
char mod_tmp_path[STK_PATH_MAX_OS];
stk_log(STK_LOG_WARN,
"Some modules have unmet dependencies, deferring");
for (j = 0; j < module_count; j++) {
if (stk_modules[j].dep_count > 0) {
build_path(mod_tmp_path, sizeof(mod_tmp_path),
stk_tmp_dir, stk_modules[j].id);
strncat(mod_tmp_path, STK_MODULE_EXT,
sizeof(mod_tmp_path) -
strlen(mod_tmp_path) - 1);
stk_pending_add(mod_tmp_path);
stk_module_unload(j);
}
}
}
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);
}
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) {
@@ -141,9 +224,10 @@ scanned:
return STK_INIT_WATCH_ERROR; return STK_INIT_WATCH_ERROR;
} }
stk_log(STK_LOG_INFO, "stk v%s initialized! Loaded %lu mod%s from %s/", stk_log(STK_LOG_INFO, "stk v%s initialized, watching %s/",
STK_VERSION_STRING, module_count, module_count != 1 ? "s" : "", STK_VERSION_STRING, stk_mod_dir);
stk_mod_dir); if (module_count > 0)
stk_log_modules();
stk_flags |= STK_FLAG_INITIALIZED; stk_flags |= STK_FLAG_INITIALIZED;
return STK_INIT_SUCCESS; return STK_INIT_SUCCESS;
@@ -183,10 +267,27 @@ size_t stk_poll(void)
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;
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;
@@ -246,13 +347,16 @@ 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) 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]); stk_module_unload(unloaded_mod_indices[i]);
}
for (i = 0; i < reload_count; ++i) { for (i = 0; i < reload_count; ++i) {
int file_index = reloaded_mod_file_indices[i]; int file_index = reloaded_mod_file_indices[i];
@@ -263,6 +367,8 @@ begin_operations:
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(STK_LOG_ERROR, "Failed to copy %s for reload", stk_log(STK_LOG_ERROR, "Failed to copy %s for reload",
@@ -270,14 +376,11 @@ begin_operations:
continue; continue;
} }
stk_module_unload(mod_index);
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(STK_LOG_ERROR, "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;
@@ -298,11 +401,10 @@ begin_operations:
} }
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_INIT_SUCCESS)
stk_log(STK_LOG_ERROR, "Failed to load module %s: %s", 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));
}
} }
if (load_count > unload_count) if (load_count > unload_count)
@@ -311,7 +413,7 @@ begin_operations:
if (unload_count > load_count) if (unload_count > load_count)
goto trim_arrays; goto trim_arrays;
goto free_poll; goto validate_deps;
append_modules: append_modules:
for (; i < load_count; ++i) { for (; i < load_count; ++i) {
@@ -342,11 +444,10 @@ 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; goto validate_deps;
trim_arrays: trim_arrays:
write_pos = unloaded_mod_indices[holes_to_fill]; write_pos = unloaded_mod_indices[holes_to_fill];
@@ -356,12 +457,8 @@ trim_arrays:
} }
for (read_pos = write_pos + 1; read_pos < module_count; ++read_pos) { for (read_pos = write_pos + 1; read_pos < module_count; ++read_pos) {
if (stk_handles[read_pos] != NULL) { if (stk_modules[read_pos].handle != NULL) {
stk_handles[write_pos] = stk_handles[read_pos]; stk_modules[write_pos] = stk_modules[read_pos];
stk_inits[write_pos] = stk_inits[read_pos];
stk_shutdowns[write_pos] = stk_shutdowns[read_pos];
memcpy(stk_module_ids[write_pos],
stk_module_ids[read_pos], STK_MOD_ID_BUFFER);
++write_pos; ++write_pos;
} }
} }
@@ -369,6 +466,82 @@ trim_arrays:
module_count = write_pos; module_count = write_pos;
stk_module_realloc_memory(module_count); stk_module_realloc_memory(module_count);
validate_deps:
if (module_count == 0)
goto free_poll;
{
size_t cascade_indices[STK_PATH_MAX];
size_t cascade_count;
char cascade_tmp_path[STK_PATH_MAX_OS];
size_t j, k, cascade_write;
do {
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;
}
}
}
if (cascade_count == 0)
break;
for (j = 0; j < cascade_count; j++) {
size_t index = cascade_indices[j];
stk_log(STK_LOG_WARN,
"Unloading '%s': unmet dependencies",
stk_modules[index].id);
build_path(cascade_tmp_path,
sizeof(cascade_tmp_path),
stk_tmp_dir, stk_modules[index].id);
strncat(cascade_tmp_path, STK_MODULE_EXT,
sizeof(cascade_tmp_path) -
strlen(cascade_tmp_path) - 1);
stk_pending_add(cascade_tmp_path);
stk_module_unload(index);
}
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);
}
stk_pending_retry();
if (module_count > 0)
stk_log_modules();
free_poll: free_poll:
free(reloaded_mod_indices); free(reloaded_mod_indices);
free(reloaded_mod_file_indices); free(reloaded_mod_file_indices);
+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 -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"}, {"", ""}};