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