wgui: prevent data copy, parser data

This commit is contained in:
Aleksander
2025-09-21 13:39:14 +02:00
parent cd18bdcea8
commit 7a97e9dee2
4 changed files with 157 additions and 73 deletions

View File

@@ -9,7 +9,7 @@ mod widget_sprite;
use crate::{ use crate::{
assets::AssetProvider, assets::AssetProvider,
components::{Component, ComponentTrait, ComponentWeak}, components::{Component, ComponentWeak},
drawing::{self}, drawing::{self},
event::EventListenerCollection, event::EventListenerCollection,
globals::WguiGlobals, globals::WguiGlobals,
@@ -49,25 +49,55 @@ struct ParserFile {
template_parameters: HashMap<Rc<str>, Rc<str>>, template_parameters: HashMap<Rc<str>, Rc<str>>,
} }
#[derive(Default, Clone)]
pub struct ParserData {
pub components_by_id: HashMap<Rc<str>, ComponentWeak>,
pub components_by_widget_id: HashMap<WidgetID, ComponentWeak>,
pub components: Vec<Component>,
pub ids: HashMap<Rc<str>, WidgetID>,
pub templates: HashMap<Rc<str>, Rc<Template>>,
pub var_map: HashMap<Rc<str>, Rc<str>>,
macro_attribs: HashMap<Rc<str>, MacroAttribs>,
}
impl ParserData {
fn take_results_from(&mut self, from: &mut Self) {
let ids = std::mem::take(&mut from.ids);
let components = std::mem::take(&mut from.components);
let components_by_id = std::mem::take(&mut from.components_by_id);
let components_by_widget_id = std::mem::take(&mut from.components_by_widget_id);
for (id, key) in ids {
self.ids.insert(id, key);
}
for c in components {
self.components.push(c);
}
for (k, v) in components_by_id {
self.components_by_id.insert(k, v);
}
for (k, v) in components_by_widget_id {
self.components_by_widget_id.insert(k, v);
}
}
}
/* /*
WARNING: this struct could contain valid components with already bound listener handles. WARNING: this struct could contain valid components with already bound listener handles.
Make sure to store them somewhere in your code. Make sure to store them somewhere in your code.
*/ */
#[derive(Default)] #[derive(Default)]
pub struct ParserState { pub struct ParserState {
pub ids: HashMap<Rc<str>, WidgetID>, pub data: ParserData,
macro_attribs: HashMap<Rc<str>, MacroAttribs>,
pub var_map: HashMap<Rc<str>, Rc<str>>,
pub components: Vec<Component>,
pub components_by_id: HashMap<Rc<str>, std::rc::Weak<dyn ComponentTrait>>,
pub components_by_widget_id: HashMap<WidgetID, std::rc::Weak<dyn ComponentTrait>>,
pub templates: HashMap<Rc<str>, Rc<Template>>,
pub path: PathBuf, pub path: PathBuf,
} }
impl ParserState { impl ParserState {
pub fn fetch_component_by_id(&self, id: &str) -> anyhow::Result<Component> { pub fn fetch_component_by_id(&self, id: &str) -> anyhow::Result<Component> {
let Some(weak) = self.components_by_id.get(id) else { let Some(weak) = self.data.components_by_id.get(id) else {
anyhow::bail!("Component by ID \"{id}\" doesn't exist"); anyhow::bail!("Component by ID \"{id}\" doesn't exist");
}; };
@@ -79,7 +109,7 @@ impl ParserState {
} }
pub fn fetch_component_by_widget_id(&self, widget_id: WidgetID) -> anyhow::Result<Component> { pub fn fetch_component_by_widget_id(&self, widget_id: WidgetID) -> anyhow::Result<Component> {
let Some(weak) = self.components_by_widget_id.get(&widget_id) else { let Some(weak) = self.data.components_by_widget_id.get(&widget_id) else {
anyhow::bail!("Component by widget ID \"{widget_id:?}\" doesn't exist"); anyhow::bail!("Component by widget ID \"{widget_id:?}\" doesn't exist");
}; };
@@ -113,7 +143,7 @@ impl ParserState {
} }
pub fn get_widget_id(&self, id: &str) -> anyhow::Result<WidgetID> { pub fn get_widget_id(&self, id: &str) -> anyhow::Result<WidgetID> {
match self.ids.get(id) { match self.data.ids.get(id) {
Some(id) => Ok(*id), Some(id) => Ok(*id),
None => anyhow::bail!("Widget by ID \"{id}\" doesn't exist"), None => anyhow::bail!("Widget by ID \"{id}\" doesn't exist"),
} }
@@ -155,20 +185,15 @@ impl ParserState {
widget_id: WidgetID, widget_id: WidgetID,
template_parameters: HashMap<Rc<str>, Rc<str>>, template_parameters: HashMap<Rc<str>, Rc<str>>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let Some(template) = self.templates.get(template_name) else { let Some(template) = self.data.templates.get(template_name) else {
anyhow::bail!("no template named \"{template_name}\" found"); anyhow::bail!("no template named \"{template_name}\" found");
}; };
let mut ctx = ParserContext { let mut ctx = ParserContext {
layout, layout,
listeners, listeners,
ids: Default::default(), data_global: &self.data,
macro_attribs: self.macro_attribs.clone(), // FIXME: prevent copying data_local: ParserData::default(),
var_map: self.var_map.clone(), // FIXME: prevent copying
components: self.components.clone(), // FIXME: prevent copying
components_by_id: self.components_by_id.clone(), // FIXME: prevent copying
components_by_widget_id: self.components_by_widget_id.clone(), // FIXME: prevent copying
templates: Default::default(),
doc_params, doc_params,
}; };
@@ -180,10 +205,7 @@ impl ParserState {
parse_widget_other_internal(&template.clone(), template_parameters, &file, &mut ctx, widget_id)?; parse_widget_other_internal(&template.clone(), template_parameters, &file, &mut ctx, widget_id)?;
// FIXME? self.data.take_results_from(&mut ctx.data_local);
ctx.ids.into_iter().for_each(|(id, key)| {
self.ids.insert(id, key);
});
Ok(()) Ok(())
} }
@@ -198,14 +220,89 @@ struct ParserContext<'a, U1, U2> {
doc_params: &'a ParseDocumentParams<'a>, doc_params: &'a ParseDocumentParams<'a>,
layout: &'a mut Layout, layout: &'a mut Layout,
listeners: &'a mut EventListenerCollection<U1, U2>, listeners: &'a mut EventListenerCollection<U1, U2>,
var_map: HashMap<Rc<str>, Rc<str>>, data_global: &'a ParserData, // current parser state at a given moment
macro_attribs: HashMap<Rc<str>, MacroAttribs>, data_local: ParserData, // newly processed items in a given template
ids: HashMap<Rc<str>, WidgetID>, }
templates: HashMap<Rc<str>, Rc<Template>>,
components: Vec<Component>, impl<U1, U2> ParserContext<'_, U1, U2> {
components_by_id: HashMap<Rc<str>, ComponentWeak>, fn get_template(&self, name: &str) -> Option<Rc<Template>> {
components_by_widget_id: HashMap<WidgetID, ComponentWeak>, // find in local
if let Some(template) = self.data_local.templates.get(name) {
return Some(template.clone());
}
// find in global
if let Some(template) = self.data_global.templates.get(name) {
return Some(template.clone());
}
None
}
fn get_var(&self, name: &str) -> Option<Rc<str>> {
// find in local
if let Some(value) = self.data_local.var_map.get(name) {
return Some(value.clone());
}
// find in global
if let Some(value) = self.data_global.var_map.get(name) {
return Some(value.clone());
}
None
}
fn get_macro_attrib(&self, value: &str) -> Option<&MacroAttribs> {
// find in local
if let Some(macro_attribs) = self.data_local.macro_attribs.get(value) {
return Some(macro_attribs);
}
// find in global
if let Some(macro_attribs) = self.data_global.macro_attribs.get(value) {
return Some(macro_attribs);
}
None
}
fn insert_template(&mut self, name: Rc<str>, template: Rc<Template>) {
self.data_local.templates.insert(name, template);
}
fn insert_var(&mut self, key: &str, value: &str) {
self.data_local.var_map.insert(Rc::from(key), Rc::from(value));
}
fn insert_macro_attrib(&mut self, name: Rc<str>, attribs: MacroAttribs) {
self.data_local.macro_attribs.insert(name, attribs);
}
fn insert_component(&mut self, widget_id: WidgetID, component: Component, id: Option<Rc<str>>) {
self
.data_local
.components_by_widget_id
.insert(widget_id, component.weak());
if let Some(id) = id
&& self
.data_local
.components_by_id
.insert(id.clone(), component.weak())
.is_some()
{
log::warn!("duplicate component ID \"{id}\" in the same layout file!");
}
self.data_local.components.push(component);
}
fn insert_id(&mut self, id: &Rc<str>, widget_id: WidgetID) {
if self.data_local.ids.insert(id.clone(), widget_id).is_some() {
log::warn!("duplicate widget ID \"{id}\" in the same layout file!");
}
}
} }
// Parses a color from a HTML hex string // Parses a color from a HTML hex string
@@ -357,14 +454,14 @@ fn parse_widget_other<'a, U1, U2>(
node: roxmltree::Node<'a, 'a>, node: roxmltree::Node<'a, 'a>,
parent_id: WidgetID, parent_id: WidgetID,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let Some(template) = ctx.templates.get(xml_tag_name) else { let Some(template) = ctx.get_template(xml_tag_name) else {
log::error!("Undefined tag named \"{xml_tag_name}\""); log::error!("Undefined tag named \"{xml_tag_name}\"");
return Ok(()); // not critical return Ok(()); // not critical
}; };
let template_parameters: HashMap<Rc<str>, Rc<str>> = iter_attribs(file, ctx, &node, false).collect(); let template_parameters: HashMap<Rc<str>, Rc<str>> = iter_attribs(file, ctx, &node, false).collect();
parse_widget_other_internal(&template.clone(), template_parameters, file, ctx, parent_id) parse_widget_other_internal(&template, template_parameters, file, ctx, parent_id)
} }
fn parse_tag_include<'a, U1, U2>( fn parse_tag_include<'a, U1, U2>(
@@ -426,7 +523,7 @@ fn parse_tag_var<'a, U1, U2>(ctx: &mut ParserContext<U1, U2>, node: roxmltree::N
return; return;
}; };
ctx.var_map.insert(Rc::from(key), Rc::from(value)); ctx.insert_var(key, value);
} }
pub fn replace_vars(input: &str, vars: &HashMap<Rc<str>, Rc<str>>) -> Rc<str> { pub fn replace_vars(input: &str, vars: &HashMap<Rc<str>, Rc<str>>) -> Rc<str> {
@@ -462,8 +559,8 @@ fn process_attrib<'a, U1, U2>(
( (
Rc::from(key), Rc::from(key),
match ctx.var_map.get(name) { match ctx.get_var(name) {
Some(name) => name.clone(), Some(name) => name,
None => Rc::from("undefined"), None => Rc::from("undefined"),
}, },
) )
@@ -493,7 +590,7 @@ fn iter_attribs<'a, U1, U2>(
let (key, value) = (attrib.name(), attrib.value()); let (key, value) = (attrib.name(), attrib.value());
if key == "macro" { if key == "macro" {
if let Some(macro_attrib) = ctx.macro_attribs.get(value) { if let Some(macro_attrib) = ctx.get_macro_attrib(value) {
for (macro_key, macro_value) in &macro_attrib.attribs { for (macro_key, macro_value) in &macro_attrib.attribs {
res.push(process_attrib(file, ctx, macro_key, macro_value)); res.push(process_attrib(file, ctx, macro_key, macro_value));
} }
@@ -544,7 +641,7 @@ fn parse_tag_template<U1, U2>(file: &ParserFile, ctx: &mut ParserContext<U1, U2>
return; return;
}; };
ctx.templates.insert( ctx.insert_template(
name, name,
Rc::new(Template { Rc::new(Template {
node: node.id(), node: node.id(),
@@ -577,7 +674,7 @@ fn parse_tag_macro<U1, U2>(file: &ParserFile, ctx: &mut ParserContext<U1, U2>, n
return; return;
}; };
ctx.macro_attribs.insert(name, MacroAttribs { attribs: macro_attribs }); ctx.insert_macro_attrib(name, MacroAttribs { attribs: macro_attribs });
} }
fn process_component<'a, U1, U2>( fn process_component<'a, U1, U2>(
@@ -587,23 +684,21 @@ fn process_component<'a, U1, U2>(
component: Component, component: Component,
widget_id: WidgetID, widget_id: WidgetID,
) { ) {
ctx.components_by_widget_id.insert(widget_id, component.weak());
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect(); let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
let mut component_id: Option<Rc<str>> = None;
for (key, value) in attribs { for (key, value) in attribs {
#[allow(clippy::single_match)] #[allow(clippy::single_match)]
match key.as_ref() { match key.as_ref() {
"id" => { "id" => {
if ctx.components_by_id.insert(value.clone(), component.weak()).is_some() { component_id = Some(value);
log::warn!("duplicate component ID \"{value}\" in the same layout file!");
}
} }
_ => {} _ => {}
} }
} }
ctx.components.push(component); ctx.insert_component(widget_id, component, component_id);
} }
fn parse_widget_universal<'a, U1, U2>( fn parse_widget_universal<'a, U1, U2>(
@@ -619,9 +714,7 @@ fn parse_widget_universal<'a, U1, U2>(
match key.as_ref() { match key.as_ref() {
"id" => { "id" => {
// Attach a specific widget to name-ID map (just like getElementById) // Attach a specific widget to name-ID map (just like getElementById)
if ctx.ids.insert(value.clone(), widget_id).is_some() { ctx.insert_id(&value, widget_id);
log::warn!("duplicate widget ID \"{value}\" in the same layout file!");
}
} }
_ => {} _ => {}
} }
@@ -733,18 +826,14 @@ fn create_default_context<'a, U1, U2>(
doc_params: &'a ParseDocumentParams, doc_params: &'a ParseDocumentParams,
layout: &'a mut Layout, layout: &'a mut Layout,
listeners: &'a mut EventListenerCollection<U1, U2>, listeners: &'a mut EventListenerCollection<U1, U2>,
data_global: &'a ParserData,
) -> ParserContext<'a, U1, U2> { ) -> ParserContext<'a, U1, U2> {
ParserContext { ParserContext {
doc_params, doc_params,
layout, layout,
listeners, listeners,
ids: Default::default(), data_local: ParserData::default(),
var_map: Default::default(), data_global,
templates: Default::default(),
macro_attribs: Default::default(),
components: Default::default(),
components_by_id: Default::default(),
components_by_widget_id: Default::default(),
} }
} }
@@ -844,20 +933,15 @@ pub fn parse_from_assets<U1, U2>(
) -> anyhow::Result<ParserState> { ) -> anyhow::Result<ParserState> {
let path = PathBuf::from(doc_params.path); let path = PathBuf::from(doc_params.path);
let mut ctx = create_default_context(doc_params, layout, listeners); let parser_data = ParserData::default();
let mut ctx = create_default_context(doc_params, layout, listeners, &parser_data);
let (file, node_layout) = get_doc_from_path(&ctx, &path)?; let (file, node_layout) = get_doc_from_path(&ctx, &path)?;
parse_document_root(&file, &mut ctx, parent_id, node_layout)?; parse_document_root(&file, &mut ctx, parent_id, node_layout)?;
// move everything essential to the result // move everything essential to the result
let result = ParserState { let result = ParserState {
ids: std::mem::take(&mut ctx.ids), data: std::mem::take(&mut ctx.data_local),
templates: std::mem::take(&mut ctx.templates),
macro_attribs: std::mem::take(&mut ctx.macro_attribs),
var_map: std::mem::take(&mut ctx.var_map),
components: std::mem::take(&mut ctx.components),
components_by_id: std::mem::take(&mut ctx.components_by_id),
components_by_widget_id: std::mem::take(&mut ctx.components_by_widget_id),
path, path,
}; };

View File

@@ -1,7 +1,7 @@
use std::{cell::RefCell, rc::Rc, sync::Arc}; use std::{cell::RefCell, rc::Rc, sync::Arc};
use button::setup_custom_button; use button::setup_custom_button;
use glam::{vec2, Affine2, Vec2}; use glam::{Affine2, Vec2, vec2};
use label::setup_custom_label; use label::setup_custom_label;
use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView}; use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView};
use wgui::{ use wgui::{
@@ -20,7 +20,7 @@ use wgui::{
use crate::{ use crate::{
backend::{ backend::{
input::{Haptics, PointerHit, PointerMode}, input::{Haptics, PointerHit, PointerMode},
overlay::{ui_transform, FrameMeta, OverlayBackend, ShouldRender}, overlay::{FrameMeta, OverlayBackend, ShouldRender, ui_transform},
}, },
graphics::{CommandBuffers, ExtentExt}, graphics::{CommandBuffers, ExtentExt},
state::AppState, state::AppState,
@@ -93,7 +93,7 @@ impl<S> GuiPanel<S> {
)?; )?;
if let Some(on_element_id) = on_custom_id { if let Some(on_element_id) = on_custom_id {
let ids = parser_state.ids.clone(); let ids = parser_state.data.ids.clone(); // FIXME: copying all ids?
for (id, widget) in ids { for (id, widget) in ids {
on_element_id( on_element_id(

View File

@@ -18,7 +18,7 @@ where
let state = BarState {}; let state = BarState {};
let mut panel = GuiPanel::new_from_template(app, "gui/bar.xml", state, None)?; let mut panel = GuiPanel::new_from_template(app, "gui/bar.xml", state, None)?;
for (id, _widget_id) in &panel.parser_state.ids { for (id, _widget_id) in &panel.parser_state.data.ids {
match id.as_ref() { match id.as_ref() {
"lock" => {} "lock" => {}
"anchor" => {} "anchor" => {}

View File

@@ -172,13 +172,13 @@ where
params, params,
)?; )?;
if let Some(widget_id) = gui_state_key.ids.get(&*my_id) { if let Some(widget_id) = gui_state_key.get_widget_id(&*my_id).ok() {
let key_state = { let key_state = {
let rect = panel let rect = panel
.layout .layout
.state .state
.widgets .widgets
.get_as::<WidgetRectangle>(*widget_id) .get_as::<WidgetRectangle>(widget_id)
.unwrap(); // want panic .unwrap(); // want panic
Rc::new(KeyState { Rc::new(KeyState {
@@ -192,7 +192,7 @@ where
panel.listeners.register( panel.listeners.register(
&mut panel.listener_handles, &mut panel.listener_handles,
*widget_id, widget_id,
EventListenerKind::MouseEnter, EventListenerKind::MouseEnter,
Box::new({ Box::new({
let k = key_state.clone(); let k = key_state.clone();
@@ -205,7 +205,7 @@ where
); );
panel.listeners.register( panel.listeners.register(
&mut panel.listener_handles, &mut panel.listener_handles,
*widget_id, widget_id,
EventListenerKind::MouseLeave, EventListenerKind::MouseLeave,
Box::new({ Box::new({
let k = key_state.clone(); let k = key_state.clone();
@@ -218,7 +218,7 @@ where
); );
panel.listeners.register( panel.listeners.register(
&mut panel.listener_handles, &mut panel.listener_handles,
*widget_id, widget_id,
EventListenerKind::MousePress, EventListenerKind::MousePress,
Box::new({ Box::new({
let k = key_state.clone(); let k = key_state.clone();
@@ -235,7 +235,7 @@ where
); );
panel.listeners.register( panel.listeners.register(
&mut panel.listener_handles, &mut panel.listener_handles,
*widget_id, widget_id,
EventListenerKind::MouseRelease, EventListenerKind::MouseRelease,
Box::new({ Box::new({
let k = key_state.clone(); let k = key_state.clone();
@@ -251,7 +251,7 @@ where
if let Some(modifier) = my_modifier { if let Some(modifier) = my_modifier {
panel.listeners.register( panel.listeners.register(
&mut panel.listener_handles, &mut panel.listener_handles,
*widget_id, widget_id,
EventListenerKind::InternalStateChange, EventListenerKind::InternalStateChange,
Box::new({ Box::new({
let k = key_state.clone(); let k = key_state.clone();