use pyo3::prelude::*;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
#[pyclass(name = "AudioEngine", unsendable)]
struct PyAudioEngine {
inner: ling_audio::AudioEngine,
}
#[pymethods]
impl PyAudioEngine {
#[new]
fn new() -> PyResult<Self> {
ling_audio::AudioEngine::new()
.map(|e| PyAudioEngine { inner: e })
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("audio init: {e}")))
}
#[pyo3(signature = (
idx,
x=0.0, y=0.0, z=0.0, w=1.0,
freq=220.0, amp=0.15,
lfo_rate=0.5, lfo_depth=0.02
))]
fn set_tone(
&self,
idx: usize,
x: f32,
y: f32,
z: f32,
w: f32,
freq: f32,
amp: f32,
lfo_rate: f32,
lfo_depth: f32,
) {
self.inner.set_tone(
idx,
ling_audio::ToneParams { x, y, z, w, freq, amp, lfo_rate, lfo_depth },
);
}
fn clear_tone(&self, idx: usize) {
self.inner.clear_tone(idx);
}
fn set_listener(&self, cry: f32, sry: f32, crx: f32, srx: f32) {
self.inner.set_listener(cry, sry, crx, srx);
}
fn load_bgm(&self, path: &str, vol: f32) {
self.inner.load_bgm(path, vol);
}
fn set_bgm_volume(&self, vol: f32) {
self.inner.set_bgm_volume(vol);
}
fn set_master_volume(&self, vol: f32) {
self.inner.set_master_volume(vol);
}
}
use glam::Vec3;
use ling_physics::hyperbolic::HyperbolicSphereWorld;
#[pyclass(name = "HyperbolicWorld")]
struct PyHyperbolicWorld {
inner: HyperbolicSphereWorld,
}
#[pymethods]
impl PyHyperbolicWorld {
#[new]
#[pyo3(signature = (radius=100.0, curvature=-1.0, gravity=9.81))]
fn new(radius: f32, curvature: f32, gravity: f32) -> Self {
PyHyperbolicWorld { inner: HyperbolicSphereWorld { radius, curvature, gravity } }
}
fn gravity_dir(&self, x: f32, y: f32, z: f32) -> (f32, f32, f32) {
let v = self.inner.gravity_dir(Vec3::new(x, y, z));
(v.x, v.y, v.z)
}
fn gravity_force(&self, x: f32, y: f32, z: f32, mass: f32) -> (f32, f32, f32) {
let v = self.inner.gravity_force(Vec3::new(x, y, z), mass);
(v.x, v.y, v.z)
}
fn up_at(&self, x: f32, y: f32, z: f32) -> (f32, f32, f32) {
let v = self.inner.up_at(Vec3::new(x, y, z));
(v.x, v.y, v.z)
}
fn world_distance(&self, ax: f32, ay: f32, az: f32, bx: f32, by: f32, bz: f32) -> f32 {
self.inner
.world_distance(Vec3::new(ax, ay, az), Vec3::new(bx, by, bz))
}
fn to_poincare(&self, x: f32, y: f32, z: f32) -> (f32, f32, f32) {
let v = self.inner.to_poincare(Vec3::new(x, y, z));
(v.x, v.y, v.z)
}
fn from_poincare(&self, x: f32, y: f32, z: f32) -> (f32, f32, f32) {
let v = self.inner.from_poincare(Vec3::new(x, y, z));
(v.x, v.y, v.z)
}
}
#[pyfunction]
fn hyp_distance(ax: f32, ay: f32, az: f32, bx: f32, by: f32, bz: f32) -> f32 {
ling_physics::hyperbolic::distance(Vec3::new(ax, ay, az), Vec3::new(bx, by, bz))
}
#[pyfunction]
fn exp_map(bx: f32, by: f32, bz: f32, vx: f32, vy: f32, vz: f32) -> (f32, f32, f32) {
let v = ling_physics::hyperbolic::exp_map(Vec3::new(bx, by, bz), Vec3::new(vx, vy, vz));
(v.x, v.y, v.z)
}
#[pyfunction]
fn log_map(bx: f32, by: f32, bz: f32, tx: f32, ty: f32, tz: f32) -> (f32, f32, f32) {
let v = ling_physics::hyperbolic::log_map(Vec3::new(bx, by, bz), Vec3::new(tx, ty, tz));
(v.x, v.y, v.z)
}
#[pyclass(name = "NetClient")]
struct PyNetClient {
outbox: Arc<Mutex<Vec<String>>>,
inbox: Arc<Mutex<VecDeque<String>>>,
connected: Arc<std::sync::atomic::AtomicBool>,
}
#[pymethods]
impl PyNetClient {
#[new]
fn new() -> Self {
PyNetClient {
outbox: Arc::new(Mutex::new(Vec::new())),
inbox: Arc::new(Mutex::new(VecDeque::new())),
connected: Arc::new(std::sync::atomic::AtomicBool::new(false)),
}
}
fn connect(&self, url: String) {
let outbox = Arc::clone(&self.outbox);
let inbox = Arc::clone(&self.inbox);
let connected = Arc::clone(&self.connected);
std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("tokio runtime build");
rt.block_on(ws_run(url, outbox, inbox, connected));
});
}
fn send(&self, msg: String) {
if let Ok(mut ob) = self.outbox.lock() {
ob.push(msg);
}
}
fn try_recv(&self) -> Option<String> {
self.inbox.lock().ok()?.pop_front()
}
fn is_connected(&self) -> bool {
self.connected.load(std::sync::atomic::Ordering::Relaxed)
}
}
async fn ws_run(
url: String,
outbox: Arc<Mutex<Vec<String>>>,
inbox: Arc<Mutex<VecDeque<String>>>,
connected: Arc<std::sync::atomic::AtomicBool>,
) {
use futures_util::{SinkExt, StreamExt};
use tokio::time::{sleep, Duration};
use tokio_tungstenite::{connect_async, tungstenite::Message};
let ws = match connect_async(&url).await {
Ok((ws, _)) => ws,
Err(e) => {
eprintln!("[ling-py netplay] connect failed ({url}): {e}");
return;
},
};
connected.store(true, std::sync::atomic::Ordering::Relaxed);
eprintln!("[ling-py netplay] connected to {url}");
let (mut write, mut read) = ws.split();
loop {
let msgs: Vec<String> = if let Ok(mut ob) = outbox.lock() {
std::mem::take(&mut *ob)
} else {
vec![]
};
for m in msgs {
if write.send(Message::Text(m)).await.is_err() {
break;
}
}
tokio::select! {
frame = read.next() => {
match frame {
Some(Ok(Message::Text(s))) => {
if let Ok(mut ib) = inbox.lock() {
ib.push_back(s.to_string());
while ib.len() > 128 { ib.pop_front(); }
}
}
Some(Ok(_)) => {} _ => break, }
}
_ = sleep(Duration::from_millis(1)) => {}
}
}
connected.store(false, std::sync::atomic::Ordering::Relaxed);
eprintln!("[ling-py netplay] disconnected from {url}");
}
#[pymodule]
fn ling_py(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyAudioEngine>()?;
m.add_class::<PyHyperbolicWorld>()?;
m.add_class::<PyNetClient>()?;
m.add_function(wrap_pyfunction!(hyp_distance, m)?)?;
m.add_function(wrap_pyfunction!(exp_map, m)?)?;
m.add_function(wrap_pyfunction!(log_map, m)?)?;
Ok(())
}