Compare commits
4 commits
da436d5814
...
64210b1983
Author | SHA1 | Date | |
---|---|---|---|
64210b1983 | |||
b86c37a3b3 | |||
ca30acf8fb | |||
574b91ea11 |
5 changed files with 82 additions and 13 deletions
12
Justfile
Normal file
12
Justfile
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
default:
|
||||||
|
@just --list
|
||||||
|
|
||||||
|
build-rust:
|
||||||
|
cd rust && cargo build
|
||||||
|
|
||||||
|
run-godot:
|
||||||
|
cd learning-rust4.4 && ~/Software/Godot_v4.4-stable_linux.x86_64 .
|
||||||
|
|
||||||
|
run:
|
||||||
|
@just build-rust
|
||||||
|
@just run-godot
|
|
@ -6,6 +6,8 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ashpd = "0.11.0"
|
ashpd = "0.11.0"
|
||||||
gdext_coroutines = {version="0.7.1", features=["async"]}
|
gdext_coroutines = {version="0.7.1", features=["async"]}
|
||||||
|
# Use this specific commit until hot reload is fixed in main
|
||||||
|
# gdext_coroutines = { git="https://github.com/Houtamelo/gdext_coroutines", rev="afd3c7e", features=["async"] }
|
||||||
# godot = {version="0.2.4", features=["api-4-2", "experimental-godot-api", "experimental-threads"]}
|
# godot = {version="0.2.4", features=["api-4-2", "experimental-godot-api", "experimental-threads"]}
|
||||||
godot = "0.2.4"
|
godot = "0.2.4"
|
||||||
godot_tokio = "0.3.0"
|
godot_tokio = "0.3.0"
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
use godot::{classes::Engine, prelude::*};
|
use godot::{classes::Engine, prelude::*};
|
||||||
use godot_tokio::AsyncRuntime;
|
use godot_tokio::AsyncRuntime;
|
||||||
|
use pipewire::Pipewire;
|
||||||
|
|
||||||
mod pipewire;
|
mod pipewire;
|
||||||
mod player;
|
mod player;
|
||||||
|
@ -16,11 +17,14 @@ struct Extension;
|
||||||
#[gdextension]
|
#[gdextension]
|
||||||
unsafe impl ExtensionLibrary for Extension {
|
unsafe impl ExtensionLibrary for Extension {
|
||||||
fn on_level_init(level: InitLevel) {
|
fn on_level_init(level: InitLevel) {
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
panic!("Not supported on Windows");
|
||||||
if level == InitLevel::Scene {
|
if level == InitLevel::Scene {
|
||||||
let mut engine = Engine::singleton();
|
let mut engine = Engine::singleton();
|
||||||
|
|
||||||
// This is where we register our async runtime singleton.
|
// This is where we register our async runtime singleton.
|
||||||
engine.register_singleton(AsyncRuntime::SINGLETON, &AsyncRuntime::new_alloc());
|
engine.register_singleton(AsyncRuntime::SINGLETON, &AsyncRuntime::new_alloc());
|
||||||
|
engine.register_singleton(Pipewire::SINGLETON, &Pipewire::new_alloc());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn on_level_deinit(level: InitLevel) {
|
fn on_level_deinit(level: InitLevel) {
|
||||||
|
@ -28,15 +32,19 @@ unsafe impl ExtensionLibrary for Extension {
|
||||||
let mut engine = Engine::singleton();
|
let mut engine = Engine::singleton();
|
||||||
|
|
||||||
// Here is where we free our async runtime singleton from memory.
|
// Here is where we free our async runtime singleton from memory.
|
||||||
if let Some(async_singleton) = engine.get_singleton(AsyncRuntime::SINGLETON) {
|
Extension::free_singleton(&mut engine, AsyncRuntime::SINGLETON);
|
||||||
engine.unregister_singleton(AsyncRuntime::SINGLETON);
|
Extension::free_singleton(&mut engine, Pipewire::SINGLETON);
|
||||||
async_singleton.free();
|
}
|
||||||
} else {
|
}
|
||||||
godot_warn!(
|
}
|
||||||
"Failed to find & free singleton -> {}",
|
|
||||||
AsyncRuntime::SINGLETON
|
impl Extension {
|
||||||
);
|
fn free_singleton(engine: &mut Engine, name: &str) {
|
||||||
}
|
if let Some(async_singleton) = engine.get_singleton(name) {
|
||||||
|
engine.unregister_singleton(name);
|
||||||
|
async_singleton.free();
|
||||||
|
} else {
|
||||||
|
godot_warn!("Failed to find & free singleton -> {}", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,46 @@
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use godot::prelude::*;
|
use godot::{classes::notify::NodeNotification, prelude::*};
|
||||||
|
use pipewire::{
|
||||||
|
channel::{Receiver, Sender},
|
||||||
|
main_loop::MainLoop,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(GodotClass)]
|
#[derive(GodotClass)]
|
||||||
#[class(base=Node)]
|
#[class(base=Node)]
|
||||||
/// Node for interacting with Pipewire
|
/// Node for interacting with Pipewire
|
||||||
/// Intended to not be spawned manually, as the plugin will create a
|
/// Intended to not be spawned manually, as the plugin will create a
|
||||||
struct Pipewire {
|
pub(crate) struct Pipewire {
|
||||||
base: Base<Node>,
|
base: Base<Node>,
|
||||||
|
|
||||||
/// Handle for the thread running the pipewire main loop
|
/// Handle for the thread running the pipewire main loop
|
||||||
thread_handle: Option<thread::Thread>,
|
thread_handle: Option<thread::JoinHandle<()>>,
|
||||||
|
shutdown_sender: Sender<Terminate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Terminate;
|
||||||
|
|
||||||
#[godot_api]
|
#[godot_api]
|
||||||
impl INode for Pipewire {
|
impl INode for Pipewire {
|
||||||
fn init(base: Base<Node>) -> Self {
|
fn init(base: Base<Node>) -> Self {
|
||||||
|
let (thread, shutdown_sender) = Self::start_pipewire_thread();
|
||||||
Self {
|
Self {
|
||||||
base,
|
base,
|
||||||
thread_handle: None,
|
thread_handle: Some(thread),
|
||||||
|
shutdown_sender,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn on_notification(&mut self, what: NodeNotification) {
|
||||||
|
if what == NodeNotification::PREDELETE
|
||||||
|
|| what == NodeNotification::UNPARENTED
|
||||||
|
|| what == NodeNotification::WM_CLOSE_REQUEST
|
||||||
|
|| what == NodeNotification::EXIT_TREE
|
||||||
|
{
|
||||||
|
godot_print!("Sending shutdown signal to pipewire thread");
|
||||||
|
let _ = self.shutdown_sender.send(Terminate {});
|
||||||
|
let handle = self.thread_handle.take().unwrap();
|
||||||
|
let _join_result = handle.join();
|
||||||
|
godot_print!("Pipewire thread completed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,4 +48,26 @@ impl INode for Pipewire {
|
||||||
#[godot_api]
|
#[godot_api]
|
||||||
impl Pipewire {
|
impl Pipewire {
|
||||||
pub const SINGLETON: &'static str = "Pipewire";
|
pub const SINGLETON: &'static str = "Pipewire";
|
||||||
|
|
||||||
|
fn start_pipewire_thread() -> (thread::JoinHandle<()>, Sender<Terminate>) {
|
||||||
|
godot_print!("Starting pipewire thread");
|
||||||
|
let (shutdown_sender, shutdown_receiver) = pipewire::channel::channel::<Terminate>();
|
||||||
|
let thread_handle = thread::spawn(|| Self::pipewire_main(shutdown_receiver));
|
||||||
|
(thread_handle, shutdown_sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Func that actually runs the main loop in the thread
|
||||||
|
fn pipewire_main(shutdown_receiver: Receiver<Terminate>) {
|
||||||
|
let mainloop = MainLoop::new(None).expect("Failed to create pipewire main loop");
|
||||||
|
|
||||||
|
let _receiver = shutdown_receiver.attach(mainloop.loop_(), {
|
||||||
|
godot_print!("Stopping pipewire thread");
|
||||||
|
let mainloop = mainloop.clone();
|
||||||
|
move |_| mainloop.quit()
|
||||||
|
});
|
||||||
|
|
||||||
|
godot_print!("Entering pipewire main loop");
|
||||||
|
mainloop.run();
|
||||||
|
println!("Pipewire main loop exited");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ struct Runner;
|
||||||
#[godot_api]
|
#[godot_api]
|
||||||
impl INode for ScreencastRunner {
|
impl INode for ScreencastRunner {
|
||||||
fn init(base: Base<Node>) -> Self {
|
fn init(base: Base<Node>) -> Self {
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
panic!("Not supported on Windows");
|
||||||
let (sender, receiver) = channel::unbounded();
|
let (sender, receiver) = channel::unbounded();
|
||||||
Self {
|
Self {
|
||||||
base,
|
base,
|
||||||
|
|
Loading…
Reference in a new issue