Compare commits
10 Commits
6abe5b467e
...
f59c3591cb
Author | SHA1 | Date |
---|---|---|
Jarrod Doyle | f59c3591cb | |
Jarrod Doyle | 3cb044cde2 | |
Jarrod Doyle | 6f8c99ed4c | |
Jarrod Doyle | 44ddda9736 | |
Jarrod Doyle | 290edc26d3 | |
Jarrod Doyle | 4df690e49e | |
Jarrod Doyle | b0589c4b9e | |
Jarrod Doyle | b884af0380 | |
Jarrod Doyle | 1e610991be | |
Jarrod Doyle | 58064c4fc1 |
|
@ -1501,6 +1501,7 @@ dependencies = [
|
|||
"log",
|
||||
"pollster",
|
||||
"rune",
|
||||
"walkdir",
|
||||
"wgpu",
|
||||
"winit",
|
||||
]
|
||||
|
|
|
@ -11,5 +11,6 @@ env_logger = "0.11.3"
|
|||
log = "0.4.21"
|
||||
pollster = "0.3.0"
|
||||
rune = "0.13.2"
|
||||
walkdir = "2.5.0"
|
||||
wgpu = "0.19.3"
|
||||
winit = "0.29.14"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
Copyright 2024 Jarrod Doyle
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,5 @@
|
|||
# Sigil2D
|
||||
|
||||
A simple and extensible game development framework using Rust and [Rune](https://rune-rs.github.io/).
|
||||
|
||||
> 🚧👷 **Warning** This is _very_ early in development. Usability is poor, and breaking changes are common.
|
|
@ -9,6 +9,6 @@ impl FrameCounter {
|
|||
|
||||
pub fn update(self) {
|
||||
self.frame += 1;
|
||||
println(`Frame: ${self.frame}`);
|
||||
log::info(`Frame: ${self.frame}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use rune::{alloc::clone::TryClone, Vm};
|
||||
use wgpu::Limits;
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
keyboard::KeyCode,
|
||||
window::WindowBuilder,
|
||||
};
|
||||
|
||||
use crate::{gfx::Context, input::Input, scripting};
|
||||
|
||||
pub struct GameConfig {
|
||||
source_dir: String,
|
||||
}
|
||||
|
||||
impl Default for GameConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
source_dir: format!("{}/scripts", env!("CARGO_MANIFEST_DIR")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Game<'w> {
|
||||
event_loop: EventLoop<()>,
|
||||
context: Context<'w>,
|
||||
vm: Vm,
|
||||
config: GameConfig,
|
||||
}
|
||||
|
||||
impl<'w> Game<'w> {
|
||||
pub fn new(config: GameConfig) -> Result<Self> {
|
||||
let event_loop = EventLoop::new()?;
|
||||
event_loop.set_control_flow(ControlFlow::Wait);
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Sigil")
|
||||
.with_inner_size(LogicalSize::new(1280.0, 720.0))
|
||||
.build(&event_loop)?;
|
||||
|
||||
let context = pollster::block_on(Context::new(Arc::new(window), Limits::default()))?;
|
||||
let runtime = scripting::Runtime::new(&config.source_dir)?;
|
||||
|
||||
Ok(Self {
|
||||
event_loop,
|
||||
context,
|
||||
vm: runtime.vm.try_clone()?,
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run(mut self) -> Result<()> {
|
||||
let frame_counter = self.vm.call(["FrameCounter", "new"], ())?;
|
||||
let mut input = Input::new();
|
||||
|
||||
self.event_loop.run(|event, elwt| {
|
||||
match event {
|
||||
Event::WindowEvent { window_id, event }
|
||||
if window_id == self.context.window.id() =>
|
||||
{
|
||||
if self.context.handle_window_event(&event, elwt) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let WindowEvent::RedrawRequested = event {
|
||||
render(&self.context);
|
||||
self.context.window.request_redraw();
|
||||
}
|
||||
|
||||
input.update(&event);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if input.is_key_just_pressed(KeyCode::Escape) {
|
||||
elwt.exit();
|
||||
}
|
||||
|
||||
self.vm
|
||||
.call(["FrameCounter", "update"], (&frame_counter,))
|
||||
.unwrap();
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn render(context: &Context) {
|
||||
let output = context.surface.get_current_texture().unwrap();
|
||||
let view = output
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut encoder = context
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Render Encoder"),
|
||||
});
|
||||
{
|
||||
let _render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.1,
|
||||
g: 0.2,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
}),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
occlusion_query_set: None,
|
||||
timestamp_writes: None,
|
||||
});
|
||||
}
|
||||
|
||||
// submit will accept anything that implements IntoIter
|
||||
context.queue.submit(std::iter::once(encoder.finish()));
|
||||
output.present();
|
||||
}
|
|
@ -1,21 +1,24 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use winit::{
|
||||
dpi::PhysicalSize, event::WindowEvent, event_loop::EventLoopWindowTarget, window::Window,
|
||||
};
|
||||
|
||||
pub struct Context<'w> {
|
||||
pub window: &'w Window,
|
||||
pub struct Context<'window> {
|
||||
pub window: Arc<Window>,
|
||||
pub instance: wgpu::Instance,
|
||||
pub size: PhysicalSize<u32>,
|
||||
pub surface: wgpu::Surface<'w>,
|
||||
pub surface: wgpu::Surface<'window>,
|
||||
pub surface_config: wgpu::SurfaceConfiguration,
|
||||
pub adapter: wgpu::Adapter,
|
||||
pub device: wgpu::Device,
|
||||
pub queue: wgpu::Queue,
|
||||
}
|
||||
|
||||
impl<'w> Context<'w> {
|
||||
pub async fn new(window: &'w Window, limits: wgpu::Limits) -> Result<Self> {
|
||||
impl<'window> Context<'window> {
|
||||
pub async fn new(window: Arc<Window>, limits: wgpu::Limits) -> Result<Self> {
|
||||
log::info!("Initialising WGPU context...");
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::VULKAN,
|
||||
|
@ -28,7 +31,7 @@ impl<'w> Context<'w> {
|
|||
// - A GPU device to draw to the surface
|
||||
// - A draw command queue
|
||||
log::info!("Initialising window surface...");
|
||||
let surface = instance.create_surface(window)?;
|
||||
let surface = instance.create_surface(window.clone())?;
|
||||
|
||||
log::info!("Requesting GPU adapter...");
|
||||
let adapter = instance
|
||||
|
|
123
src/main.rs
123
src/main.rs
|
@ -1,130 +1,15 @@
|
|||
mod game;
|
||||
mod gfx;
|
||||
mod input;
|
||||
|
||||
use std::sync::Arc;
|
||||
mod scripting;
|
||||
|
||||
use anyhow::Result;
|
||||
use gfx::Context;
|
||||
use input::Input;
|
||||
use rune::{
|
||||
runtime::Object,
|
||||
termcolor::{ColorChoice, StandardStream},
|
||||
Diagnostics, Source, Sources, Vm,
|
||||
};
|
||||
use wgpu::Limits;
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::*,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
keyboard::KeyCode,
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
use game::{Game, GameConfig};
|
||||
|
||||
pub fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
let (event_loop, window) = make_window()?;
|
||||
let context = pollster::block_on(Context::new(&window, Limits::default()))?;
|
||||
run(event_loop, context)?;
|
||||
Game::new(GameConfig::default())?.run()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(event_loop: EventLoop<()>, mut context: Context) -> Result<()> {
|
||||
// !HACK: Temporrary scripting engine setup
|
||||
let source_dir = format!("{}/scripts", env!("CARGO_MANIFEST_DIR"));
|
||||
let rune_context = rune::Context::with_default_modules()?;
|
||||
let runtime = Arc::new(rune_context.runtime()?);
|
||||
let mut sources = Sources::new();
|
||||
sources.insert(Source::from_path(format!("{source_dir}/frame_counter.rn"))?)?;
|
||||
sources.insert(Source::memory("pub fn add(a, b) { a + b }")?)?;
|
||||
let mut diagnostics = Diagnostics::new();
|
||||
let result = rune::prepare(&mut sources)
|
||||
.with_context(&rune_context)
|
||||
.with_diagnostics(&mut diagnostics)
|
||||
.build();
|
||||
if !diagnostics.is_empty() {
|
||||
let mut writer = StandardStream::stderr(ColorChoice::Always);
|
||||
diagnostics.emit(&mut writer, &sources)?;
|
||||
}
|
||||
let mut vm = Vm::new(runtime, Arc::new(result?));
|
||||
let frame_counter = vm.call(["FrameCounter", "new"], ())?;
|
||||
|
||||
let mut input = Input::new();
|
||||
|
||||
event_loop.run(|event, elwt| {
|
||||
match event {
|
||||
Event::WindowEvent { window_id, event } if window_id == context.window.id() => {
|
||||
if context.handle_window_event(&event, elwt) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let WindowEvent::RedrawRequested = event {
|
||||
render(&context);
|
||||
context.window.request_redraw();
|
||||
}
|
||||
|
||||
input.update(&event);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if input.is_key_just_pressed(KeyCode::Escape) {
|
||||
elwt.exit();
|
||||
}
|
||||
|
||||
vm.call(["FrameCounter", "update"], (&frame_counter,))
|
||||
.unwrap();
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_window() -> Result<(EventLoop<()>, Window)> {
|
||||
let event_loop = EventLoop::new()?;
|
||||
event_loop.set_control_flow(ControlFlow::Wait);
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Sigil")
|
||||
.with_inner_size(LogicalSize::new(1280.0, 720.0))
|
||||
.build(&event_loop)?;
|
||||
|
||||
Ok((event_loop, window))
|
||||
}
|
||||
|
||||
fn render(context: &Context) {
|
||||
let output = context.surface.get_current_texture().unwrap();
|
||||
let view = output
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut encoder = context
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Render Encoder"),
|
||||
});
|
||||
{
|
||||
let _render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.1,
|
||||
g: 0.2,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
}),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
occlusion_query_set: None,
|
||||
timestamp_writes: None,
|
||||
});
|
||||
}
|
||||
|
||||
// submit will accept anything that implements IntoIter
|
||||
context.queue.submit(std::iter::once(encoder.finish()));
|
||||
output.present();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
pub mod modules;
|
||||
mod runtime;
|
||||
|
||||
pub use runtime::Runtime;
|
|
@ -0,0 +1,39 @@
|
|||
use anyhow::Result;
|
||||
use rune::Module;
|
||||
|
||||
#[rune::module(::log)]
|
||||
pub fn module() -> Result<Module> {
|
||||
let mut module = Module::from_meta(self::module_meta)?.with_unique("log");
|
||||
module.function_meta(info)?;
|
||||
module.function_meta(warn)?;
|
||||
module.function_meta(error)?;
|
||||
module.function_meta(trace)?;
|
||||
module.function_meta(debug)?;
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
#[rune::function]
|
||||
pub fn info(message: &str) {
|
||||
log::info!("{message}");
|
||||
}
|
||||
|
||||
#[rune::function]
|
||||
pub fn warn(message: &str) {
|
||||
log::warn!("{message}");
|
||||
}
|
||||
|
||||
#[rune::function]
|
||||
pub fn error(message: &str) {
|
||||
log::error!("{message}");
|
||||
}
|
||||
|
||||
#[rune::function]
|
||||
pub fn trace(message: &str) {
|
||||
log::trace!("{message}");
|
||||
}
|
||||
|
||||
#[rune::function]
|
||||
pub fn debug(message: &str) {
|
||||
log::debug!("{message}");
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
pub mod log;
|
|
@ -0,0 +1,59 @@
|
|||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use rune::{
|
||||
termcolor::{ColorChoice, StandardStream},
|
||||
Context, Diagnostics, Source, Sources, Vm,
|
||||
};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use super::modules;
|
||||
|
||||
pub struct Runtime {
|
||||
pub vm: Vm,
|
||||
pub sources: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
pub fn new(source_dir: &str) -> Result<Self> {
|
||||
let mut context = Context::with_default_modules()?;
|
||||
context.install(modules::log::module()?)?;
|
||||
|
||||
let runtime = Arc::new(context.runtime()?);
|
||||
let mut diagnostics = Diagnostics::new();
|
||||
|
||||
let (mut sources, source_paths) = Self::get_sources(source_dir)?;
|
||||
let result = rune::prepare(&mut sources)
|
||||
.with_context(&context)
|
||||
.with_diagnostics(&mut diagnostics)
|
||||
.build();
|
||||
|
||||
if !diagnostics.is_empty() {
|
||||
let mut writer = StandardStream::stderr(ColorChoice::Always);
|
||||
diagnostics.emit(&mut writer, &sources)?;
|
||||
}
|
||||
|
||||
let vm = Vm::new(runtime, Arc::new(result?));
|
||||
|
||||
Ok(Self {
|
||||
vm,
|
||||
sources: source_paths,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_sources(source_dir: &str) -> Result<(Sources, Vec<PathBuf>)> {
|
||||
let mut source_paths = vec![];
|
||||
let mut sources = Sources::new();
|
||||
for entry in WalkDir::new(source_dir).into_iter().filter_map(|e| e.ok()) {
|
||||
let path = entry.path();
|
||||
if path.is_file() && path.extension().is_some_and(|e| e == "rn") {
|
||||
sources.insert(Source::from_path(path)?)?;
|
||||
source_paths.push(path.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
log::warn!("Source paths: {source_paths:?}");
|
||||
|
||||
Ok((sources, source_paths))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue