chore: I can open a window. wtf!
This commit is contained in:
commit
a898d370ac
4
.formatter.exs
Normal file
4
.formatter.exs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Used by "mix format"
|
||||||
|
[
|
||||||
|
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# The directory Mix will write compiled artifacts to.
|
||||||
|
/_build/
|
||||||
|
|
||||||
|
# If you run "mix test --cover", coverage assets end up here.
|
||||||
|
/cover/
|
||||||
|
|
||||||
|
# The directory Mix downloads your dependencies sources to.
|
||||||
|
/deps/
|
||||||
|
|
||||||
|
# Where third-party dependencies like ExDoc output generated docs.
|
||||||
|
/doc/
|
||||||
|
|
||||||
|
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||||
|
/.fetch
|
||||||
|
|
||||||
|
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||||
|
erl_crash.dump
|
||||||
|
|
||||||
|
# Also ignore archive artifacts (built via "mix archive.build").
|
||||||
|
*.ez
|
||||||
|
|
||||||
|
# Ignore package tarball (built via "mix hex.build").
|
||||||
|
scenic_driver_renderling-*.tar
|
||||||
|
|
||||||
|
# Temporary files, for example, from tests.
|
||||||
|
/tmp/
|
3
.tool-versions
Normal file
3
.tool-versions
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
rust 1.78.0
|
||||||
|
elixir 1.16.2
|
||||||
|
erlang 26.2.5
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"cSpell.words": ["Renderling"]
|
||||||
|
}
|
20
README.md
Normal file
20
README.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Scenic.Driver.Renderling
|
||||||
|
|
||||||
|
**TODO: Add description**
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||||
|
by adding `scenic_driver_renderling` to your list of dependencies in `mix.exs`:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
def deps do
|
||||||
|
[
|
||||||
|
{:scenic_driver_renderling, "~> 0.1.0"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||||
|
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||||
|
be found at <https://hexdocs.pm/scenic_driver_renderling>.
|
11
lib/scenic_driver_rendering/window/config.ex
Normal file
11
lib/scenic_driver_rendering/window/config.ex
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
defmodule Scenic.Driver.Renderling.Window.Config do
|
||||||
|
defstruct [:title, :resizeable, :width, :height, :server_path]
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
title: String.t(),
|
||||||
|
resizeable: boolean,
|
||||||
|
width: non_neg_integer(),
|
||||||
|
height: non_neg_integer(),
|
||||||
|
server_path: String.t()
|
||||||
|
}
|
||||||
|
end
|
5
lib/scenic_driver_rendering/window/nif.ex
Normal file
5
lib/scenic_driver_rendering/window/nif.ex
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
defmodule Scenic.Driver.Renderling.Window.Nif do
|
||||||
|
use Rustler, otp_app: :scenic_driver_renderling, crate: :renderling_window
|
||||||
|
|
||||||
|
def init(_cfg), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
end
|
18
lib/scenic_driver_renderling.ex
Normal file
18
lib/scenic_driver_renderling.ex
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
defmodule Scenic.Driver.Renderling do
|
||||||
|
@moduledoc """
|
||||||
|
Documentation for `Scenic.Driver.Renderling`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Hello world.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Scenic.Driver.Renderling.hello()
|
||||||
|
:world
|
||||||
|
|
||||||
|
"""
|
||||||
|
def hello do
|
||||||
|
:world
|
||||||
|
end
|
||||||
|
end
|
29
mix.exs
Normal file
29
mix.exs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
defmodule Scenic.Driver.Renderling.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :scenic_driver_renderling,
|
||||||
|
version: "0.1.0",
|
||||||
|
elixir: "~> 1.16",
|
||||||
|
start_permanent: Mix.env() == :prod,
|
||||||
|
deps: deps()
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run "mix help compile.app" to learn about applications.
|
||||||
|
def application do
|
||||||
|
[
|
||||||
|
extra_applications: [:logger]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run "mix help deps" to learn about dependencies.
|
||||||
|
defp deps do
|
||||||
|
[
|
||||||
|
{:rustler, "~> 0.32", runtime: false}
|
||||||
|
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||||
|
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
5
mix.lock
Normal file
5
mix.lock
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
%{
|
||||||
|
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
|
||||||
|
"rustler": {:hex, :rustler, "0.32.1", "f4cf5a39f9e85d182c0a3f75fa15b5d0add6542ab0bf9ceac6b4023109ebd3fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "b96be75526784f86f6587f051bc8d6f4eaff23d6e0f88dbcfe4d5871f52946f7"},
|
||||||
|
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
|
||||||
|
}
|
5
native/renderling_window/.cargo/config.toml
Normal file
5
native/renderling_window/.cargo/config.toml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[target.'cfg(target_os = "macos")']
|
||||||
|
rustflags = [
|
||||||
|
"-C", "link-arg=-undefined",
|
||||||
|
"-C", "link-arg=dynamic_lookup",
|
||||||
|
]
|
1
native/renderling_window/.gitignore
vendored
Normal file
1
native/renderling_window/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
3449
native/renderling_window/Cargo.lock
generated
Normal file
3449
native/renderling_window/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
24
native/renderling_window/Cargo.toml
Normal file
24
native/renderling_window/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
[package]
|
||||||
|
authors = []
|
||||||
|
edition = "2021"
|
||||||
|
name = "renderling_window"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
name = "renderling_window"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
clap = {version = "4", features = ["derive"]}
|
||||||
|
ipc-channel = "0.18.0"
|
||||||
|
renderling = "0.4.1"
|
||||||
|
rustler = "0.32.1"
|
||||||
|
serde = {version = "1", features = ["derive"]}
|
||||||
|
wgpu = "0.20.0"
|
||||||
|
winit = "0.30.0"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "server"
|
||||||
|
path = "src/server.rs"
|
20
native/renderling_window/README.md
Normal file
20
native/renderling_window/README.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# NIF for Elixir.Scenic.Driver.Renderling.Window
|
||||||
|
|
||||||
|
## To build the NIF module:
|
||||||
|
|
||||||
|
- Your NIF will now build along with your project.
|
||||||
|
|
||||||
|
## To load the NIF:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
defmodule Scenic.Driver.Renderling.Window do
|
||||||
|
use Rustler, otp_app: :scenic_driver_rendering, crate: "renderling_window"
|
||||||
|
|
||||||
|
# When your NIF is loaded, it will override this function.
|
||||||
|
def add(_a, _b), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
[This](https://github.com/rusterlium/NifIo) is a complete example of a NIF written in Rust.
|
79
native/renderling_window/src/lib.rs
Normal file
79
native/renderling_window/src/lib.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
mod message;
|
||||||
|
|
||||||
|
use crate::message::{IpcExchange, Message};
|
||||||
|
use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender};
|
||||||
|
use rustler::types::OwnedBinary;
|
||||||
|
use rustler::{Atom, Env, Error, NifStruct, ResourceArc, Term};
|
||||||
|
use std::env;
|
||||||
|
use std::io::stdout;
|
||||||
|
use std::process::{Child, Command, Stdio};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
#[derive(NifStruct)]
|
||||||
|
#[module = "Scenic.Driver.Renderling.Window.Config"]
|
||||||
|
pub struct WindowConfig {
|
||||||
|
pub title: String,
|
||||||
|
pub resizeable: bool,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub server_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IpcResourceInner {
|
||||||
|
ipc_channel_name: String,
|
||||||
|
sender: IpcSender<Message>,
|
||||||
|
receiver: IpcReceiver<Message>,
|
||||||
|
handle: Child,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for IpcResource {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
println!("Shutting down server.");
|
||||||
|
let mut mutex = self.0.lock().unwrap();
|
||||||
|
// mutex.sender.send(Message::Shutdown).unwrap();
|
||||||
|
mutex.handle.try_wait().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IpcResource(Mutex<IpcResourceInner>);
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn init(cfg: WindowConfig) -> ResourceArc<IpcResource> {
|
||||||
|
let (ipc, ipc_channel_name) = IpcOneShotServer::<IpcExchange>::new().unwrap();
|
||||||
|
|
||||||
|
let handle = Command::new(cfg.server_path)
|
||||||
|
.arg("--ipc-channel")
|
||||||
|
.arg(&ipc_channel_name)
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.spawn()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (_, IpcExchange { sender, receiver }) = ipc.accept().unwrap();
|
||||||
|
|
||||||
|
sender
|
||||||
|
.send(Message::Init {
|
||||||
|
title: cfg.title,
|
||||||
|
resizeable: cfg.resizeable,
|
||||||
|
width: cfg.width,
|
||||||
|
height: cfg.height,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
ResourceArc::new(IpcResource(Mutex::new(IpcResourceInner {
|
||||||
|
ipc_channel_name,
|
||||||
|
handle,
|
||||||
|
sender,
|
||||||
|
receiver,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(env: Env, _: Term) -> bool {
|
||||||
|
rustler::resource!(IpcResource, env);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
rustler::init!(
|
||||||
|
"Elixir.Scenic.Driver.Renderling.Window.Nif",
|
||||||
|
[init],
|
||||||
|
load = load
|
||||||
|
);
|
19
native/renderling_window/src/message.rs
Normal file
19
native/renderling_window/src/message.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use ipc_channel::ipc::{IpcReceiver, IpcSender};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
Shutdown,
|
||||||
|
Init {
|
||||||
|
title: String,
|
||||||
|
resizeable: bool,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct IpcExchange {
|
||||||
|
pub sender: IpcSender<Message>,
|
||||||
|
pub receiver: IpcReceiver<Message>,
|
||||||
|
}
|
111
native/renderling_window/src/server.rs
Normal file
111
native/renderling_window/src/server.rs
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
mod message;
|
||||||
|
|
||||||
|
use crate::message::{IpcExchange, Message};
|
||||||
|
use clap::Parser;
|
||||||
|
use ipc_channel::ipc;
|
||||||
|
use ipc_channel::ipc::IpcSender;
|
||||||
|
|
||||||
|
use winit::application::ApplicationHandler;
|
||||||
|
use winit::dpi::LogicalSize;
|
||||||
|
use winit::event::WindowEvent;
|
||||||
|
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||||
|
use winit::keyboard::{Key, ModifiersState};
|
||||||
|
use winit::window::{Window, WindowId};
|
||||||
|
|
||||||
|
#[cfg(macos_platform)]
|
||||||
|
use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS};
|
||||||
|
#[cfg(any(x11_platform, wayland_platform))]
|
||||||
|
use winit::platform::startup_notify::{
|
||||||
|
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct CmdArgs {
|
||||||
|
#[arg(long)]
|
||||||
|
ipc_channel: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
let args = CmdArgs::parse();
|
||||||
|
|
||||||
|
println!("Server: Started.");
|
||||||
|
|
||||||
|
let sender = IpcSender::connect(args.ipc_channel)?;
|
||||||
|
|
||||||
|
let (c2s_send, c2s_recv) = ipc::channel()?;
|
||||||
|
let (s2c_send, s2c_recv) = ipc::channel()?;
|
||||||
|
sender.send(IpcExchange {
|
||||||
|
sender: c2s_send,
|
||||||
|
receiver: s2c_recv,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let msg = c2s_recv.recv()?;
|
||||||
|
|
||||||
|
if let &Message::Init { .. } = &msg {
|
||||||
|
let event_loop = EventLoop::<Message>::with_user_event().build()?;
|
||||||
|
let event_loop_proxy = event_loop.create_proxy();
|
||||||
|
|
||||||
|
std::thread::spawn(move || loop {
|
||||||
|
let event = c2s_recv.recv().unwrap();
|
||||||
|
event_loop_proxy.send_event(event).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut state = Application::new(msg);
|
||||||
|
event_loop.run_app(&mut state).map_err(Into::into)
|
||||||
|
} else {
|
||||||
|
panic!("Server: Invalid message received {:?}", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Application {
|
||||||
|
title: String,
|
||||||
|
resizeable: bool,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
window: Option<Window>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application {
|
||||||
|
fn new(event: Message) -> Self {
|
||||||
|
match event {
|
||||||
|
Message::Init {
|
||||||
|
title,
|
||||||
|
resizeable,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
} => Application {
|
||||||
|
title,
|
||||||
|
resizeable,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
window: None,
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplicationHandler<Message> for Application {
|
||||||
|
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: Message) {
|
||||||
|
println!("message event {:?}", event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_event(
|
||||||
|
&mut self,
|
||||||
|
_event_loop: &ActiveEventLoop,
|
||||||
|
_window_id: WindowId,
|
||||||
|
event: WindowEvent,
|
||||||
|
) {
|
||||||
|
println!("window event {:?}", event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
|
let window_attributes = Window::default_attributes()
|
||||||
|
.with_title(self.title.clone())
|
||||||
|
.with_resizable(self.resizeable)
|
||||||
|
.with_inner_size(LogicalSize::new(self.width, self.height));
|
||||||
|
|
||||||
|
self.window = event_loop.create_window(window_attributes).ok();
|
||||||
|
}
|
||||||
|
}
|
BIN
priv/native/librenderling_bridge.so
Executable file
BIN
priv/native/librenderling_bridge.so
Executable file
Binary file not shown.
BIN
priv/native/librenderling_window.so
Executable file
BIN
priv/native/librenderling_window.so
Executable file
Binary file not shown.
BIN
priv/native/server
Executable file
BIN
priv/native/server
Executable file
Binary file not shown.
8
test/scenic_driver_renderling_test.exs
Normal file
8
test/scenic_driver_renderling_test.exs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
defmodule Scenic.Driver.RenderlingTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest Scenic.Driver.Renderling
|
||||||
|
|
||||||
|
test "greets the world" do
|
||||||
|
assert Scenic.Driver.Renderling.hello() == :world
|
||||||
|
end
|
||||||
|
end
|
1
test/test_helper.exs
Normal file
1
test/test_helper.exs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ExUnit.start()
|
Loading…
Reference in a new issue