Skip to content

Commit cefe0ab

Browse files
committed
docs: optimized CLAUDE.md
1 parent 320aa76 commit cefe0ab

File tree

2 files changed

+141
-556
lines changed

2 files changed

+141
-556
lines changed

ARCHITECTURE.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Architecture - Embedded Ruby VM
2+
3+
## Three-Layer Design
4+
5+
```
6+
Layer 3: High-Level Kotlin (RubyInterpreterExtensions.kt, ScriptBatch.kt)
7+
| Ergonomic APIs: executeBatch(), batch(), executeSync()
8+
v
9+
Layer 2: Low-Level Kotlin (RubyInterpreter.kt, RubyScript.kt)
10+
| Direct wrappers over C API: enqueue(), execute(latch)
11+
v
12+
Layer 1: C Core (ruby-api-loader.h, ruby-interpreter.h, ruby-vm.c)
13+
Ruby VM integration: ruby_api_load(), ruby_interpreter_enqueue()
14+
```
15+
16+
**Principle:** "Easy things should be easy, hard things should be possible."
17+
18+
- Layer 3 handles 80% of use cases (batch execution, simple workflows)
19+
- Layer 2 enables custom synchronization with external systems (e.g., coordinating Ruby scripts + external DB via shared CountDownLatch)
20+
- Layer 1 stays minimal: platform-agnostic Ruby VM integration, thread management, IPC, asset loading
21+
22+
**Don't flatten the layers.** Each serves a purpose. Layer 2 costs ~15 lines to maintain and removing it would force ugly workarounds for edge cases.
23+
24+
## Library Loading Architecture
25+
26+
The project supports both **static** and **dynamic** linking through a unified API in `ruby-api-loader.h`:
27+
28+
```c
29+
int ruby_api_load(const char* lib_path, RubyAPI* api);
30+
```
31+
32+
### Dynamic Loading (Default)
33+
34+
Used by Kotlin/Native and JNI platforms:
35+
36+
1. **Asset Extraction**: `libassets.a` extracts Ruby runtime to cache directory at first run
37+
2. **Dependency Preloading**: `load_dependencies_from_file()` reads `libembedded-ruby.deps` and preloads `libruby.so` with `RTLD_GLOBAL`
38+
3. **Main Library Loading**: `ruby_api_load()` uses `dlopen()` to load `libembedded-ruby.so`
39+
4. **Symbol Resolution**: Function pointers populated via `dlsym()`
40+
41+
Key files:
42+
- `libembedded-ruby.so` - Shared library with all Ruby VM functions exported
43+
- `libembedded-ruby.deps` - Dependency list (generated by CMake)
44+
- `libassets.a` - Static library for asset extraction
45+
- `ruby-api-loader.h` - API loader with integrated dependency preloading
46+
47+
CMake uses `--whole-archive` to export symbols from `libruby-vm.a` into `libembedded-ruby.so`:
48+
```cmake
49+
if(UNIX AND NOT APPLE)
50+
target_link_libraries(embedded-ruby PRIVATE
51+
-Wl,--whole-archive logging ruby-vm -Wl,--no-whole-archive)
52+
endif()
53+
```
54+
55+
### Static Loading (Optional)
56+
57+
Enabled by defining `RUBY_STATIC` at compile time. Same `ruby_api_load()` signature — function pointers are assigned directly instead of via dlsym. Use for pure C apps or platforms without dynamic loading.
58+
59+
### Architecture Constraints
60+
61+
- Ruby runtime (`libruby.so`) must be deployed at runtime, not hardcoded at build time
62+
- `libembedded-ruby.so` cannot statically link Ruby (would create circular dependency)
63+
- Asset system handles runtime extraction of Ruby and its dependencies
64+
65+
## Design Decisions
66+
67+
### Why Kotlin Wrappers Instead of C Extensions?
68+
69+
C API stays minimal and focused on Ruby VM integration. Kotlin is better for high-level patterns (builders, sealed classes, extensions), easier to maintain/test, and avoids cross-platform C synchronization headaches.
70+
71+
### Why Hybrid Static/Dynamic Library Loading?
72+
73+
- **Runtime deployment requirement**: Ruby runtime extracted at runtime
74+
- **Platform flexibility**: JVM/Android uses JNI+dynamic, Kotlin/Native uses cinterop+dlopen, pure C can use static
75+
- **Transparent API**: Same function works for both approaches
76+
- Trade-off: dynamic loading adds small runtime overhead but enables runtime asset extraction
77+
78+
## Cross-Platform Patterns
79+
80+
### Time APIs
81+
- **JVM**: `System.currentTimeMillis()`
82+
- **Native**: `clock_gettime(CLOCK_MONOTONIC, ...)` via `platform.posix`
83+
84+
### File APIs
85+
- **JVM**: `java.io.File(path).readText()`
86+
- **Native**: `kotlin.io.path.Path(path).readText()`
87+
88+
### Multi-platform APIs
89+
- Use `actual`/`expect` for platform-specific implementations
90+
- Platform-specific code: JNI for JVM, cinterop for Native

0 commit comments

Comments
 (0)