idk. but added AGENTS.md

This commit is contained in:
Sebastian Moser
2026-03-06 19:26:07 +01:00
parent b4bfedf372
commit 8e7f2d1ddc
44 changed files with 6146 additions and 887 deletions

View File

@@ -5,18 +5,24 @@ version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.5.59", optional = true }
deno_core = { version = "0.385.0", optional = true }
clap = { version = "4.5.59", optional = true, features = ["string"] }
flume = "0.12.0"
futures = "0.3.32"
mize = { path = "../mize/", default-features = false }
rustyscript = "0.12.3"
tokio = "1.49.0"
# https://github.com/rscarson/rustyscript/pull/415
rustyscript = { git = 'https://github.com/rscarson/rustyscript', rev = 'e39529eac2c5d37cfcbb7d7d63da7264d7a5afdb', optional = true }
deno_core = { version = "0.380.1", optional = true }
chrono = "0.4.44"
reqwest = { version = "0.13.2", features = ["blocking", "json"] }
serde = "1.0.228"
serde_json = "1.0.149"
[features]
default = ["target-os"]
target-os = [ "clap", "mize/target-os", "deno_core" ]
target-os = [ "clap", "mize/target-os", "deno_core", "rustyscript" ]
target-wasm = [ "mize/target-wasm" ]
clap = ["dep:clap"]
[lib]
path = "lib.rs"

225
packages/marts/c2vi/mod.rs Normal file
View File

@@ -0,0 +1,225 @@
use chrono::{Datelike, Timelike, Utc};
use clap::{Arg, Command};
use rusqlite::{params, Connection, Result};
use serde::{Deserialize, Serialize};
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process::Command as StdCommand;
// Assuming PPC and other modules are structured like this.
// This might need adjustment based on the actual definitions in other files.
use crate::habitica::Habitica; // Assuming this exists
pub struct C2vi {
pub db: Connection,
}
impl C2vi {
pub fn new(local_storage_path: &str) -> Result<Self> {
let db_path = PathBuf::from(local_storage_path).join("data.db");
let conn = Connection::open(db_path)?;
Ok(C2vi { db: conn })
}
}
pub fn extend_cli_c2vi(mut cli: Command) -> Command {
cli.subcommand(
Command::new("bed")
.about("Command to run when c2vi goes to bed")
.action(async || {
// Placeholder for actual PPC context
// await save_habitica_dailies(ppc);
// await save_habitica_logs(ppc);
}),
)
.subcommand(
Command::new("clearTodos")
.about("Clear all todos in Habitica")
.alias("clt")
.action(async || {
// Placeholder
}),
)
.subcommand(
Command::new("listTodos")
.about("List all todos in Habitica")
.alias("dut")
.action(async || {
// Placeholder
}),
)
.subcommand(
Command::new("buyHealthPotion")
.about("Buy a health potion in Habitica")
.alias("buyh")
.action(async || {
// Placeholder
}),
)
.subcommand(
Command::new("addActionItems")
.about("Add action items from a temp file")
.alias("aal")
.action(async || {
// add_action_items();
}),
)
.subcommand(
Command::new("skip")
.about("Skip a Habitica todo")
.alias("sk")
.arg(Arg::new("num").default_value("1"))
.action(async || {
// let num = matches.value_of("num").unwrap().parse::<i32>().unwrap();
// skip_habitica_todo(ppc, num);
}),
)
.subcommand(
Command::new("dumpLog")
.about("Dump the complete Habitica log")
.action(async || {
// dump_habitica_logs(ppc);
}),
)
.subcommand(
Command::new("listDailies")
.about("List all dailies from the local db")
.action(async || {
// Placeholder
}),
)
.subcommand(
Command::new("printTask")
.about("Print a specific todo from Habitica")
.arg(Arg::new("id").required(true))
.action(async || {
// let id = matches.value_of("id").unwrap();
// Placeholder
}),
)
}
fn format_date(date: chrono::DateTime<Utc>) -> String {
date.format("%Y-%m-%d").to_string()
}
#[derive(Serialize, Deserialize)]
struct HabiticaDaily {
id: String,
text: String,
notes: String,
priority: Option<String>,
frequency: String,
}
#[derive(Serialize, Deserialize)]
struct HabiticaLog {
date: String,
data: String, // JSON string
}
fn setup_tables(conn: &Connection) -> Result<()> {
conn.execute(
"CREATE TABLE IF NOT EXISTS habitica_dailies (
id TEXT PRIMARY KEY,
text TEXT NOT NULL,
notes TEXT,
priority TEXT,
frequency TEXT
)",
[],
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS habitica_log (
date TEXT PRIMARY KEY,
data TEXT NOT NULL
)",
[],
)?;
Ok(())
}
// In-progress translation. Many functions will need the PPC context passed in.
/*
async fn save_habitica_dailies(ppc: &PPC) -> Result<()> {
println!("######### cli bed cmd #########");
setup_tables(&ppc.c2vi.db)?;
// Assuming ppc.habitica.get_tasks returns a Result
let dailies = ppc.habitica.get_tasks("dailys").await?;
write_dailies_to_database(&dailies, &ppc.c2vi.db).await?;
Ok(())
}
async fn write_dailies_to_database(dailies: &[HabiticaTask], db: &Connection) -> Result<()> {
println!("Syncing {} dailies to database...", dailies.len());
let tx = db.transaction()?;
for daily in dailies {
tx.execute(
"INSERT INTO habitica_dailies (id, text, notes, priority, frequency)
VALUES (?1, ?2, ?3, ?4, ?5)
ON CONFLICT(id) DO UPDATE SET
text=excluded.text,
notes=excluded.notes,
priority=excluded.priority,
frequency=excluded.frequency",
params![
daily.id,
daily.text,
daily.notes.as_deref().unwrap_or(""),
daily.priority.map(|p| p.to_string()),
daily.frequency.as_deref().unwrap_or("daily"),
],
)?;
}
tx.commit()
}
async fn add_action_items(ppc: &PPC) -> Result<()> {
let initial_content = "
gu 10min TIMER
uw 15min FROG
b coffee/tee/kakau
uw FROG
b breakfast, brush teeth, lüften
uw FROG
lunch
uw 15min TIMER of next days FROG
fw
dt 30min TIMER
fun 20:00 (smth that can be stopped easily)
se 21:40
read
bed 22:10
";
let tmp_file = env::temp_dir().join(format!("habitica-tasks-{}.md", Utc::now().timestamp()));
fs::write(&tmp_file, initial_content.trim())?;
StdCommand::new("nvim").arg(&tmp_file).status()?;
let list_str = fs::read_to_string(&tmp_file)?;
fs::remove_file(&tmp_file)?;
let tasks: Vec<&str> = list_str.lines().map(|t| t.trim()).filter(|t| !t.is_empty()).collect();
let mut task_ids = vec![];
for todo_text in tasks {
// Assuming api_request exists and works like this
let data = ppc.habitica.api_request("POST", "tasks/user", Some(serde_json::json!({ "text": todo_text, "type": "todo" }))).await?;
let task_id = data["id"].as_str().unwrap().to_string();
println!("Added to-do: '{}' with id '{}'", todo_text, task_id);
task_ids.push(task_id);
}
for task_id in task_ids {
ppc.habitica.api_request("POST", &format!("tasks/{}/move/to/-1", task_id), None).await?;
}
Ok(())
}
*/

