chore: I can open a window. wtf!

This commit is contained in:
James Harton 2024-05-15 10:16:58 +12:00
commit a898d370ac
Signed by: james
GPG key ID: 90E82DAA13F624F4
23 changed files with 3841 additions and 0 deletions

4
.formatter.exs Normal file
View file

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

26
.gitignore vendored Normal file
View 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
View file

@ -0,0 +1,3 @@
rust 1.78.0
elixir 1.16.2
erlang 26.2.5

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"cSpell.words": ["Renderling"]
}

20
README.md Normal file
View 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>.

View 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

View 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

View 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
View 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
View 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"},
}

View 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
View file

@ -0,0 +1 @@
/target

3449
native/renderling_window/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View 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"

View 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.

View 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
);

View 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>,
}

View 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();
}
}

Binary file not shown.

Binary file not shown.

BIN
priv/native/server Executable file

Binary file not shown.

View 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
View file

@ -0,0 +1 @@
ExUnit.start()