organize repo

This commit is contained in:
KingKDot
2026-04-15 11:11:53 -05:00
parent f6c9337141
commit 7367923ac5
36 changed files with 39 additions and 25 deletions
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "sample-rust"
version = "0.1.0"
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "sample-rust"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[profile.release]
opt-level = "z"
lto = true
strip = true
+276
View File
@@ -0,0 +1,276 @@
/*
* Sample Rust Plugin — fully unloadable, no GC runtime.
*
* Exports the standard Overlord plugin ABI:
* PluginOnLoad, PluginOnEvent, PluginOnUnload, PluginSetCallback,
* PluginGetRuntime (returns "rust" — tells the host this DLL can be freed)
*
* Build:
* cargo build --release
* Output: target/release/sample_rust.dll (Windows) or libsample_rust.so (Linux)
*
* Note: Uses C-style globals instead of std::sync::Mutex because the DLL is
* loaded via an in-memory PE loader, and Rust's std Mutex (SRWLOCK) may not
* initialize correctly without OS-level LoadLibrary. The Go host already
* serializes all calls through its own mutex, so this is safe.
*/
use std::os::raw::{c_char, c_int};
use std::slice;
use std::ptr;
// ---------------------------------------------------------------------------
// Host callback types — platform-specific calling conventions
// ---------------------------------------------------------------------------
#[cfg(windows)]
type HostCallback = unsafe extern "stdcall" fn(
event: *const u8,
event_len: usize,
payload: *const u8,
payload_len: usize,
);
#[cfg(not(windows))]
type HostCallback = unsafe extern "C" fn(
ctx: usize,
event: *const u8,
event_len: i32,
payload: *const u8,
payload_len: i32,
);
// ---------------------------------------------------------------------------
// Global state — C-style, no Mutex.
// The Go host serializes all calls via dllPlugin.mu / soPlugin.mu.
// ---------------------------------------------------------------------------
static mut G_CALLBACK: Option<HostCallback> = None;
#[cfg(not(windows))]
static mut G_CALLBACK_CTX: usize = 0;
static mut G_CLIENT_ID: [u8; 256] = [0u8; 256];
static mut G_CLIENT_ID_LEN: usize = 0;
static mut G_EVENT_COUNT: u64 = 0;
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
unsafe fn send_event(event: &[u8], payload: &[u8]) {
if let Some(cb) = G_CALLBACK {
#[cfg(windows)]
cb(
event.as_ptr(),
event.len(),
payload.as_ptr(),
payload.len(),
);
#[cfg(not(windows))]
cb(
G_CALLBACK_CTX,
event.as_ptr(),
event.len() as i32,
payload.as_ptr(),
payload.len() as i32,
);
}
}
/// Minimal JSON string extraction by key — zero allocations.
unsafe fn json_extract_into(json: &[u8], key: &[u8], out: &mut [u8]) -> usize {
// Search for "key":" or "key": "
let klen = key.len();
if json.len() < klen + 4 {
return 0;
}
let mut i = 0;
while i + klen + 3 < json.len() {
if json[i] == b'"'
&& json[i + 1..i + 1 + klen] == *key
&& json[i + 1 + klen] == b'"'
{
// Skip past "key"
let mut j = i + 1 + klen + 1; // past closing quote
// Skip : and optional space
while j < json.len() && (json[j] == b':' || json[j] == b' ') {
j += 1;
}
// Expect opening quote
if j < json.len() && json[j] == b'"' {
j += 1;
let start = j;
while j < json.len() && json[j] != b'"' {
j += 1;
}
let vlen = j - start;
let copy_len = if vlen < out.len() { vlen } else { out.len() - 1 };
ptr::copy_nonoverlapping(json[start..].as_ptr(), out.as_mut_ptr(), copy_len);
return copy_len;
}
}
i += 1;
}
0
}
unsafe fn bytes_to_slice<'a>(p: *const c_char, len: c_int) -> &'a [u8] {
if p.is_null() || len <= 0 {
return &[];
}
slice::from_raw_parts(p as *const u8, len as usize)
}
// ---------------------------------------------------------------------------
// Exported ABI
// ---------------------------------------------------------------------------
#[no_mangle]
pub extern "C" fn PluginGetRuntime() -> *const c_char {
b"rust\0".as_ptr() as *const c_char
}
#[cfg(windows)]
#[no_mangle]
pub unsafe extern "C" fn PluginSetCallback(cb: u64) {
G_CALLBACK = Some(std::mem::transmute(cb as usize));
}
#[cfg(windows)]
#[no_mangle]
pub unsafe extern "C" fn PluginOnLoad(
host_info: *const c_char,
host_info_len: c_int,
cb: u64,
) -> c_int {
G_CALLBACK = Some(std::mem::transmute(cb as usize));
G_EVENT_COUNT = 0;
let info = bytes_to_slice(host_info, host_info_len);
G_CLIENT_ID_LEN = json_extract_into(info, b"clientId", &mut G_CLIENT_ID);
// Build a small on-stack response and send
send_event(
b"ready",
br#"{"message":"sample-rust plugin ready"}"#,
);
0
}
#[cfg(not(windows))]
#[no_mangle]
pub unsafe extern "C" fn PluginOnLoad(
host_info: *const c_char,
host_info_len: c_int,
cb: usize,
ctx: usize,
) -> c_int {
G_CALLBACK = Some(std::mem::transmute(cb));
G_CALLBACK_CTX = ctx;
G_EVENT_COUNT = 0;
let info = bytes_to_slice(host_info, host_info_len);
G_CLIENT_ID_LEN = json_extract_into(info, b"clientId", &mut G_CLIENT_ID);
send_event(
b"ready",
br#"{"message":"sample-rust plugin ready"}"#,
);
0
}
#[no_mangle]
pub unsafe extern "C" fn PluginOnEvent(
event: *const c_char,
event_len: c_int,
payload: *const c_char,
payload_len: c_int,
) -> c_int {
let ev = bytes_to_slice(event, event_len);
let pl = bytes_to_slice(payload, payload_len);
G_EVENT_COUNT += 1;
if ev == b"ping" {
send_event(b"pong", &[]);
return 0;
}
if ev == b"ui_message" {
// Build echo response on the stack — limited to 512 bytes
let prefix = br#"{"message":"echo from Rust: "#;
let suffix = br#""}"#;
let max_pl = 450;
let pl_copy = if pl.len() < max_pl { pl.len() } else { max_pl };
let total = prefix.len() + pl_copy + suffix.len();
let mut buf = [0u8; 512];
if total <= buf.len() {
ptr::copy_nonoverlapping(prefix.as_ptr(), buf.as_mut_ptr(), prefix.len());
ptr::copy_nonoverlapping(
pl.as_ptr(),
buf.as_mut_ptr().add(prefix.len()),
pl_copy,
);
ptr::copy_nonoverlapping(
suffix.as_ptr(),
buf.as_mut_ptr().add(prefix.len() + pl_copy),
suffix.len(),
);
send_event(b"echo", &buf[..total]);
}
return 0;
}
if ev == b"stats" {
// Format event count into a stack buffer
let mut buf = [0u8; 128];
let prefix = br#"{"event_count":"#;
let suffix = b"}";
ptr::copy_nonoverlapping(prefix.as_ptr(), buf.as_mut_ptr(), prefix.len());
let mut num_buf = [0u8; 20];
let num_len = fmt_u64(G_EVENT_COUNT, &mut num_buf);
ptr::copy_nonoverlapping(
num_buf[20 - num_len..].as_ptr(),
buf.as_mut_ptr().add(prefix.len()),
num_len,
);
ptr::copy_nonoverlapping(
suffix.as_ptr(),
buf.as_mut_ptr().add(prefix.len() + num_len),
suffix.len(),
);
let total = prefix.len() + num_len + suffix.len();
send_event(b"stats_reply", &buf[..total]);
return 0;
}
0
}
#[no_mangle]
pub unsafe extern "C" fn PluginOnUnload() {
G_CALLBACK = None;
#[cfg(not(windows))]
{
G_CALLBACK_CTX = 0;
}
G_CLIENT_ID_LEN = 0;
G_EVENT_COUNT = 0;
}
// ---------------------------------------------------------------------------
// Tiny u64-to-decimal without std::fmt (avoids allocator)
// ---------------------------------------------------------------------------
fn fmt_u64(mut n: u64, buf: &mut [u8; 20]) -> usize {
if n == 0 {
buf[19] = b'0';
return 1;
}
let mut i = 20;
while n > 0 {
i -= 1;
buf[i] = b'0' + (n % 10) as u8;
n /= 10;
}
20 - i
}
@@ -0,0 +1 @@
{"rustc_fingerprint":2568544898921049753,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.94.0 (4a4ef493e 2026-03-02)\nbinary: rustc\ncommit-hash: 4a4ef493e3a1488c6e321570238084b38948f6db\ncommit-date: 2026-03-02\nhost: x86_64-pc-windows-msvc\nrelease: 1.94.0\nLLVM version: 21.1.8\n","stderr":""},"12004014463585500860":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\this1\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\this1\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}}
+93
View File
@@ -0,0 +1,93 @@
:root {
color-scheme: dark;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "Segoe UI", system-ui, sans-serif;
background: #0f1115;
color: #e8ecf1;
}
.container {
max-width: 820px;
margin: 40px auto;
padding: 0 20px;
}
.header h1 {
margin: 0 0 4px 0;
font-size: 28px;
}
.sub {
margin: 0 0 24px 0;
color: #9aa4b2;
}
.card {
background: #161a22;
border: 1px solid #222938;
border-radius: 12px;
padding: 18px;
margin-bottom: 18px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.row {
display: grid;
grid-template-columns: 120px 1fr;
gap: 12px;
align-items: center;
margin-bottom: 12px;
}
.label {
color: #9aa4b2;
font-size: 14px;
}
.input {
padding: 10px 12px;
border-radius: 8px;
border: 1px solid #2a3346;
background: #0f131b;
color: #e8ecf1;
}
.actions {
display: flex;
gap: 10px;
margin-top: 14px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
background: #2563eb;
color: #fff;
cursor: pointer;
font-size: 14px;
}
.btn:hover {
background: #1d4ed8;
}
.log {
background: #0b0e14;
border: 1px solid #1e2533;
border-radius: 8px;
padding: 14px;
font-size: 13px;
line-height: 1.6;
max-height: 300px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
}
+42
View File
@@ -0,0 +1,42 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Sample Rust Plugin</title>
<link rel="stylesheet" href="/plugins/sample-rust/assets/sample-rust.css" />
</head>
<body>
<main class="container">
<header class="header">
<h1>Sample Rust Plugin</h1>
<p class="sub">Rust &mdash; fully unloadable, zero runtime leak</p>
</header>
<section class="card">
<div class="row">
<label class="label">Client ID</label>
<input id="client-id" class="input" placeholder="clientId" />
</div>
<div class="row">
<label class="label">Message</label>
<input id="message" class="input" placeholder="hello from UI" />
</div>
<div class="actions">
<button id="send-btn" class="btn">Send event to client</button>
<button id="ping-btn" class="btn">Ping</button>
<button id="stats-btn" class="btn">Get Stats</button>
</div>
</section>
<section class="card">
<h2>Log</h2>
<pre id="log" class="log"></pre>
</section>
</main>
<script src="/plugins/sample-rust/assets/sample-rust.js"></script>
</body>
</html>
+47
View File
@@ -0,0 +1,47 @@
const params = new URLSearchParams(window.location.search);
const clientIdInput = document.getElementById("client-id");
const messageInput = document.getElementById("message");
const logEl = document.getElementById("log");
const sendBtn = document.getElementById("send-btn");
const pingBtn = document.getElementById("ping-btn");
const statsBtn = document.getElementById("stats-btn");
const pluginId = "sample-rust";
clientIdInput.value = params.get("clientId") || "";
messageInput.value = "hello from sample-rust UI";
function log(line) {
const ts = new Date().toISOString();
logEl.textContent = `${ts} ${line}\n` + logEl.textContent;
}
async function sendEvent(event, payload) {
const clientId = clientIdInput.value.trim();
if (!clientId) { log("Missing clientId"); return; }
const res = await fetch(`/api/clients/${encodeURIComponent(clientId)}/plugins/${pluginId}/event`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ event, payload }),
});
if (!res.ok) { log(`Send failed: ${res.status} ${await res.text()}`); return; }
log(`Sent event: ${event}`);
}
sendBtn.addEventListener("click", () => {
sendEvent("ui_message", { message: messageInput.value.trim() });
});
pingBtn.addEventListener("click", () => {
sendEvent("ping", {});
});
statsBtn.addEventListener("click", () => {
sendEvent("stats", {});
});
if (typeof EventSource !== "undefined") {
const clientId = params.get("clientId");
if (clientId) {
log("Ready — Rust plugin (fully unloadable)");
}
}
Binary file not shown.