View File

@@ -8,8 +8,12 @@ use mize::{mize_part, Mize, MizePart, MizeResult};
pub struct CliPart {
mize: Mize,
cmd: Option<Command>,
actions: HashMap<String, fn(&ArgMatches) -> MizeResult<()>>,
parsers: Option<Vec<Box<dyn (FnOnce(Mize, Vec<String>) -> MizeResult<()>) + Send + Sync>>>,
actions: HashMap<
String,
Box<dyn FnOnce(&ArgMatches, Mize) -> MizeResult<()> + Send + Sync + 'static>,
>,
parsers:
Option<Vec<Box<dyn FnOnce(Mize, Vec<String>) -> MizeResult<()> + Send + Sync + 'static>>>,
}
pub fn cli(mize: &mut Mize) -> MizeResult<()> {
@@ -37,7 +41,7 @@ impl MizePart for CliPart {
return Ok(());
}
};
let action = match self.actions.get(sub_cmd) {
let action = match self.actions.remove(sub_cmd) {
Some(action) => action,
None => {
// external parsers
@@ -56,7 +60,7 @@ impl MizePart for CliPart {
return Ok(());
}
};
action(matches.subcommand_matches(sub_cmd).unwrap())?;
action(matches.subcommand_matches(sub_cmd).unwrap(), mize.clone())?;
Ok(())
}
fn opts(&self, mize: &mut Mize) {
@@ -65,14 +69,14 @@ impl MizePart for CliPart {
}
impl CliPart {
pub fn subcommand(
pub fn subcommand<T: FnOnce(&ArgMatches, Mize) -> MizeResult<()> + Send + Sync + 'static>(
&mut self,
subcmd: Command,
action: fn(sub_matches: &ArgMatches) -> MizeResult<()>,
action: T,
) -> &mut Command {
let name = subcmd.get_name().to_string();
let cmd = self.cmd.take().unwrap().subcommand(subcmd);
self.actions.insert(name, action);
self.actions.insert(name, Box::new(action));
self.cmd = Some(cmd);
self.cmd.as_mut().unwrap()
}

View File

@@ -6,6 +6,6 @@
"stub_deno": "./js/stub_deno.ts"
},
"tasks": {
"build": "deno bundle -o ./deno_dist/habitica.js ./habitica.ts"
"build": "deno bundle -o ./deno_dist/habitica.js ./habitica.ts && deno -A ./esbuild.config.mjs"
}
}

View File

@@ -1,75 +1,76 @@
// habitica.ts
var opts = {
"habitica.api_url": {
env_var_name: "PPC_HABITICA_API_URL",
default_val: "https://habitica.com/api/v3"
},
"habitica.user_id": {
env_var_name: "PPC_HABITICA_USER_ID"
},
"habitica.client_name": {
default_val: "3544a0b8-d71a-46e0-9bb1-6ddbb2abcddb-PPC-Software"
},
"habitica.api_token": {
env_var_name: "PPC_HABITICA_API_TOKEN"
(() => {
// habitica.ts
var opts = {
"habitica.api_url": {
env_var_name: "PPC_HABITICA_API_URL",
default_val: "https://habitica.com/api/v3"
},
"habitica.user_id": {
env_var_name: "PPC_HABITICA_USER_ID"
},
"habitica.client_name": {
default_val: "3544a0b8-d71a-46e0-9bb1-6ddbb2abcddb-PPC-Software"
},
"habitica.api_token": {
env_var_name: "PPC_HABITICA_API_TOKEN"
}
};
async function habitica(mize) {
Deno.core.print("hiiiiiiiiiiiiiiiiiiii from habitica");
log("hiiiiiiiiiiiiiiiiiiii");
mize.add_opts(opts);
mize.add_part(new Habitica(mize));
}
};
async function habitica(mize) {
console.log("hiiiiiiiiiiiiiiiiiiii");
mize.add_opts(opts);
mize.add_part(new Habitica(mize));
}
async function handleRateLimit(response) {
const limit = response.headers.get("X-RateLimit-Limit") || "NONE";
const remaining = parseInt(response.headers.get("X-RateLimit-Remaining") || "10", 10);
const reset = response.headers.get("X-RateLimit-Reset");
console.log(`RateLimit: ${limit} | Remaining: ${remaining} | Reset: ${reset}`);
if (remaining < 2 && reset) {
const resetDate = new Date(reset);
const now = /* @__PURE__ */ new Date();
const waitMs = resetDate.getTime() - now.getTime() + 1e3;
if (waitMs > 0) {
console.log(`Waiting ${Math.round(waitMs / 1e3)} secs for next rate limit window...`);
await new Promise((resolve) => setTimeout(resolve, waitMs));
async function handleRateLimit(response) {
const limit = response.headers.get("X-RateLimit-Limit") || "NONE";
const remaining = parseInt(response.headers.get("X-RateLimit-Remaining") || "10", 10);
const reset = response.headers.get("X-RateLimit-Reset");
console.log(`RateLimit: ${limit} | Remaining: ${remaining} | Reset: ${reset}`);
if (remaining < 2 && reset) {
const resetDate = new Date(reset);
const now = /* @__PURE__ */ new Date();
const waitMs = resetDate.getTime() - now.getTime() + 1e3;
if (waitMs > 0) {
console.log(`Waiting ${Math.round(waitMs / 1e3)} secs for next rate limit window...`);
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
}
}
}
var Habitica = class {
mize;
constructor(mize) {
this.mize = mize;
}
async get_tasks(type = "todos") {
return await this.api_request("GET", `tasks/user?type=${type}`);
}
async delete_task(id) {
await this.api_request("DELETE", `tasks/${id}`);
}
async api_request(method, path, extraHeaders = {}, data = {}) {
const mize = this.mize;
if (!path.startsWith("/")) {
path = "/" + path;
var Habitica = class {
mize;
constructor(mize) {
this.mize = mize;
}
const headers = Object.assign({
"Content-Type": "application/json",
"x-api-user": mize.get_config("habitica.user_id"),
"x-api-key": mize.get_config("habitica.api_token"),
"x-client": mize.get_config("habitica.client_name")
}, extraHeaders);
const response = await fetch(mize.get_config("habitica.api_url") + path, {
method,
headers,
body: JSON.stringify(data)
});
await handleRateLimit(response);
if (!response.ok) {
console.log("request_url:", mize.get_config("habitica.api_url") + path);
throw new Error(`Habitica API error: ${response.status} ${response.statusText}`);
async get_tasks(type = "todos") {
return await this.api_request("GET", `tasks/user?type=${type}`);
}
return (await response.json()).data;
}
};
export {
habitica,
opts
};
async delete_task(id) {
await this.api_request("DELETE", `tasks/${id}`);
}
async api_request(method, path, extraHeaders = {}, data = {}) {
const mize = this.mize;
if (!path.startsWith("/")) {
path = "/" + path;
}
const headers = Object.assign({
"Content-Type": "application/json",
"x-api-user": mize.get_config("habitica.user_id"),
"x-api-key": mize.get_config("habitica.api_token"),
"x-client": mize.get_config("habitica.client_name")
}, extraHeaders);
const response = await fetch(mize.get_config("habitica.api_url") + path, {
method,
headers,
body: JSON.stringify(data)
});
await handleRateLimit(response);
if (!response.ok) {
console.log("request_url:", mize.get_config("habitica.api_url") + path);
throw new Error(`Habitica API error: ${response.status} ${response.statusText}`);
}
return (await response.json()).data;
}
};
habitica(globalThis.mize);
Deno.core.print("hiiiiiiiiiiiiiiiiiiii from habitica one");
})();

View File

@@ -0,0 +1,12 @@
import * as esbuild from "npm:esbuild";
import { denoPlugin } from "jsr:@deno/esbuild-plugin";
await esbuild.build({
plugins: [denoPlugin()],
entryPoints: ["./habitica.ts"],
outfile: "./deno_dist/habitica.js",
bundle: true,
format: "iife",
});
esbuild.stop();

108
packages/marts/habitica.rs Normal file
View File

@@ -0,0 +1,108 @@
use chrono::{DateTime, Utc};
use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizeResult};
use reqwest::{blocking::Client, blocking::RequestBuilder, blocking::Response, header, Method};
use serde_json::{json, Value};
use std::thread::sleep;
use std::time::Duration;
#[mize_part]
#[derive(Default)]
pub struct Habitica {
mize: Mize,
client: Client,
}
pub fn habitica(mize: &mut Mize) -> MizeResult<()> {
let client = Client::new();
mize.register_part(Box::new(Habitica {
mize: mize.clone(),
client,
}))
}
impl MizePart for Habitica {}
impl Habitica {
fn api_request(&mut self, method: Method, path: String, data: Value) -> MizeResult<Value> {
let api_url = self.mize.get_config("habitica.api_url")?.to_string();
let user_id = self.mize.get_config("habitica.user_id")?.to_string();
let api_token = self.mize.get_config("habitica.api_token")?.to_string();
let client_name = self.mize.get_config("habitica.client_name")?.to_string();
let mut headers = header::HeaderMap::new();
headers.insert(
"Content-Type",
header::HeaderValue::from_static("application/json"),
);
headers.insert("x-api-user", header::HeaderValue::from_str(&user_id)?);
headers.insert("x-api-key", header::HeaderValue::from_str(&api_token)?);
headers.insert("x-client", header::HeaderValue::from_str(&client_name)?);
let url = format!("{}/{}", api_url, path.trim_start_matches('/'));
let mut request_builder = self.client.request(method, &url).headers(headers);
if data != json!({}) {
request_builder = request_builder.json(&data);
}
let response = request_builder.send()?;
handle_rate_limit(&response);
if !response.status().is_success() {
let status = response.status();
let text = response.text().unwrap_or_default();
println!("request_url: {}", url);
return Err(mize_err!(
"Habitica API error: {} {} - {}",
status.as_u16(),
status.canonical_reason().unwrap_or(""),
text
));
}
let json_response: Value = response.json()?;
Ok(json_response.get("data").cloned().unwrap_or(Value::Null))
}
}
fn handle_rate_limit(response: &Response) {
let headers = response.headers();
let limit = headers
.get("X-RateLimit-Limit")
.and_then(|v| v.to_str().ok())
.unwrap_or("NONE");
let remaining = headers
.get("X-RateLimit-Remaining")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<i32>().ok())
.unwrap_or(10);
let reset = headers
.get("X-RateLimit-Reset")
.and_then(|v| v.to_str().ok());
println!(
"RateLimit: {} | Remaining: {} | Reset: {}",
limit,
remaining,
reset.unwrap_or("N/A")
);
if remaining < 2 {
if let Some(reset_str) = reset {
if let Ok(reset_date) = DateTime::parse_from_rfc2822(reset_str) {
let now = Utc::now();
let wait_duration = reset_date.signed_duration_since(now).to_std();
if let Ok(wait_duration) = wait_duration {
let wait_ms = wait_duration.as_millis() as u64 + 1000;
println!(
"Waiting {} secs for next rate limit window...",
(wait_ms / 1000)
);
sleep(Duration::from_millis(wait_ms));
}
}
}
}
}

View File

@@ -1,105 +0,0 @@
import { Mize } from "@ppc/marts/stub_deno";
export const opts = {
"habitica.api_url": {
env_var_name: "PPC_HABITICA_API_URL",
default_val: "https://habitica.com/api/v3",
},
"habitica.user_id": {
env_var_name: "PPC_HABITICA_USER_ID",
},
"habitica.client_name": {
default_val: "3544a0b8-d71a-46e0-9bb1-6ddbb2abcddb-PPC-Software",
},
"habitica.api_token": {
env_var_name: "PPC_HABITICA_API_TOKEN",
},
};
export async function habitica(mize: Mize) {
console.log("hiiiiiiiiiiiiiiiiiiii");
mize.add_opts(opts);
mize.add_part(new Habitica(mize));
}
async function handleRateLimit(response) {
const limit = response.headers.get("X-RateLimit-Limit") || "NONE";
const remaining = parseInt(
response.headers.get("X-RateLimit-Remaining") || "10",
10,
);
const reset = response.headers.get("X-RateLimit-Reset");
console.log(
`RateLimit: ${limit} | Remaining: ${remaining} | Reset: ${reset}`,
);
if (remaining < 2 && reset) {
const resetDate = new Date(reset);
const now = new Date();
const waitMs = resetDate.getTime() - now.getTime() + 1000;
if (waitMs > 0) {
console.log(
`Waiting ${Math.round(waitMs / 1000)} secs for next rate limit window...`,
);
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
}
}
class Habitica {
mize: Mize;
constructor(mize: Mize) {
this.mize = mize;
}
async get_tasks(type: string = "todos") {
return await this.api_request("GET", `tasks/user?type=${type}`);
}
async delete_task(id: string) {
await this.api_request("DELETE", `tasks/${id}`);
}
async api_request(
method: string,
path: string,
extraHeaders: object = {},
data: object = {},
) {
const mize = this.mize;
if (!path.startsWith("/")) {
path = "/" + path;
}
const headers = Object.assign(
{
"Content-Type": "application/json",
"x-api-user": mize.get_config("habitica.user_id"),
"x-api-key": mize.get_config("habitica.api_token"),
"x-client": mize.get_config("habitica.client_name"),
},
extraHeaders,
);
const response = await fetch(mize.get_config("habitica.api_url") + path, {
method,
headers,
body: JSON.stringify(data),
});
await handleRateLimit(response);
if (!response.ok) {
console.log("request_url:", mize.get_config("habitica.api_url") + path);
throw new Error(
`Habitica API error: ${response.status} ${response.statusText}`,
);
}
return (await response.json()).data;
}
}

View File

@@ -4,6 +4,9 @@ use deno_core::OpState;
use mize::{Mize, MizeError};
use std::sync::Arc;
use crate::CliPart;
use crate::JsPart;
#[op2(fast)]
fn op_mize_get_part(#[string] name: &str) {
println!("js wants the part {}", name);
@@ -22,6 +25,21 @@ fn op_mize_add_part(state: &mut OpState, #[string] key: &str) {
println!("adding part {}", key);
}
#[op2(fast)]
fn op_cli_subcommand(state: &mut OpState, #[string] name: String) {
let mut mize: Mize = state.borrow::<Mize>().clone();
let mut cli = mize.get_part_native::<CliPart>("cli").unwrap();
cli.subcommand(
clap::Command::new(name.clone()),
move |_sub_matches, mut mize| {
let mut js = mize.get_part_native::<JsPart>("js").unwrap();
js.eval("log(\"cli command being called\")".to_string())?;
js.eval(format!("mize.vars.cli.subcommand({name})"))?;
Ok(())
},
);
}
// Create an extension with your custom ops
extension!(
my_extension,

View File

@@ -0,0 +1,11 @@
import { Mize } from "./stub_deno.ts";
function argsToMessage(...args) {
return args.map((arg) => JSON.stringify(arg)).join(" ");
}
globalThis.log = (...args) => {
Deno.core.print(`[out]: ${argsToMessage(...args)}\n`, false);
};
globalThis.mize = new Mize();

View File

@@ -1,6 +1,10 @@
use deno_core::error::AnyError;
use deno_core::url::Url;
use deno_core::{extension, op2, JsRuntime, ModuleSpecifier, PollEventLoopOptions, RuntimeOptions};
use deno_core::v8::OneByteConst;
use deno_core::{
ascii_str_include, extension, op2, JsRuntime, ModuleSpecifier, PollEventLoopOptions,
RuntimeOptions,
};
use deno_core::{FastStaticString, FsModuleLoader};
use mize::async_trait;
use mize::instance::MizePartCreate;
@@ -19,35 +23,36 @@ mod glue_deno;
#[derive(Default)]
pub struct JsPart {
mize: Mize,
closure_sender: Option<flume::Sender<Box<dyn FnOnce(&mut JsRuntime) -> MizeResult<()> + Send>>>,
async_closure_sender: Option<flume::Sender<BoxClosure>>,
sender: Option<flume::Sender<JsRuntimeThreadMessage>>,
}
pub type BoxFuture<'a> = Pin<Box<dyn Future<Output = MizeResult<()>> + 'a>>;
pub type BoxClosure = Box<dyn for<'a> FnOnce(&'a mut JsRuntime) -> BoxFuture<'a> + Send + 'static>;
impl MizePart for JsPart {
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
println!("js part init");
fn run(&mut self, mize: &mut Mize) -> MizeResult<()> {
println!("js part run");
self.sender
.as_ref()
.unwrap()
.send(JsRuntimeThreadMessage::DoRunPhase)
.unwrap();
Ok(())
}
}
pub fn js(mize: &mut Mize) -> MizeResult<()> {
let (closure_sender, closure_receiver) =
flume::unbounded::<Box<dyn FnOnce(&mut JsRuntime) -> MizeResult<()> + Send>>();
let (async_closure_sender, async_closure_receiver) = flume::unbounded::<BoxClosure>();
let (sender, receiver) = flume::unbounded::<JsRuntimeThreadMessage>();
let mize_clone = mize.clone();
// the thread which will run any js
mize.spawn("js_runtime_thread", || {
js_runtime_thread(mize_clone, closure_receiver, async_closure_receiver)
mize.spawn_and_wait("js_runtime_thread", || {
js_runtime_thread(mize_clone, receiver)
})?;
mize.add_part(Box::new(JsPart {
mize: mize.clone(),
closure_sender: Some(closure_sender),
async_closure_sender: Some(async_closure_sender),
sender: Some(sender),
}))
}
@@ -57,169 +62,75 @@ pub fn part_from_file(
code: FastStaticString,
) -> MizeResult<()> {
let mut js = mize.get_part_native::<JsPart>("js")?;
js.with_runtime_async(move |runtime: &mut JsRuntime| {
let module_spec = ModuleSpecifier::from_str(name).unwrap();
return Box::pin(async move {
runtime
.load_side_es_module_from_code(&module_spec, code)
.await;
Ok(())
});
});
js.execute_init_js(code)?;
Ok(())
}
impl JsPart {
/*
pub fn part_from_js_file(
&mut self,
name: &'static str,
path: &'static str,
) -> Box<dyn MizePart + Send + Sync> {
Box::new(PartFromJsFileAdapter {
mize: self.mize.clone(),
name,
js_file: path,
})
}
*/
pub fn with_runtime<T: FnOnce(&mut JsRuntime) -> MizeResult<()> + Send + 'static>(
&mut self,
func: T,
) {
self.closure_sender
.as_mut()
pub fn execute_init_js(&mut self, code: FastStaticString) -> MizeResult<()> {
self.sender
.as_ref()
.unwrap()
.send(Box::new(func))
.send(JsRuntimeThreadMessage::RunInitJs(code))
.unwrap();
}
pub fn with_runtime_async<F>(&mut self, func: F)
where
F: for<'a> FnOnce(&'a mut JsRuntime) -> BoxFuture<'a> + Send + 'static,
{
self.async_closure_sender
.as_mut()
.unwrap()
.send(Box::new(func))
.unwrap();
}
}
/*
#[mize_part]
#[derive(Default)]
struct PartFromJsFileAdapter {
mize: Mize,
name: &'static str,
js_file: &'static str,
}
#[async_trait]
impl MizePart for PartFromJsFileAdapter {
fn deps(&self) -> &'static [&'static str] {
&["js"]
}
fn name(&self) -> &'static str {
self.name
}
async fn async_init(&mut self, mize: &mut Mize) -> MizeResult<()> {
let js = mize.get_part_native::<JsPart>("js")?;
let path = self.js_file;
let js_code = format!(
r#"
import {{ opts, deps, create }} from "{path}";
const isPromise = (value) => {{
return !!value && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function';
}};
// Call the async function
let create_result = create(mize);
if (isPromise(create_result)) {{
create_result = await create_result;
}}
const result = {{
opts: opts(mize),
deps: deps(mize),
create: create_result,
}};
result;
"#
);
// Execute the script
let result = js.runtime().execute_script("[main]", js_code)?;
// Resolve the promise and run event loop
let resolved_value = js.runtime().resolve_value(result).await?;
// Run the event loop to completion
js.runtime()
.run_event_loop(PollEventLoopOptions::default())
.await?;
println!("Execution of part {} completed successfully", self.name());
Ok(())
}
async fn async_run(&mut self, mize: &mut Mize) -> MizeResult<()> {
let js = mize.get_part_native::<JsPart>("js")?;
let path = self.js_file;
let js_code = format!(
r#"
import {{ run }} from "{path}";
const result = run()
result;
"#
);
// Execute the script
let result = js.runtime().execute_script("[main]", js_code)?;
// Resolve the promise and run event loop
let resolved_value = js.runtime().resolve_value(result).await?;
// Run the event loop to completion
js.runtime()
.run_event_loop(PollEventLoopOptions::default())
.await?;
println!("Running of part {} completed successfully", self.name());
pub fn eval(&mut self, code: String) -> MizeResult<()> {
self.sender
.as_ref()
.unwrap()
.send(JsRuntimeThreadMessage::RunJs(code))
.unwrap();
Ok(())
}
}
*/
fn js_runtime_thread(
mize: Mize,
closure_receiver: flume::Receiver<Box<dyn FnOnce(&mut JsRuntime) -> MizeResult<()> + Send>>,
async_closure_receiver: flume::Receiver<BoxClosure>,
receiver: flume::Receiver<JsRuntimeThreadMessage>,
) -> MizeResult<()> {
let mut js_runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(Rc::new(FsModuleLoader)),
extensions: vec![glue_deno::my_extension::init(mize.clone())],
//module_loader: Some(Rc::new(FsModuleLoader)),
//extensions: vec![glue_deno::my_extension::init(mize.clone())],
..Default::default()
});
let poll_opts = PollEventLoopOptions::default();
js_runtime
.execute_script("[stub]", ascii_str_include!("../deno_dist/glue_deno.js"))
.unwrap();
let tokio_runtime = Builder::new_current_thread().enable_all().build().unwrap();
let local = LocalSet::new();
local.block_on(&tokio_runtime, async move {
loop {
// Drain queued closures quickly (no await here if possible)
if let Ok(func) = closure_receiver.recv_async().await {
if let Err(e) = func(&mut js_runtime) {
mize.report_err(e);
loop {
println!("js thread waiting for smth");
let msg = receiver.recv().unwrap();
println!("js thread got smth");
match msg {
JsRuntimeThreadMessage::RunInitJs(js_code) => {
if let Err(err) = js_runtime.execute_script("[init]", js_code) {
println!("err: {}", err);
}
println!("done running js");
}
JsRuntimeThreadMessage::RunJs(js_code) => {
if let Err(err) = js_runtime.execute_script("[idk]", js_code) {
println!("err: {}", err);
}
println!("done running js");
}
JsRuntimeThreadMessage::DoRunPhase => {
if let Err(err) = js_runtime.execute_script("[runPhase]", "mize.runPhase()") {
println!("err: {}", err);
}
println!("done with runPhase");
return Ok(());
}
// Drive JS one tick
let _ = futures::future::poll_fn(|cx| js_runtime.poll_event_loop(cx, poll_opts)).await;
}
});
Ok(())
}
}
enum JsRuntimeThreadMessage {
//Closure(Box<dyn FnOnce(&mut JsRuntime) -> MizeResult<()> + Send>),
//AsyncClosure(BoxClosure),
RunInitJs(FastStaticString),
RunJs(String),
DoRunPhase,
}

View File

@@ -5,6 +5,9 @@ export class Mize {
async add_opts(opts: any): Promise<void> {}
async add_part(part: any): Promise<void> {}
async report_err(err: MizeError) {}
runPhase() {
Deno.core.print("runPhase in js \n");
}
}
type MizeError = string;

View File

@@ -5,18 +5,10 @@ mod cli;
#[cfg(feature = "target-os")]
pub use cli::*;
#[cfg(feature = "target-os")]
pub mod habitica;
#[cfg(feature = "target-os")]
pub use habitica::*;
pub mod js;
pub use js::*;
#[cfg(feature = "target-os")]
pub fn habitica(mize: &mut mize::Mize) -> MizeResult<()> {
use deno_core::ascii_str_include;
js::part_from_file(
mize,
"habitica",
ascii_str_include!("./deno_dist/habitica.js"),
)?;
Ok(())
}

View File

@@ -270,9 +270,23 @@ rec {
#_shell_type = "rust";
nativeBuildInputs = attrs.nativeBuildInputs or [] ++ [
pkgs.wasm-pack
pkgs.dioxus-cli
pkgs.spacetimedb
pkgs.deno
pkgs.pkg-config
pkgs.wasm-bindgen-cli
(pkgs.buildWasmBindgenCli rec {
src = pkgs.fetchCrate {
pname = "wasm-bindgen-cli";
version = "0.2.114";
hash = "sha256-xrCym+rFY6EUQFWyWl6OPA+LtftpUAE5pIaElAIVqW0=";
};
cargoDeps = pkgs.rustPlatform.fetchCargoVendor {
inherit src;
inherit (src) pname version;
hash = "sha256-Z8+dUXPQq7S+Q7DWNr2Y9d8GMuEdSnq00quUR0wDNPM=";
};
})
(fenix.packages."x86_64-linux".combine [
fenix.packages."x86_64-linux".latest.toolchain
fenix.packages."x86_64-linux".targets.wasm32-unknown-unknown.latest.toolchain

View File

@@ -78,7 +78,7 @@ pub struct Mize {
// TODO: set to a random uuid
pub(crate) self_namespace: Arc<Mutex<Namespace>>,
pub(crate) op_tx: Sender<Operation>,
threads: Arc<Mutex<Vec<(u32, String)>>>,
threads: Arc<Mutex<Vec<(u32, String, Option<JoinHandle<MizeResult<()>>>)>>>,
next_thread_id: Arc<Mutex<u32>>,
give_msg_wait: Arc<Mutex<HashMap<MizeId, Vec<Sender<ItemData>>>>>,
create_msg_wait: Arc<Mutex<Option<Sender<MizeId>>>>,
@@ -254,11 +254,11 @@ impl Mize {
let instance_clone = instance.clone();
let op_rx_clone = op_rx.clone();
let closure = move || updater_thread(op_rx_clone, &instance_clone);
instance.spawn("updater_thread", closure)?;
instance.spawn_background("updater_thread", closure)?;
let instance_clone_two = instance.clone();
let closure_two = move || updater_thread(op_rx, &instance_clone_two);
instance.spawn("updater_thread", closure_two)?;
instance.spawn_background("updater_thread", closure_two)?;
}
// set up async update "threads" when using wasm
@@ -569,7 +569,7 @@ impl Mize {
pub fn add_listener<T: ConnListener + 'static>(&mut self, listener: T) -> MizeResult<()> {
let mut instance_clone = self.clone();
self.spawn("some_listener", move || listener.listen(instance_clone));
self.spawn_background("some_listener", move || listener.listen(instance_clone));
Ok(())
}
@@ -667,7 +667,59 @@ impl Mize {
));
}
pub fn spawn(
pub fn spawn_and_wait(
&mut self,
name: &str,
func: impl FnOnce() -> MizeResult<()> + Send + 'static,
) -> MizeResult<()> {
let mize_clone = self.clone();
let mut threads_inner = self.threads.lock()?;
let mut next_thread_id = self.next_thread_id.lock()?;
let my_thread_id_no_mutex_guard = *next_thread_id;
let thread_mutex = self.threads.clone();
let name_to_move = name.to_owned();
let to_spawn = move || -> MizeResult<()> {
debug!("spawning thread: {}", name_to_move);
let my_thread_id = my_thread_id_no_mutex_guard;
if let Err(err) = func() {
mize_clone.report_err(err);
}
//let mut threads_inner = thread_mutex.lock()?;
/*
*threads_inner = threads_inner
.clone()
.into_iter()
.filter(|el| match el {
(my_thread_id, _) => false,
(_, _) => true,
})
.collect();
*/
debug!("thread '{}' stopped", name_to_move);
Ok(())
};
*next_thread_id += 1;
#[cfg(feature = "target-os")]
let handle = thread::spawn(move || to_spawn());
threads_inner.push((*next_thread_id, name.to_owned(), Some(handle)));
#[cfg(feature = "target-wasm ")]
{
//console_log!("in instance::spawn with wasm target")
}
//NOT WELL SUPPORTED
//crate::platform::wasm::wasm_spawn(to_spawn)?;
Ok(())
}
pub fn spawn_background(
&mut self,
name: &str,
func: impl FnOnce() -> MizeResult<()> + Send + 'static,
@@ -675,8 +727,6 @@ impl Mize {
let mut threads_inner = self.threads.lock()?;
let mut next_thread_id = self.next_thread_id.lock()?;
threads_inner.push((*next_thread_id, name.to_owned()));
let my_thread_id_no_mutex_guard = *next_thread_id;
let thread_mutex = self.threads.clone();
let name_to_move = name.to_owned();
@@ -687,6 +737,7 @@ impl Mize {
func()?;
let mut threads_inner = thread_mutex.lock()?;
/*
*threads_inner = threads_inner
.clone()
.into_iter()
@@ -695,6 +746,7 @@ impl Mize {
(_, _) => true,
})
.collect();
*/
debug!("thread '{}' stopped", name_to_move);
Ok(())
};
@@ -704,6 +756,8 @@ impl Mize {
#[cfg(feature = "target-os")]
thread::spawn(move || to_spawn());
threads_inner.push((*next_thread_id, name.to_owned(), None));
#[cfg(feature = "target-wasm ")]
{
//console_log!("in instance::spawn with wasm target")
@@ -747,7 +801,7 @@ impl Mize {
let mut threads_inner = self.threads.lock()?;
let mut next_thread_id = self.next_thread_id.lock()?;
threads_inner.push((*next_thread_id, name.to_owned()));
threads_inner.push((*next_thread_id, name.to_owned(), None));
let runtime_inner = self.runtime.lock().unwrap();
runtime_inner.spawn(func);
@@ -780,7 +834,7 @@ impl Mize {
.lock()
.expect("mutex lock failed in spawn_async_blocking");
threads_inner.push((*next_thread_id, name.to_owned()));
threads_inner.push((*next_thread_id, name.to_owned(), None));
let runtime_inner = self.runtime.lock().unwrap();
let handle = runtime_inner.handle().to_owned();
*next_thread_id += 1;
@@ -849,6 +903,12 @@ impl Mize {
for part in parts.values_mut() {
part.as_deref_mut().unwrap().run(&mut self.clone())?;
}
let mut threads_inner = self.threads.lock().unwrap();
for (id, name, handle) in threads_inner.drain(..) {
if let Some(handle) = handle {
handle.join().unwrap()?;
}
}
Ok(())
}

View File

@@ -58,7 +58,7 @@ async fn connect_async(mut instance: Mize, store_path: PathBuf) -> MizeResult<u6
let conn_id = instance.new_connection(send_tx)?;
let cloned_instance = instance.clone();
instance.spawn("incomming", move || {
instance.spawn_background("incomming", move || {
let result = unix_incomming(unix_read, cloned_instance, conn_id);
// if unix incomming fails, close the connection
if let Err(err) = result {
@@ -68,7 +68,7 @@ async fn connect_async(mut instance: Mize, store_path: PathBuf) -> MizeResult<u6
});
let outgoing_cloned_instance = instance.clone();
instance.spawn("outgoing", move || {
instance.spawn_background("outgoing", move || {
let result = unix_outgoing(unix_write, send_rx, outgoing_cloned_instance, conn_id);
// if writing fails, close this connection
if let Err(err) = result {
@@ -108,7 +108,7 @@ async fn unix_listen(listener: UnixListener, mut instance: Mize) -> MizeResult<(
let conn_id = instance.new_connection(send_tx)?;
let cloned_instance = instance.clone();
instance.spawn("incomming", move || {
instance.spawn_background("incomming", move || {
let result = unix_incomming(unix_read, cloned_instance, conn_id);
// if unix incomming fails, close the connection
if let Err(err) = result {
@@ -118,7 +118,7 @@ async fn unix_listen(listener: UnixListener, mut instance: Mize) -> MizeResult<(
});
let outgoing_cloned_instance = instance.clone();
instance.spawn("outgoing", move || {
instance.spawn_background("outgoing", move || {
let result = unix_outgoing(unix_write, send_rx, outgoing_cloned_instance, conn_id);
// if writing fails, close this connection
if let Err(err) = result {

View File

@@ -16,7 +16,7 @@ fn main() {
#[cfg(feature = "target-os")]
fn os_main(mize: &mut Mize) -> MizeResult<()> {
marts::cli(mize)?;
marts::js(mize)?;
//marts::js(mize)?;
marts::habitica(mize)?;
let mut cli = mize.get_part_native::<marts::CliPart>("cli")?;
@@ -29,7 +29,7 @@ fn os_main(mize: &mut Mize) -> MizeResult<()> {
.about("the ppc desktop program"))
})?;
cli.subcommand(Command::new("test"), |_| {
cli.subcommand(Command::new("test"), |_, _| {
println!("test ppc...");
Ok(())
});