mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'aweinstock/dungeonmap' into 'master'
Add voxel data to the minimap (for visualizing trees and houses and dungeons) See merge request veloren/veloren!2301
This commit is contained in:
commit
2af6cb07f8
@ -59,6 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- /skill_preset command which allows you to apply skill presets
|
||||
- Added timed bans and ban history.
|
||||
- Added non-admin moderators with limit privileges and updated the security model to reflect this.
|
||||
- Added a minimap mode that visualizes terrain within a chunk.
|
||||
- Chat tabs
|
||||
- NPC's now hear certain sounds
|
||||
- Renamed Animal Trainers to Beastmasters and gave them their own set of armor to wear
|
||||
|
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -1032,20 +1032,6 @@ dependencies = [
|
||||
"itertools 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd01a6eb3daaafa260f6fc94c3a6c36390abc2080e38e3e34ced87393fb77d80"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque 0.8.0",
|
||||
"crossbeam-epoch 0.9.3",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.0"
|
||||
@ -5815,7 +5801,8 @@ dependencies = [
|
||||
"coreaudio-sys",
|
||||
"cpal",
|
||||
"criterion",
|
||||
"crossbeam",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-utils 0.8.3",
|
||||
"directories-next",
|
||||
"dispatch 0.1.4",
|
||||
"dot_vox",
|
||||
|
@ -13,7 +13,8 @@
|
||||
"hud.map.dungeons": "Dungeons",
|
||||
"hud.map.caves": "Caves",
|
||||
"hud.map.cave": "Cave",
|
||||
"hud.map.peaks": "Mountains",
|
||||
"hud.map.peaks": "Mountains",
|
||||
"hud.map.voxel_map": "Voxel map",
|
||||
"hud.map.trees": "Giant Trees",
|
||||
"hud.map.tree": "Giant Tree",
|
||||
"hud.map.town": "Town",
|
||||
|
@ -81,7 +81,8 @@ bincode = "1.3.1"
|
||||
chrono = { version = "0.4.9", features = ["serde"] }
|
||||
cpal = "0.13"
|
||||
copy_dir = "0.1.2"
|
||||
crossbeam = "0.8.0"
|
||||
crossbeam-utils = "0.8.1"
|
||||
crossbeam-channel = "0.5"
|
||||
# TODO: remove
|
||||
directories-next = "2.0"
|
||||
dot_vox = "4.0"
|
||||
|
@ -11,6 +11,7 @@ pub fn init(world: &mut World) {
|
||||
|
||||
{
|
||||
let pool = world.read_resource::<SlowJobPool>();
|
||||
pool.configure("IMAGE_PROCESSING", |n| n / 2);
|
||||
pool.configure("FIGURE_MESHING", |n| n / 2);
|
||||
pool.configure("TERRAIN_MESHING", |n| n / 2);
|
||||
}
|
||||
|
@ -65,6 +65,9 @@ widget_ids! {
|
||||
show_peaks_img,
|
||||
show_peaks_box,
|
||||
show_peaks_text,
|
||||
show_voxel_map_img,
|
||||
show_voxel_map_box,
|
||||
show_voxel_map_text,
|
||||
show_difficulty_img,
|
||||
show_difficulty_box,
|
||||
show_difficulty_text,
|
||||
@ -200,6 +203,7 @@ impl<'a> Widget for Map<'a> {
|
||||
let show_caves = self.global_state.settings.interface.map_show_caves;
|
||||
let show_trees = self.global_state.settings.interface.map_show_trees;
|
||||
let show_peaks = self.global_state.settings.interface.map_show_peaks;
|
||||
let show_voxel_map = self.global_state.settings.interface.map_show_voxel_map;
|
||||
let show_topo_map = self.global_state.settings.interface.map_show_topo_map;
|
||||
let mut events = Vec::new();
|
||||
let i18n = &self.localized_strings;
|
||||
@ -636,6 +640,44 @@ impl<'a> Widget for Map<'a> {
|
||||
.graphics_for(state.ids.show_peaks_box)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.show_peaks_text, ui);
|
||||
// Voxel map (TODO: enable this once Pfau approves the final UI, and once
|
||||
// there's a non-placeholder graphic for the checkbox)
|
||||
const EXPOSE_VOXEL_MAP_TOGGLE_IN_UI: bool = false;
|
||||
if EXPOSE_VOXEL_MAP_TOGGLE_IN_UI {
|
||||
Image::new(self.imgs.mmap_poi_peak)
|
||||
.down_from(state.ids.show_peaks_img, 10.0)
|
||||
.w_h(20.0, 20.0)
|
||||
.set(state.ids.show_voxel_map_img, ui);
|
||||
if Button::image(if show_voxel_map {
|
||||
self.imgs.checkbox_checked
|
||||
} else {
|
||||
self.imgs.checkbox
|
||||
})
|
||||
.w_h(18.0, 18.0)
|
||||
.hover_image(if show_voxel_map {
|
||||
self.imgs.checkbox_checked_mo
|
||||
} else {
|
||||
self.imgs.checkbox_mo
|
||||
})
|
||||
.press_image(if show_voxel_map {
|
||||
self.imgs.checkbox_checked
|
||||
} else {
|
||||
self.imgs.checkbox_press
|
||||
})
|
||||
.right_from(state.ids.show_voxel_map_img, 10.0)
|
||||
.set(state.ids.show_voxel_map_box, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::SettingsChange(MapShowVoxelMap(!show_voxel_map)));
|
||||
}
|
||||
Text::new(i18n.get("hud.map.voxel_map"))
|
||||
.right_from(state.ids.show_voxel_map_box, 10.0)
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.graphics_for(state.ids.show_voxel_map_box)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.show_voxel_map_text, ui);
|
||||
}
|
||||
// Map icons
|
||||
if state.ids.mmap_poi_icons.len() < self.client.pois().len() {
|
||||
state.update(|state| {
|
||||
|
@ -4,22 +4,347 @@ use super::{
|
||||
TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
||||
};
|
||||
use crate::{
|
||||
hud::{Graphic, Ui},
|
||||
session::settings_change::{Interface as InterfaceChange, Interface::*},
|
||||
ui::{fonts::Fonts, img_ids},
|
||||
ui::{fonts::Fonts, img_ids, KeyedJobs},
|
||||
GlobalState,
|
||||
};
|
||||
use client::{self, Client};
|
||||
use common::{comp, comp::group::Role, terrain::TerrainChunkSize, vol::RectVolSize};
|
||||
use common::{
|
||||
comp,
|
||||
comp::group::Role,
|
||||
grid::Grid,
|
||||
slowjob::SlowJobPool,
|
||||
terrain::{Block, BlockKind, TerrainChunk, TerrainChunkSize, TerrainGrid},
|
||||
vol::{ReadVol, RectVolSize},
|
||||
};
|
||||
use common_net::msg::world_msg::SiteKind;
|
||||
use conrod_core::{
|
||||
color, position,
|
||||
widget::{self, Button, Image, Rectangle, Text},
|
||||
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use image::{DynamicImage, RgbaImage};
|
||||
use specs::{saveload::MarkerAllocator, WorldExt};
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
|
||||
struct MinimapColumn {
|
||||
/// Coordinate of lowest z-slice
|
||||
zlo: i32,
|
||||
/// Z-slices of colors and filled-ness
|
||||
layers: Vec<Grid<(Rgba<u8>, bool)>>,
|
||||
/// Color and filledness above the highest layer
|
||||
above: (Rgba<u8>, bool),
|
||||
/// Color and filledness below the lowest layer
|
||||
below: (Rgba<u8>, bool),
|
||||
}
|
||||
|
||||
pub struct VoxelMinimap {
|
||||
chunk_minimaps: HashMap<Vec2<i32>, MinimapColumn>,
|
||||
composited: RgbaImage,
|
||||
image_id: img_ids::Rotations,
|
||||
last_pos: Vec3<i32>,
|
||||
last_ceiling: i32,
|
||||
keyed_jobs: KeyedJobs<Vec2<i32>, MinimapColumn>,
|
||||
}
|
||||
|
||||
const VOXEL_MINIMAP_SIDELENGTH: u32 = 256;
|
||||
|
||||
impl VoxelMinimap {
|
||||
pub fn new(ui: &mut Ui) -> Self {
|
||||
let composited = RgbaImage::from_pixel(
|
||||
VOXEL_MINIMAP_SIDELENGTH,
|
||||
VOXEL_MINIMAP_SIDELENGTH,
|
||||
image::Rgba([0, 0, 0, 64]),
|
||||
);
|
||||
Self {
|
||||
chunk_minimaps: HashMap::new(),
|
||||
image_id: ui.add_graphic_with_rotations(Graphic::Image(
|
||||
Arc::new(DynamicImage::ImageRgba8(composited.clone())),
|
||||
Some(Rgba::from([0.0, 0.0, 0.0, 0.0])),
|
||||
)),
|
||||
composited,
|
||||
last_pos: Vec3::zero(),
|
||||
last_ceiling: 0,
|
||||
keyed_jobs: KeyedJobs::new("IMAGE_PROCESSING"),
|
||||
}
|
||||
}
|
||||
|
||||
fn block_color(block: &Block) -> Option<Rgba<u8>> {
|
||||
block
|
||||
.get_color()
|
||||
.map(|rgb| Rgba::new(rgb.r, rgb.g, rgb.b, 255))
|
||||
.or_else(|| {
|
||||
matches!(block.kind(), BlockKind::Water).then(|| Rgba::new(119, 149, 197, 255))
|
||||
})
|
||||
}
|
||||
|
||||
/// Each layer is a slice of the terrain near that z-level
|
||||
fn composite_layer_slice(chunk: &TerrainChunk, layers: &mut Vec<Grid<(Rgba<u8>, bool)>>) {
|
||||
for z in chunk.get_min_z()..chunk.get_max_z() {
|
||||
let grid = Grid::populate_from(Vec2::new(32, 32), |v| {
|
||||
let mut rgba = Rgba::<f32>::zero();
|
||||
let (weights, zoff) = (&[1, 2, 4, 1, 1, 1][..], -2);
|
||||
for dz in 0..weights.len() {
|
||||
let color = chunk
|
||||
.get(Vec3::new(v.x, v.y, dz as i32 + z + zoff))
|
||||
.ok()
|
||||
.and_then(Self::block_color)
|
||||
.unwrap_or_else(Rgba::zero);
|
||||
rgba += color.as_() * weights[dz as usize] as f32;
|
||||
}
|
||||
let rgba: Rgba<u8> = (rgba / weights.iter().map(|x| *x as f32).sum::<f32>()).as_();
|
||||
(rgba, true)
|
||||
});
|
||||
layers.push(grid);
|
||||
}
|
||||
}
|
||||
|
||||
/// Each layer is the overhead as if its z-level were the ceiling
|
||||
fn composite_layer_overhead(chunk: &TerrainChunk, layers: &mut Vec<Grid<(Rgba<u8>, bool)>>) {
|
||||
for z in chunk.get_min_z()..chunk.get_max_z() {
|
||||
let grid = Grid::populate_from(Vec2::new(32, 32), |v| {
|
||||
let mut rgba = None;
|
||||
|
||||
let mut seen_solids: u32 = 0;
|
||||
let mut seen_air: u32 = 0;
|
||||
for dz in chunk.get_min_z()..=z {
|
||||
if let Some(color) = chunk
|
||||
.get(Vec3::new(v.x, v.y, z - dz + chunk.get_min_z()))
|
||||
.ok()
|
||||
.and_then(Self::block_color)
|
||||
{
|
||||
if seen_air > 0 {
|
||||
rgba = Some(color);
|
||||
break;
|
||||
}
|
||||
seen_solids += 1;
|
||||
} else {
|
||||
seen_air += 1;
|
||||
}
|
||||
// Don't penetrate too far into ground, only penetrate through shallow
|
||||
// ceilings
|
||||
if seen_solids > 12 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let block = chunk.get(Vec3::new(v.x, v.y, z)).ok();
|
||||
// Treat Leaves and Wood as translucent for the purposes of ceiling checks,
|
||||
// since otherwise trees would cause ceiling removal to trigger
|
||||
// when running under a branch.
|
||||
let is_filled = block.map_or(true, |b| {
|
||||
b.is_filled() && !matches!(b.kind(), BlockKind::Leaves | BlockKind::Wood)
|
||||
});
|
||||
let rgba = rgba.unwrap_or_else(|| Rgba::new(0, 0, 0, 255));
|
||||
(rgba, is_filled)
|
||||
});
|
||||
layers.push(grid);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_chunks_near(
|
||||
&mut self,
|
||||
pool: &SlowJobPool,
|
||||
terrain: &TerrainGrid,
|
||||
cpos: Vec2<i32>,
|
||||
) -> bool {
|
||||
let mut new_chunks = false;
|
||||
|
||||
for (key, chunk) in terrain.iter() {
|
||||
let delta: Vec2<u32> = (key - cpos).map(i32::abs).as_();
|
||||
if delta.x < VOXEL_MINIMAP_SIDELENGTH / TerrainChunkSize::RECT_SIZE.x
|
||||
&& delta.y < VOXEL_MINIMAP_SIDELENGTH / TerrainChunkSize::RECT_SIZE.y
|
||||
&& !self.chunk_minimaps.contains_key(&key)
|
||||
{
|
||||
if let Some((_, column)) = self.keyed_jobs.spawn(Some(&pool), key, || {
|
||||
let arc_chunk = Arc::clone(chunk);
|
||||
move |_| {
|
||||
let mut layers = Vec::new();
|
||||
const MODE_OVERHEAD: bool = true;
|
||||
if MODE_OVERHEAD {
|
||||
Self::composite_layer_overhead(&arc_chunk, &mut layers);
|
||||
} else {
|
||||
Self::composite_layer_slice(&arc_chunk, &mut layers);
|
||||
}
|
||||
let above = arc_chunk
|
||||
.get(Vec3::new(0, 0, arc_chunk.get_max_z() + 1))
|
||||
.ok()
|
||||
.copied()
|
||||
.unwrap_or_else(Block::empty);
|
||||
let below = arc_chunk
|
||||
.get(Vec3::new(0, 0, arc_chunk.get_min_z() - 1))
|
||||
.ok()
|
||||
.copied()
|
||||
.unwrap_or_else(Block::empty);
|
||||
MinimapColumn {
|
||||
zlo: arc_chunk.get_min_z(),
|
||||
layers,
|
||||
above: (
|
||||
Self::block_color(&above).unwrap_or_else(Rgba::zero),
|
||||
above.is_filled(),
|
||||
),
|
||||
below: (
|
||||
Self::block_color(&below).unwrap_or_else(Rgba::zero),
|
||||
below.is_filled(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}) {
|
||||
self.chunk_minimaps.insert(key, column);
|
||||
new_chunks = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
new_chunks
|
||||
}
|
||||
|
||||
fn remove_chunks_far(&mut self, terrain: &TerrainGrid, cpos: Vec2<i32>) {
|
||||
self.chunk_minimaps.retain(|key, _| {
|
||||
let delta: Vec2<u32> = (key - cpos).map(i32::abs).as_();
|
||||
delta.x < 1 + VOXEL_MINIMAP_SIDELENGTH / TerrainChunkSize::RECT_SIZE.x
|
||||
&& delta.y < 1 + VOXEL_MINIMAP_SIDELENGTH / TerrainChunkSize::RECT_SIZE.y
|
||||
&& terrain.get_key(*key).is_some()
|
||||
});
|
||||
}
|
||||
|
||||
pub fn maintain(&mut self, client: &Client, ui: &mut Ui) {
|
||||
let player = client.entity();
|
||||
let pos = if let Some(pos) = client.state().ecs().read_storage::<comp::Pos>().get(player) {
|
||||
pos.0
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let vpos = pos.xy() - VOXEL_MINIMAP_SIDELENGTH as f32 / 2.0;
|
||||
let cpos: Vec2<i32> = vpos
|
||||
.map2(TerrainChunkSize::RECT_SIZE, |i, j| (i as u32).div_euclid(j))
|
||||
.as_();
|
||||
|
||||
let pool = client.state().ecs().read_resource::<SlowJobPool>();
|
||||
let terrain = client.state().terrain();
|
||||
let new_chunks = self.add_chunks_near(&pool, &terrain, cpos);
|
||||
self.remove_chunks_far(&terrain, cpos);
|
||||
|
||||
// ceiling_offset is the distance from the player to a block heuristically
|
||||
// detected as the ceiling height (a non-tree solid block above them, or
|
||||
// the sky if no such block exists). This is used for determining which
|
||||
// z-slice of the minimap to show, such that house roofs and caves and
|
||||
// dungeons are all handled uniformly.
|
||||
let ceiling_offset = {
|
||||
let voff = Vec2::new(
|
||||
VOXEL_MINIMAP_SIDELENGTH as f32,
|
||||
VOXEL_MINIMAP_SIDELENGTH as f32,
|
||||
) / 2.0;
|
||||
let coff: Vec2<i32> = voff
|
||||
.map2(TerrainChunkSize::RECT_SIZE, |i, j| (i as u32).div_euclid(j))
|
||||
.as_();
|
||||
let cmod: Vec2<i32> = vpos
|
||||
.map2(TerrainChunkSize::RECT_SIZE, |i, j| (i as u32).rem_euclid(j))
|
||||
.as_();
|
||||
let column = self.chunk_minimaps.get(&(cpos + coff));
|
||||
column
|
||||
.map(
|
||||
|MinimapColumn {
|
||||
zlo, layers, above, ..
|
||||
}| {
|
||||
(0..layers.len() as i32)
|
||||
.find(|dz| {
|
||||
layers
|
||||
.get((pos.z as i32 - zlo + dz) as usize)
|
||||
.and_then(|grid| grid.get(cmod))
|
||||
.map_or(false, |(_, b)| *b)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
// if the `find` returned None, there's no solid blocks above the
|
||||
// player within the chunk
|
||||
if above.1 {
|
||||
// if the `above` block is solid, the chunk has an infinite
|
||||
// solid ceiling, and so we render from 1 block above the
|
||||
// player (which is where the player's head is if they're 2
|
||||
// blocks tall)
|
||||
1
|
||||
} else {
|
||||
// if the ceiling is a non-solid sky, use the largest value
|
||||
// (subsequent arithmetic on ceiling_offset must be saturating)
|
||||
i32::MAX
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
.unwrap_or(0)
|
||||
};
|
||||
if cpos.distance_squared(self.last_pos.xy()) >= 1
|
||||
|| self.last_pos.z != pos.z as i32
|
||||
|| self.last_ceiling != ceiling_offset
|
||||
|| new_chunks
|
||||
{
|
||||
self.last_pos = cpos.with_z(pos.z as i32);
|
||||
self.last_ceiling = ceiling_offset;
|
||||
for y in 0..VOXEL_MINIMAP_SIDELENGTH {
|
||||
for x in 0..VOXEL_MINIMAP_SIDELENGTH {
|
||||
let voff = Vec2::new(x as f32, y as f32);
|
||||
let coff: Vec2<i32> = voff
|
||||
.map2(TerrainChunkSize::RECT_SIZE, |i, j| (i as u32).div_euclid(j))
|
||||
.as_();
|
||||
let cmod: Vec2<i32> = voff
|
||||
.map2(TerrainChunkSize::RECT_SIZE, |i, j| (i as u32).rem_euclid(j))
|
||||
.as_();
|
||||
let column = self.chunk_minimaps.get(&(cpos + coff));
|
||||
let color: Rgba<u8> = column
|
||||
.and_then(|column| {
|
||||
let MinimapColumn {
|
||||
zlo,
|
||||
layers,
|
||||
above,
|
||||
below,
|
||||
} = column;
|
||||
if (pos.z as i32).saturating_add(ceiling_offset) < *zlo {
|
||||
// If the ceiling is below the bottom of a chunk, color it black,
|
||||
// so that the middles of caves/dungeons don't show the forests
|
||||
// around them.
|
||||
Some(Rgba::new(0, 0, 0, 255))
|
||||
} else {
|
||||
// Otherwise, take the pixel from the precomputed z-level view at
|
||||
// the ceiling's height (using the top slice of the chunk if the
|
||||
// ceiling is above the chunk, (e.g. so that forests with
|
||||
// differently-tall trees are handled properly)
|
||||
layers
|
||||
.get(
|
||||
(((pos.z as i32 - zlo).saturating_add(ceiling_offset))
|
||||
as usize)
|
||||
.min(layers.len().saturating_sub(1)),
|
||||
)
|
||||
.and_then(|grid| grid.get(cmod).map(|c| c.0.as_()))
|
||||
.or_else(|| {
|
||||
Some(if pos.z as i32 > *zlo {
|
||||
above.0
|
||||
} else {
|
||||
below.0
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(Rgba::zero);
|
||||
self.composited.put_pixel(
|
||||
x,
|
||||
VOXEL_MINIMAP_SIDELENGTH - y - 1,
|
||||
image::Rgba([color.r, color.g, color.b, color.a]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ui.replace_graphic(
|
||||
self.image_id.none,
|
||||
Graphic::Image(
|
||||
Arc::new(DynamicImage::ImageRgba8(self.composited.clone())),
|
||||
Some(Rgba::from([0.0, 0.0, 0.0, 0.0])),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
widget_ids! {
|
||||
struct Ids {
|
||||
mmap_frame,
|
||||
@ -40,6 +365,7 @@ widget_ids! {
|
||||
mmap_site_icons[],
|
||||
member_indicators[],
|
||||
location_marker,
|
||||
voxel_minimap,
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,6 +382,7 @@ pub struct MiniMap<'a> {
|
||||
ori: Vec3<f32>,
|
||||
global_state: &'a GlobalState,
|
||||
location_marker: Option<Vec2<f32>>,
|
||||
voxel_minimap: &'a VoxelMinimap,
|
||||
}
|
||||
|
||||
impl<'a> MiniMap<'a> {
|
||||
@ -69,6 +396,7 @@ impl<'a> MiniMap<'a> {
|
||||
ori: Vec3<f32>,
|
||||
global_state: &'a GlobalState,
|
||||
location_marker: Option<Vec2<f32>>,
|
||||
voxel_minimap: &'a VoxelMinimap,
|
||||
) -> Self {
|
||||
Self {
|
||||
show,
|
||||
@ -81,6 +409,7 @@ impl<'a> MiniMap<'a> {
|
||||
ori,
|
||||
global_state,
|
||||
location_marker,
|
||||
voxel_minimap,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,6 +445,7 @@ impl<'a> Widget for MiniMap<'a> {
|
||||
let show_minimap = self.global_state.settings.interface.minimap_show;
|
||||
let is_facing_north = self.global_state.settings.interface.minimap_face_north;
|
||||
let show_topo_map = self.global_state.settings.interface.map_show_topo_map;
|
||||
let show_voxel_map = self.global_state.settings.interface.map_show_voxel_map;
|
||||
let orientation = if is_facing_north {
|
||||
Vec3::new(0.0, 1.0, 0.0)
|
||||
} else {
|
||||
@ -277,6 +607,34 @@ impl<'a> Widget for MiniMap<'a> {
|
||||
.set(state.ids.map_layers[index], ui);
|
||||
}
|
||||
}
|
||||
if show_voxel_map {
|
||||
let voxelmap_rotation = if is_facing_north {
|
||||
self.voxel_minimap.image_id.none
|
||||
} else {
|
||||
self.voxel_minimap.image_id.source_north
|
||||
};
|
||||
let cmod: Vec2<f64> = player_pos
|
||||
.xy()
|
||||
.map2(TerrainChunkSize::RECT_SIZE, |i, j| (i as u32).rem_euclid(j))
|
||||
.as_();
|
||||
let rect_src = position::Rect::from_xy_dim(
|
||||
[
|
||||
cmod.x + VOXEL_MINIMAP_SIDELENGTH as f64 / 2.0,
|
||||
-cmod.y + VOXEL_MINIMAP_SIDELENGTH as f64 / 2.0,
|
||||
],
|
||||
[
|
||||
TerrainChunkSize::RECT_SIZE.x as f64 * max_zoom / zoom,
|
||||
TerrainChunkSize::RECT_SIZE.y as f64 * max_zoom / zoom,
|
||||
],
|
||||
);
|
||||
Image::new(voxelmap_rotation)
|
||||
.middle_of(state.ids.mmap_frame_bg)
|
||||
.w_h(map_size.x, map_size.y)
|
||||
.parent(state.ids.mmap_frame_bg)
|
||||
.source_rectangle(rect_src)
|
||||
.graphics_for(state.ids.map_layers[0])
|
||||
.set(state.ids.voxel_minimap, ui);
|
||||
}
|
||||
|
||||
// Map icons
|
||||
if state.ids.mmap_site_icons.len() < self.client.sites().len() {
|
||||
|
@ -42,7 +42,7 @@ use img_ids::Imgs;
|
||||
use item_imgs::ItemImgs;
|
||||
use loot_scroller::LootScroller;
|
||||
use map::Map;
|
||||
use minimap::MiniMap;
|
||||
use minimap::{MiniMap, VoxelMinimap};
|
||||
use popup::Popup;
|
||||
use prompt_dialog::PromptDialog;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -81,6 +81,7 @@ use common::{
|
||||
},
|
||||
consts::MAX_PICKUP_RANGE,
|
||||
outcome::Outcome,
|
||||
slowjob::SlowJobPool,
|
||||
terrain::{SpriteKind, TerrainChunk},
|
||||
trade::{ReducedInventory, TradeAction},
|
||||
uid::Uid,
|
||||
@ -810,6 +811,7 @@ pub struct Hud {
|
||||
events: Vec<Event>,
|
||||
crosshair_opacity: f32,
|
||||
floaters: Floaters,
|
||||
voxel_minimap: VoxelMinimap,
|
||||
}
|
||||
|
||||
impl Hud {
|
||||
@ -866,6 +868,7 @@ impl Hud {
|
||||
);
|
||||
|
||||
Self {
|
||||
voxel_minimap: VoxelMinimap::new(&mut ui),
|
||||
ui,
|
||||
imgs,
|
||||
world_map,
|
||||
@ -957,6 +960,9 @@ impl Hud {
|
||||
) -> Vec<Event> {
|
||||
span!(_guard, "update_layout", "Hud::update_layout");
|
||||
let mut events = core::mem::take(&mut self.events);
|
||||
if global_state.settings.interface.map_show_voxel_map {
|
||||
self.voxel_minimap.maintain(&client, &mut self.ui);
|
||||
}
|
||||
let (ref mut ui_widgets, ref mut item_tooltip_manager, ref mut tooltip_manager) =
|
||||
&mut self.ui.set_widgets();
|
||||
// self.ui.set_item_widgets(); pulse time for pulsating elements
|
||||
@ -2364,6 +2370,7 @@ impl Hud {
|
||||
camera.get_orientation(),
|
||||
&global_state,
|
||||
self.show.location_marker,
|
||||
&self.voxel_minimap,
|
||||
)
|
||||
.set(self.ids.minimap, ui_widgets)
|
||||
{
|
||||
@ -3599,9 +3606,14 @@ impl Hud {
|
||||
|
||||
// Check if item images need to be reloaded
|
||||
self.item_imgs.reload_if_changed(&mut self.ui);
|
||||
|
||||
// TODO: using a thread pool in the obvious way for speeding up map zoom results
|
||||
// in flickering artifacts, figure out a better way to make use of the
|
||||
// thread pool
|
||||
let _pool = client.state().ecs().read_resource::<SlowJobPool>();
|
||||
self.ui.maintain(
|
||||
&mut global_state.window.renderer_mut(),
|
||||
None,
|
||||
//Some(&pool),
|
||||
Some(proj_mat * view_mat * Mat4::translation_3d(-focus_off)),
|
||||
);
|
||||
|
||||
|
@ -1563,6 +1563,7 @@ impl CharSelectionUi {
|
||||
self.controls
|
||||
.view(&global_state.settings, &client, &self.error, &i18n),
|
||||
global_state.window.renderer_mut(),
|
||||
None,
|
||||
global_state.clipboard.as_ref(),
|
||||
);
|
||||
|
||||
|
@ -4,7 +4,7 @@ use client::{
|
||||
Client, ServerInfo,
|
||||
};
|
||||
use common::consts::MIN_RECOMMENDED_TOKIO_THREADS;
|
||||
use crossbeam::channel::{unbounded, Receiver, Sender, TryRecvError};
|
||||
use crossbeam_channel::{unbounded, Receiver, Sender, TryRecvError};
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
|
@ -571,6 +571,7 @@ impl<'a> MainMenuUi {
|
||||
let (messages, _) = self.ui.maintain(
|
||||
self.controls.view(&global_state.settings, dt.as_secs_f32()),
|
||||
global_state.window.renderer_mut(),
|
||||
None,
|
||||
global_state.clipboard.as_ref(),
|
||||
);
|
||||
|
||||
|
@ -25,7 +25,7 @@ use common::{
|
||||
vol::BaseVol,
|
||||
};
|
||||
use core::{hash::Hash, ops::Range};
|
||||
use crossbeam::atomic;
|
||||
use crossbeam_utils::atomic;
|
||||
use hashbrown::{hash_map::Entry, HashMap};
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
|
@ -21,7 +21,7 @@ use common::{
|
||||
};
|
||||
use common_base::span;
|
||||
use core::{f32, fmt::Debug, i32, marker::PhantomData, time::Duration};
|
||||
use crossbeam::channel;
|
||||
use crossbeam_channel as channel;
|
||||
use enum_iterator::IntoEnumIterator;
|
||||
use guillotiere::AtlasAllocator;
|
||||
use hashbrown::HashMap;
|
||||
|
@ -122,6 +122,7 @@ pub enum Interface {
|
||||
MapShowCaves(bool),
|
||||
MapShowTrees(bool),
|
||||
MapShowPeaks(bool),
|
||||
MapShowVoxelMap(bool),
|
||||
|
||||
ResetInterfaceSettings,
|
||||
}
|
||||
@ -511,6 +512,9 @@ impl SettingsChange {
|
||||
Interface::MapShowPeaks(map_show_peaks) => {
|
||||
settings.interface.map_show_peaks = map_show_peaks;
|
||||
},
|
||||
Interface::MapShowVoxelMap(map_show_voxel_map) => {
|
||||
settings.interface.map_show_voxel_map = map_show_voxel_map;
|
||||
},
|
||||
Interface::ResetInterfaceSettings => {
|
||||
// Reset Interface Settings
|
||||
let tmp = settings.interface.intro_show;
|
||||
|
@ -34,6 +34,7 @@ pub struct InterfaceSettings {
|
||||
pub map_show_caves: bool,
|
||||
pub map_show_trees: bool,
|
||||
pub map_show_peaks: bool,
|
||||
pub map_show_voxel_map: bool,
|
||||
pub minimap_show: bool,
|
||||
pub minimap_face_north: bool,
|
||||
pub minimap_zoom: f64,
|
||||
@ -67,6 +68,7 @@ impl Default for InterfaceSettings {
|
||||
map_show_caves: true,
|
||||
map_show_trees: false,
|
||||
map_show_peaks: false,
|
||||
map_show_voxel_map: false,
|
||||
minimap_show: true,
|
||||
minimap_face_north: false,
|
||||
minimap_zoom: 10.0,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use common::{clock::Clock, consts::MIN_RECOMMENDED_TOKIO_THREADS};
|
||||
use crossbeam::channel::{bounded, unbounded, Receiver, Sender, TryRecvError};
|
||||
use crossbeam_channel::{bounded, unbounded, Receiver, Sender, TryRecvError};
|
||||
use server::{
|
||||
persistence::{DatabaseSettings, SqlLogMode},
|
||||
Error as ServerError, Event, Input, Server,
|
||||
|
@ -3,13 +3,16 @@ mod renderer;
|
||||
|
||||
pub use renderer::{SampleStrat, Transform};
|
||||
|
||||
use crate::render::{RenderError, Renderer, Texture};
|
||||
use common::figure::Segment;
|
||||
use crate::{
|
||||
render::{RenderError, Renderer, Texture},
|
||||
ui::KeyedJobs,
|
||||
};
|
||||
use common::{figure::Segment, slowjob::SlowJobPool};
|
||||
use guillotiere::{size2, SimpleAtlasAllocator};
|
||||
use hashbrown::{hash_map::Entry, HashMap};
|
||||
use image::{DynamicImage, RgbaImage};
|
||||
use pixel_art::resize_pixel_art;
|
||||
use std::sync::Arc;
|
||||
use std::{hash::Hash, sync::Arc};
|
||||
use tracing::warn;
|
||||
use vek::*;
|
||||
|
||||
@ -142,6 +145,9 @@ pub struct GraphicCache {
|
||||
textures: Vec<Texture>,
|
||||
// Stores the location of graphics rendered at a particular resolution and cached on the cpu
|
||||
cache_map: HashMap<Parameters, CachedDetails>,
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
keyed_jobs: KeyedJobs<(Id, Vec2<u16>), Option<(RgbaImage, Option<Rgba<f32>>)>>,
|
||||
}
|
||||
impl GraphicCache {
|
||||
pub fn new(renderer: &mut Renderer) -> Self {
|
||||
@ -153,6 +159,7 @@ impl GraphicCache {
|
||||
atlases: vec![(atlas, 0)],
|
||||
textures: vec![texture],
|
||||
cache_map: HashMap::default(),
|
||||
keyed_jobs: KeyedJobs::new("IMAGE_PROCESSING"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,6 +229,7 @@ impl GraphicCache {
|
||||
pub fn cache_res(
|
||||
&mut self,
|
||||
renderer: &mut Renderer,
|
||||
pool: Option<&SlowJobPool>,
|
||||
graphic_id: Id,
|
||||
dims: Vec2<u16>,
|
||||
source: Aabr<f64>,
|
||||
@ -277,7 +285,8 @@ impl GraphicCache {
|
||||
// graphic
|
||||
if !valid {
|
||||
// Create image
|
||||
let (image, border) = draw_graphic(graphic_map, graphic_id, dims)?;
|
||||
let (image, border) =
|
||||
draw_graphic(graphic_map, graphic_id, dims, &mut self.keyed_jobs, pool)?;
|
||||
// If the cache location is invalid, we know the underlying texture is mutable,
|
||||
// so we should be able to replace the graphic. However, we still want to make
|
||||
// sure that we are not reusing textures for images that specify a border
|
||||
@ -292,8 +301,10 @@ impl GraphicCache {
|
||||
Entry::Vacant(details) => details,
|
||||
};
|
||||
|
||||
// Construct image
|
||||
let (image, border_color) = draw_graphic(graphic_map, graphic_id, dims)?;
|
||||
// Construct image in a threadpool
|
||||
|
||||
let (image, border_color) =
|
||||
draw_graphic(graphic_map, graphic_id, dims, &mut self.keyed_jobs, pool)?;
|
||||
|
||||
// Upload
|
||||
let atlas_size = atlas_size(renderer);
|
||||
@ -382,23 +393,43 @@ impl GraphicCache {
|
||||
}
|
||||
|
||||
// Draw a graphic at the specified dimensions
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn draw_graphic(
|
||||
graphic_map: &GraphicMap,
|
||||
graphic_id: Id,
|
||||
dims: Vec2<u16>,
|
||||
keyed_jobs: &mut KeyedJobs<(Id, Vec2<u16>), Option<(RgbaImage, Option<Rgba<f32>>)>>,
|
||||
pool: Option<&SlowJobPool>,
|
||||
) -> Option<(RgbaImage, Option<Rgba<f32>>)> {
|
||||
match graphic_map.get(&graphic_id) {
|
||||
// Short-circuit spawning a job on the threadpool for blank graphics
|
||||
Some(Graphic::Blank) => None,
|
||||
// Render image at requested resolution
|
||||
// TODO: Use source aabr.
|
||||
Some(&Graphic::Image(ref image, border_color)) => Some((
|
||||
resize_pixel_art(&image.to_rgba8(), u32::from(dims.x), u32::from(dims.y)),
|
||||
border_color,
|
||||
)),
|
||||
Some(Graphic::Voxel(ref segment, trans, sample_strat)) => Some((
|
||||
renderer::draw_vox(&segment, dims, trans.clone(), *sample_strat),
|
||||
None,
|
||||
)),
|
||||
Some(inner) => {
|
||||
keyed_jobs
|
||||
.spawn(pool, (graphic_id, dims), || {
|
||||
let inner = inner.clone();
|
||||
move |_| {
|
||||
match inner {
|
||||
// Render image at requested resolution
|
||||
// TODO: Use source aabr.
|
||||
Graphic::Image(ref image, border_color) => Some((
|
||||
resize_pixel_art(
|
||||
&image.to_rgba8(),
|
||||
u32::from(dims.x),
|
||||
u32::from(dims.y),
|
||||
),
|
||||
border_color,
|
||||
)),
|
||||
Graphic::Voxel(ref segment, trans, sample_strat) => Some((
|
||||
renderer::draw_vox(&segment, dims, trans, sample_strat),
|
||||
None,
|
||||
)),
|
||||
Graphic::Blank => None,
|
||||
}
|
||||
}
|
||||
})
|
||||
.and_then(|(_, v)| v)
|
||||
},
|
||||
None => {
|
||||
warn!(
|
||||
?graphic_id,
|
||||
|
@ -15,6 +15,7 @@ use super::{
|
||||
scale::{Scale, ScaleMode},
|
||||
};
|
||||
use crate::{render::Renderer, window::Window, Error};
|
||||
use common::slowjob::SlowJobPool;
|
||||
use common_base::span;
|
||||
use iced::{mouse, Cache, Size, UserInterface};
|
||||
use iced_winit::Clipboard;
|
||||
@ -142,6 +143,7 @@ impl IcedUi {
|
||||
&mut self,
|
||||
root: E,
|
||||
renderer: &mut Renderer,
|
||||
pool: Option<&SlowJobPool>,
|
||||
clipboard: Option<&Clipboard>,
|
||||
) -> (Vec<M>, mouse::Interaction) {
|
||||
span!(_guard, "maintain", "IcedUi::maintain");
|
||||
@ -207,7 +209,7 @@ impl IcedUi {
|
||||
|
||||
self.cache = Some(user_interface.into_cache());
|
||||
|
||||
self.renderer.draw(primitive, renderer);
|
||||
self.renderer.draw(primitive, renderer, pool);
|
||||
|
||||
(messages, mouse_interaction)
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ use crate::{
|
||||
},
|
||||
Error,
|
||||
};
|
||||
use common::util::srgba_to_linear;
|
||||
use common::{slowjob::SlowJobPool, util::srgba_to_linear};
|
||||
use common_base::span;
|
||||
use std::{convert::TryInto, ops::Range};
|
||||
use vek::*;
|
||||
@ -184,7 +184,12 @@ impl IcedRenderer {
|
||||
self.cache.resize_glyph_cache(renderer).unwrap();
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, primitive: Primitive, renderer: &mut Renderer) {
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
primitive: Primitive,
|
||||
renderer: &mut Renderer,
|
||||
pool: Option<&SlowJobPool>,
|
||||
) {
|
||||
span!(_guard, "draw", "IcedRenderer::draw");
|
||||
// Re-use memory
|
||||
self.draw_commands.clear();
|
||||
@ -194,7 +199,7 @@ impl IcedRenderer {
|
||||
self.current_state = State::Plain;
|
||||
self.start = 0;
|
||||
|
||||
self.draw_primitive(primitive, Vec2::zero(), 1.0, renderer);
|
||||
self.draw_primitive(primitive, Vec2::zero(), 1.0, renderer, pool);
|
||||
|
||||
// Enter the final command.
|
||||
self.draw_commands.push(match self.current_state {
|
||||
@ -426,12 +431,13 @@ impl IcedRenderer {
|
||||
offset: Vec2<u32>,
|
||||
alpha: f32,
|
||||
renderer: &mut Renderer,
|
||||
pool: Option<&SlowJobPool>,
|
||||
) {
|
||||
match primitive {
|
||||
Primitive::Group { primitives } => {
|
||||
primitives
|
||||
.into_iter()
|
||||
.for_each(|p| self.draw_primitive(p, offset, alpha, renderer));
|
||||
.for_each(|p| self.draw_primitive(p, offset, alpha, renderer, pool));
|
||||
},
|
||||
Primitive::Image {
|
||||
handle,
|
||||
@ -536,6 +542,7 @@ impl IcedRenderer {
|
||||
// Cache graphic at particular resolution.
|
||||
let (uv_aabr, tex_id) = match graphic_cache.cache_res(
|
||||
renderer,
|
||||
pool,
|
||||
graphic_id,
|
||||
resolution,
|
||||
// TODO: take f32 here
|
||||
@ -730,7 +737,7 @@ impl IcedRenderer {
|
||||
// TODO: cull primitives outside the current scissor
|
||||
|
||||
// Renderer child
|
||||
self.draw_primitive(*content, offset + clip_offset, alpha, renderer);
|
||||
self.draw_primitive(*content, offset + clip_offset, alpha, renderer, pool);
|
||||
|
||||
// Reset scissor
|
||||
self.draw_commands.push(match self.current_state {
|
||||
@ -745,7 +752,7 @@ impl IcedRenderer {
|
||||
.push(DrawCommand::Scissor(self.window_scissor));
|
||||
},
|
||||
Primitive::Opacity { alpha: a, content } => {
|
||||
self.draw_primitive(*content, offset, alpha * a, renderer);
|
||||
self.draw_primitive(*content, offset, alpha * a, renderer, pool);
|
||||
},
|
||||
Primitive::Nothing => {},
|
||||
}
|
||||
|
102
voxygen/src/ui/keyed_jobs.rs
Normal file
102
voxygen/src/ui/keyed_jobs.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use common::slowjob::{SlowJob, SlowJobPool};
|
||||
use hashbrown::{hash_map::Entry, HashMap};
|
||||
use std::{
|
||||
hash::Hash,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
enum KeyedJobTask<V> {
|
||||
Pending(Instant, Option<SlowJob>),
|
||||
Completed(Instant, V),
|
||||
}
|
||||
|
||||
pub struct KeyedJobs<K, V> {
|
||||
tx: crossbeam_channel::Sender<(K, V)>,
|
||||
rx: crossbeam_channel::Receiver<(K, V)>,
|
||||
tasks: HashMap<K, KeyedJobTask<V>>,
|
||||
name: &'static str,
|
||||
last_gc: Instant,
|
||||
}
|
||||
|
||||
const KEYEDJOBS_GC_INTERVAL: Duration = Duration::from_secs(1);
|
||||
|
||||
impl<K: Hash + Eq + Send + Sync + 'static + Clone, V: Send + Sync + 'static> KeyedJobs<K, V> {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new(name: &'static str) -> Self {
|
||||
let (tx, rx) = crossbeam_channel::unbounded();
|
||||
Self {
|
||||
tx,
|
||||
rx,
|
||||
tasks: HashMap::new(),
|
||||
name,
|
||||
last_gc: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a task on a specified threadpool. The function is given as a thunk
|
||||
/// so that if work is needed to create captured variables (e.g.
|
||||
/// `Arc::clone`), that only occurs if the task hasn't yet been scheduled.
|
||||
pub fn spawn<F: FnOnce(&K) -> V + Send + Sync + 'static>(
|
||||
&mut self,
|
||||
pool: Option<&SlowJobPool>,
|
||||
k: K,
|
||||
f: impl FnOnce() -> F,
|
||||
) -> Option<(K, V)> {
|
||||
if let Some(pool) = pool {
|
||||
while let Ok((k2, v)) = self.rx.try_recv() {
|
||||
if k == k2 {
|
||||
return Some((k, v));
|
||||
} else {
|
||||
self.tasks
|
||||
.insert(k2, KeyedJobTask::Completed(Instant::now(), v));
|
||||
}
|
||||
}
|
||||
let now = Instant::now();
|
||||
if now - self.last_gc > KEYEDJOBS_GC_INTERVAL {
|
||||
self.last_gc = now;
|
||||
self.tasks.retain(|_, task| match task {
|
||||
KeyedJobTask::Completed(at, _) => now - *at < KEYEDJOBS_GC_INTERVAL,
|
||||
KeyedJobTask::Pending(at, job) => {
|
||||
let fresh = now - *at < KEYEDJOBS_GC_INTERVAL;
|
||||
if !fresh {
|
||||
if let Some(job) = job.take() {
|
||||
pool.cancel(job)
|
||||
}
|
||||
}
|
||||
fresh
|
||||
},
|
||||
});
|
||||
}
|
||||
match self.tasks.entry(k.clone()) {
|
||||
Entry::Occupied(e) => {
|
||||
let mut ret = None;
|
||||
e.replace_entry_with(|_, v| {
|
||||
if let KeyedJobTask::Completed(_, v) = v {
|
||||
ret = Some((k, v));
|
||||
None
|
||||
} else {
|
||||
Some(v)
|
||||
}
|
||||
});
|
||||
ret
|
||||
},
|
||||
Entry::Vacant(e) => {
|
||||
// TODO: consider adding a limit to the number of submitted jobs based on the
|
||||
// number of available threads, once SlowJobPool supports a notion of
|
||||
// approximating that
|
||||
let tx = self.tx.clone();
|
||||
let f = f();
|
||||
let job = pool.spawn(self.name, move || {
|
||||
let v = f(&k);
|
||||
let _ = tx.send((k, v));
|
||||
});
|
||||
e.insert(KeyedJobTask::Pending(Instant::now(), Some(job)));
|
||||
None
|
||||
},
|
||||
}
|
||||
} else {
|
||||
let v = f()(&k);
|
||||
Some((k, v))
|
||||
}
|
||||
}
|
||||
}
|
@ -8,9 +8,11 @@ pub mod img_ids;
|
||||
#[macro_use]
|
||||
pub mod fonts;
|
||||
pub mod ice;
|
||||
pub mod keyed_jobs;
|
||||
|
||||
pub use event::Event;
|
||||
pub use graphic::{Graphic, Id as GraphicId, Rotation, SampleStrat, Transform};
|
||||
pub use keyed_jobs::KeyedJobs;
|
||||
pub use scale::{Scale, ScaleMode};
|
||||
pub use widgets::{
|
||||
image_frame::ImageFrame,
|
||||
@ -34,7 +36,7 @@ use crate::{
|
||||
#[rustfmt::skip]
|
||||
use ::image::GenericImageView;
|
||||
use cache::Cache;
|
||||
use common::util::srgba_to_linear;
|
||||
use common::{slowjob::SlowJobPool, util::srgba_to_linear};
|
||||
use common_base::span;
|
||||
use conrod_core::{
|
||||
event::Input,
|
||||
@ -306,7 +308,12 @@ impl Ui {
|
||||
pub fn widget_input(&self, id: widget::Id) -> Widget { self.ui.widget_input(id) }
|
||||
|
||||
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
|
||||
pub fn maintain(&mut self, renderer: &mut Renderer, view_projection_mat: Option<Mat4<f32>>) {
|
||||
pub fn maintain(
|
||||
&mut self,
|
||||
renderer: &mut Renderer,
|
||||
pool: Option<&SlowJobPool>,
|
||||
view_projection_mat: Option<Mat4<f32>>,
|
||||
) {
|
||||
span!(_guard, "maintain", "Ui::maintain");
|
||||
// Maintain tooltip manager
|
||||
self.tooltip_manager
|
||||
@ -353,16 +360,17 @@ impl Ui {
|
||||
}
|
||||
|
||||
let mut retry = false;
|
||||
self.maintain_internal(renderer, view_projection_mat, &mut retry);
|
||||
self.maintain_internal(renderer, pool, view_projection_mat, &mut retry);
|
||||
if retry {
|
||||
// Update the glyph cache and try again.
|
||||
self.maintain_internal(renderer, view_projection_mat, &mut retry);
|
||||
self.maintain_internal(renderer, pool, view_projection_mat, &mut retry);
|
||||
}
|
||||
}
|
||||
|
||||
fn maintain_internal(
|
||||
&mut self,
|
||||
renderer: &mut Renderer,
|
||||
pool: Option<&SlowJobPool>,
|
||||
view_projection_mat: Option<Mat4<f32>>,
|
||||
retry: &mut bool,
|
||||
) {
|
||||
@ -806,6 +814,7 @@ impl Ui {
|
||||
// Cache graphic at particular resolution.
|
||||
let (uv_aabr, tex_id) = match graphic_cache.cache_res(
|
||||
renderer,
|
||||
pool,
|
||||
*graphic_id,
|
||||
resolution,
|
||||
source_aabr,
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
ui, Error,
|
||||
};
|
||||
use common_base::span;
|
||||
use crossbeam::channel;
|
||||
use crossbeam_channel as channel;
|
||||
use gilrs::{EventType, Gilrs};
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
|
Loading…
Reference in New Issue
Block a user