Optimize rendering by pre-loading resources #231
|
@ -14,7 +14,7 @@ GNU General Public License for more details.
|
||||||
You should have received a copy of the GNU General Public License along with On the Grid. If not, see <https://www.gnu.org/licenses/>.
|
You should have received a copy of the GNU General Public License along with On the Grid. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::CoreApi;
|
use crate::{CoreApi, ResourceManager};
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
|
|
||||||
use otg_core::{
|
use otg_core::{
|
||||||
|
@ -22,7 +22,7 @@ use otg_core::{
|
||||||
CoreRequest, CoreResponse,
|
CoreRequest, CoreResponse,
|
||||||
};
|
};
|
||||||
use sgf::GameRecord;
|
use sgf::GameRecord;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::{rc::Rc, sync::{Arc, RwLock}};
|
||||||
|
|
||||||
use crate::views::{GameReview, HomeView, SettingsView};
|
use crate::views::{GameReview, HomeView, SettingsView};
|
||||||
|
|
||||||
|
@ -58,10 +58,12 @@ pub struct AppWindow {
|
||||||
// Not liking this, but I have to keep track of the settings view model separately from
|
// Not liking this, but I have to keep track of the settings view model separately from
|
||||||
// anything else. I'll have to look into this later.
|
// anything else. I'll have to look into this later.
|
||||||
settings_view_model: Arc<RwLock<Option<SettingsView>>>,
|
settings_view_model: Arc<RwLock<Option<SettingsView>>>,
|
||||||
|
|
||||||
|
resources: ResourceManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppWindow {
|
impl AppWindow {
|
||||||
pub fn new(app: &adw::Application, core: CoreApi) -> Self {
|
pub fn new(app: &adw::Application, core: CoreApi, resources: ResourceManager) -> Self {
|
||||||
let window = Self::setup_window(app);
|
let window = Self::setup_window(app);
|
||||||
let overlay = Self::setup_overlay();
|
let overlay = Self::setup_overlay();
|
||||||
let stack = adw::NavigationView::new();
|
let stack = adw::NavigationView::new();
|
||||||
|
@ -77,6 +79,7 @@ impl AppWindow {
|
||||||
overlay,
|
overlay,
|
||||||
core,
|
core,
|
||||||
settings_view_model: Default::default(),
|
settings_view_model: Default::default(),
|
||||||
|
resources,
|
||||||
};
|
};
|
||||||
|
|
||||||
let home = s.setup_home();
|
let home = s.setup_home();
|
||||||
|
@ -88,7 +91,7 @@ impl AppWindow {
|
||||||
|
|
||||||
pub fn open_game_review(&self, game_record: GameRecord) {
|
pub fn open_game_review(&self, game_record: GameRecord) {
|
||||||
let header = adw::HeaderBar::new();
|
let header = adw::HeaderBar::new();
|
||||||
let game_review = GameReview::new(self.core.clone(), game_record);
|
let game_review = GameReview::new(self.core.clone(), game_record, self.resources.clone());
|
||||||
|
|
||||||
let layout = gtk::Box::builder()
|
let layout = gtk::Box::builder()
|
||||||
.orientation(gtk::Orientation::Vertical)
|
.orientation(gtk::Orientation::Vertical)
|
||||||
|
|
|
@ -35,7 +35,7 @@ You should have received a copy of the GNU General Public License along with On
|
||||||
// Now, we know what kind of object we have for the current board representation. Let's make use of
|
// Now, we know what kind of object we have for the current board representation. Let's make use of
|
||||||
// that.
|
// that.
|
||||||
|
|
||||||
use crate::perftrace;
|
use crate::{perftrace, Resource, ResourceManager};
|
||||||
|
|
||||||
use gio::resources_lookup_data;
|
use gio::resources_lookup_data;
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
|
@ -56,6 +56,7 @@ const MARGIN: i32 = 20;
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct GobanPrivate {
|
pub struct GobanPrivate {
|
||||||
board_state: Rc<RefCell<otg_core::Goban>>,
|
board_state: Rc<RefCell<otg_core::Goban>>,
|
||||||
|
resource_manager: Rc<RefCell<Option<ResourceManager>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GobanPrivate {}
|
impl GobanPrivate {}
|
||||||
|
@ -88,10 +89,11 @@ glib::wrapper! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Goban {
|
impl Goban {
|
||||||
pub fn new(board_state: otg_core::Goban) -> Self {
|
pub fn new(board_state: otg_core::Goban, resources: ResourceManager) -> Self {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
|
|
||||||
*s.imp().board_state.borrow_mut() = board_state;
|
*s.imp().board_state.borrow_mut() = board_state;
|
||||||
|
*s.imp().resource_manager.borrow_mut() = Some(resources);
|
||||||
s.set_width_request(WIDTH);
|
s.set_width_request(WIDTH);
|
||||||
s.set_height_request(HEIGHT);
|
s.set_height_request(HEIGHT);
|
||||||
|
|
||||||
|
@ -107,19 +109,42 @@ impl Goban {
|
||||||
|
|
||||||
fn redraw(&self, ctx: &cairo::Context, width: i32, height: i32) {
|
fn redraw(&self, ctx: &cairo::Context, width: i32, height: i32) {
|
||||||
println!("{} x {}", width, height);
|
println!("{} x {}", width, height);
|
||||||
|
/*
|
||||||
let background = load_pixbuf(
|
let background = load_pixbuf(
|
||||||
"/com/luminescent-dreams/otg-gtk/wood_texture.jpg",
|
"/com/luminescent-dreams/otg-gtk/wood_texture.jpg",
|
||||||
false,
|
false,
|
||||||
WIDTH + 40,
|
WIDTH + 40,
|
||||||
HEIGHT + 40,
|
HEIGHT + 40,
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
|
|
||||||
|
let background = self
|
||||||
|
.imp()
|
||||||
|
.resource_manager
|
||||||
|
.borrow()
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|r| r.resource("/com/luminescent-dreams/otg-gtk/wood_texture.jpg"));
|
||||||
|
|
||||||
|
let black_texture = self
|
||||||
|
.imp()
|
||||||
|
.resource_manager
|
||||||
|
.borrow()
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|r| r.resource("/com/luminescent-dreams/otg-gtk/black_stone.png"));
|
||||||
|
|
||||||
|
let white_texture = self
|
||||||
|
.imp()
|
||||||
|
.resource_manager
|
||||||
|
.borrow()
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|r| r.resource("/com/luminescent-dreams/otg-gtk/white_stone.png"));
|
||||||
|
|
||||||
match background {
|
match background {
|
||||||
Ok(Some(ref background)) => {
|
Some(Resource::Image(ref background)) => {
|
||||||
ctx.set_source_pixbuf(background, 0., 0.);
|
ctx.set_source_pixbuf(background, 0., 0.);
|
||||||
ctx.paint().expect("paint should never fail");
|
ctx.paint().expect("paint should never fail");
|
||||||
}
|
}
|
||||||
Ok(None) | Err(_) => ctx.set_source_rgb(0.7, 0.7, 0.7),
|
None => ctx.set_source_rgb(0.7, 0.7, 0.7),
|
||||||
}
|
}
|
||||||
|
|
||||||
let board = self.imp().board_state.borrow();
|
let board = self.imp().board_state.borrow();
|
||||||
|
@ -129,7 +154,14 @@ impl Goban {
|
||||||
let hspace_between = ((width - 40) as f64) / ((board.size.width - 1) as f64);
|
let hspace_between = ((width - 40) as f64) / ((board.size.width - 1) as f64);
|
||||||
let vspace_between = ((height - 40) as f64) / ((board.size.height - 1) as f64);
|
let vspace_between = ((height - 40) as f64) / ((board.size.height - 1) as f64);
|
||||||
|
|
||||||
let pen = Pen::new(MARGIN as f64, MARGIN as f64, hspace_between, vspace_between);
|
let pen = Pen::new(
|
||||||
|
MARGIN as f64,
|
||||||
|
MARGIN as f64,
|
||||||
|
hspace_between,
|
||||||
|
vspace_between,
|
||||||
|
black_texture,
|
||||||
|
white_texture,
|
||||||
|
);
|
||||||
|
|
||||||
(0..board.size.width).for_each(|col| {
|
(0..board.size.width).for_each(|col| {
|
||||||
ctx.move_to(
|
ctx.move_to(
|
||||||
|
@ -179,35 +211,27 @@ struct Pen {
|
||||||
y_offset: f64,
|
y_offset: f64,
|
||||||
hspace_between: f64,
|
hspace_between: f64,
|
||||||
vspace_between: f64,
|
vspace_between: f64,
|
||||||
black_stone: Pixbuf,
|
black_stone: Option<Pixbuf>,
|
||||||
white_stone: Pixbuf,
|
white_stone: Option<Pixbuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pen {
|
impl Pen {
|
||||||
fn new(x_offset: f64, y_offset: f64, hspace_between: f64, vspace_between: f64) -> Self {
|
fn new(
|
||||||
let radius = (hspace_between / 2. - 2.) as i32;
|
x_offset: f64,
|
||||||
let black_stone = load_pixbuf(
|
y_offset: f64,
|
||||||
"/com/luminescent-dreams/otg-gtk/black_stone.png",
|
hspace_between: f64,
|
||||||
true,
|
vspace_between: f64,
|
||||||
512,
|
black_stone: Option<Resource>,
|
||||||
512,
|
white_stone: Option<Resource>,
|
||||||
)
|
) -> Self {
|
||||||
.unwrap()
|
let black_stone = match black_stone {
|
||||||
.unwrap();
|
Some(Resource::Image(img)) => Some(img),
|
||||||
let black_stone = black_stone
|
_ => None,
|
||||||
.scale_simple(radius * 2, radius * 2, InterpType::Nearest)
|
};
|
||||||
.unwrap();
|
let white_stone = match white_stone {
|
||||||
let white_stone = load_pixbuf(
|
Some(Resource::Image(img)) => Some(img),
|
||||||
"/com/luminescent-dreams/otg-gtk/white_stone.png",
|
_ => None,
|
||||||
true,
|
};
|
||||||
512,
|
|
||||||
512,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
let white_stone = white_stone
|
|
||||||
.scale_simple(radius * 2, radius * 2, InterpType::Nearest)
|
|
||||||
.unwrap();
|
|
||||||
Pen {
|
Pen {
|
||||||
x_offset,
|
x_offset,
|
||||||
y_offset,
|
y_offset,
|
||||||
|
@ -232,8 +256,14 @@ impl Pen {
|
||||||
fn stone(&self, ctx: &cairo::Context, row: u8, col: u8, color: Color, _liberties: Option<u8>) {
|
fn stone(&self, ctx: &cairo::Context, row: u8, col: u8, color: Color, _liberties: Option<u8>) {
|
||||||
let (x_loc, y_loc) = self.stone_location(row, col);
|
let (x_loc, y_loc) = self.stone_location(row, col);
|
||||||
match color {
|
match color {
|
||||||
Color::White => ctx.set_source_pixbuf(&self.white_stone, x_loc, y_loc),
|
Color::White => match self.white_stone {
|
||||||
Color::Black => ctx.set_source_pixbuf(&self.black_stone, x_loc, y_loc),
|
Some(ref white_stone) => ctx.set_source_pixbuf(&white_stone, x_loc, y_loc),
|
||||||
|
None => ctx.set_source_rgb(0.9, 0.9, 0.9),
|
||||||
|
},
|
||||||
|
Color::Black => match self.black_stone {
|
||||||
|
Some(ref black_stone) => ctx.set_source_pixbuf(&black_stone, x_loc, y_loc),
|
||||||
|
None => ctx.set_source_rgb(0.0, 0.0, 0.0),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
ctx.paint().expect("paint should never fail");
|
ctx.paint().expect("paint should never fail");
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -21,10 +21,12 @@ pub use app_window::AppWindow;
|
||||||
|
|
||||||
mod views;
|
mod views;
|
||||||
|
|
||||||
use async_std::task::{yield_now};
|
use async_std::task::yield_now;
|
||||||
use otg_core::{Core, Observable, CoreRequest, CoreResponse};
|
use gio::resources_lookup_data;
|
||||||
use std::{rc::Rc};
|
use gtk::gdk_pixbuf::{Colorspace, InterpType, Pixbuf};
|
||||||
|
use image::{io::Reader as ImageReader, ImageError};
|
||||||
|
use otg_core::{Core, CoreRequest, CoreResponse, Observable};
|
||||||
|
use std::{cell::RefCell, collections::HashMap, io::Cursor, rc::Rc};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CoreApi {
|
pub struct CoreApi {
|
||||||
|
@ -37,6 +39,93 @@ impl CoreApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Resource {
|
||||||
|
Image(Pixbuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ResourceManager {
|
||||||
|
resources: Rc<RefCell<HashMap<String, Resource>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResourceManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut resources = HashMap::new();
|
||||||
|
|
||||||
|
for (path, xres, yres, transparency) in [
|
||||||
|
(
|
||||||
|
"/com/luminescent-dreams/otg-gtk/wood_texture.jpg",
|
||||||
|
840,
|
||||||
|
840,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/com/luminescent-dreams/otg-gtk/black_stone.png",
|
||||||
|
40,
|
||||||
|
40,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/com/luminescent-dreams/otg-gtk/white_stone.png",
|
||||||
|
40,
|
||||||
|
40,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
match perftrace(&format!("loading {}", path), || {
|
||||||
|
Self::load_image(path, transparency, xres, yres)
|
||||||
|
}) {
|
||||||
|
Ok(Some(image)) => {
|
||||||
|
resources.insert(path.to_owned(), Resource::Image(image));
|
||||||
|
}
|
||||||
|
Ok(None) => println!("no image in resource bundle for {}", path),
|
||||||
|
Err(err) => println!("failed to load image {}: {}", path, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
resources: Rc::new(RefCell::new(resources)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resource(&self, path: &str) -> Option<Resource> {
|
||||||
|
self.resources.borrow().get(path).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_image(
|
||||||
|
path: &str,
|
||||||
|
transparency: bool,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
) -> Result<Option<Pixbuf>, ImageError> {
|
||||||
|
let image_bytes = resources_lookup_data(path, gio::ResourceLookupFlags::NONE).unwrap();
|
||||||
|
|
||||||
|
let image = ImageReader::new(Cursor::new(image_bytes))
|
||||||
|
.with_guessed_format()
|
||||||
|
.unwrap()
|
||||||
|
.decode();
|
||||||
|
image.map(|image| {
|
||||||
|
let stride = if transparency {
|
||||||
|
image.to_rgba8().sample_layout().height_stride
|
||||||
|
} else {
|
||||||
|
image.to_rgb8().sample_layout().height_stride
|
||||||
|
};
|
||||||
|
Pixbuf::from_bytes(
|
||||||
|
&glib::Bytes::from(image.as_bytes()),
|
||||||
|
Colorspace::Rgb,
|
||||||
|
transparency,
|
||||||
|
8,
|
||||||
|
image.width() as i32,
|
||||||
|
image.height() as i32,
|
||||||
|
stride as i32,
|
||||||
|
)
|
||||||
|
.scale_simple(width, height, InterpType::Nearest)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
pub fn perftrace<F, A>(trace_name: &str, f: F) -> A
|
pub fn perftrace<F, A>(trace_name: &str, f: F) -> A
|
||||||
where
|
where
|
||||||
F: FnOnce() -> A,
|
F: FnOnce() -> A,
|
||||||
|
|
|
@ -4,8 +4,7 @@ use async_std::task::spawn;
|
||||||
use gio::ActionEntry;
|
use gio::ActionEntry;
|
||||||
use otg_core::{Config, ConfigOption, Core, CoreNotification, LibraryPath, Observable};
|
use otg_core::{Config, ConfigOption, Core, CoreNotification, LibraryPath, Observable};
|
||||||
use otg_gtk::{
|
use otg_gtk::{
|
||||||
AppWindow,
|
AppWindow, CoreApi, ResourceManager
|
||||||
CoreApi,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,8 +122,9 @@ fn main() {
|
||||||
|
|
||||||
app.connect_activate({
|
app.connect_activate({
|
||||||
move |app| {
|
move |app| {
|
||||||
|
let resources = ResourceManager::new();
|
||||||
let core_api = CoreApi { core: core.clone() };
|
let core_api = CoreApi { core: core.clone() };
|
||||||
let app_window = AppWindow::new(app, core_api);
|
let app_window = AppWindow::new(app, core_api, resources);
|
||||||
|
|
||||||
setup_app_configuration_action(app, app_window.clone());
|
setup_app_configuration_action(app, app_window.clone());
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,9 @@ You should have received a copy of the GNU General Public License along with On
|
||||||
// I'll get all of the information about the game from the core, and then render everything in the
|
// I'll get all of the information about the game from the core, and then render everything in the
|
||||||
// UI. So this will be a heavy lift on the UI side.
|
// UI. So this will be a heavy lift on the UI side.
|
||||||
|
|
||||||
use crate::{components::{Goban, PlayerCard, ReviewTree}, CoreApi};
|
use crate::{
|
||||||
|
components::{Goban, PlayerCard, ReviewTree}, CoreApi, ResourceManager
|
||||||
|
};
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
use otg_core::Color;
|
use otg_core::Color;
|
||||||
|
@ -31,8 +33,6 @@ use sgf::GameRecord;
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct GameReviewPrivate {}
|
pub struct GameReviewPrivate {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
impl ObjectSubclass for GameReviewPrivate {
|
impl ObjectSubclass for GameReviewPrivate {
|
||||||
const NAME: &'static str = "GameReview";
|
const NAME: &'static str = "GameReview";
|
||||||
|
@ -49,7 +49,7 @@ glib::wrapper! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameReview {
|
impl GameReview {
|
||||||
pub fn new(_api: CoreApi, record: GameRecord) -> Self {
|
pub fn new(_api: CoreApi, record: GameRecord, resources: ResourceManager) -> Self {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
|
|
||||||
// It's actually really bad to be just throwing away errors. Panics make everyone unhappy.
|
// It's actually really bad to be just throwing away errors. Panics make everyone unhappy.
|
||||||
|
@ -58,7 +58,7 @@ impl GameReview {
|
||||||
let board_repr = otg_core::Goban::default()
|
let board_repr = otg_core::Goban::default()
|
||||||
.apply_moves(record.mainline())
|
.apply_moves(record.mainline())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let board = Goban::new(board_repr);
|
let board = Goban::new(board_repr, resources);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
s.attach(&board, 0, 0, 2, 2);
|
s.attach(&board, 0, 0, 2, 2);
|
||||||
|
|
Loading…
Reference in New Issue