Jelajahi Sumber

wallet: begin making async version of gfx subsystem

darkfi 2 tahun lalu
induk
melakukan
0d478f0721

+ 6 - 0
bin/darkwallet/Cargo.toml

@@ -28,6 +28,10 @@ thiserror = "1.0.61"
 # Only for async Mutex
 # Only for async Mutex
 smol = "2.0.0"
 smol = "2.0.0"
 atomic_float = "1.0.0"
 atomic_float = "1.0.0"
+async-channel = "2.3.1"
+easy-parallel = "3.3.1"
+crossbeam-skiplist = "0.1"
+rand = "0.8.5"
 
 
 #rustpython-vm = "0.3.1"
 #rustpython-vm = "0.3.1"
 
 
@@ -50,3 +54,5 @@ simplelog = "0.12.1"
 [lints]
 [lints]
 workspace = true
 workspace = true
 
 
+[workspace.lints.clippy]
+

+ 3 - 0
bin/darkwallet/src/error.rs

@@ -98,4 +98,7 @@ pub enum Error {
 
 
     #[error("S-expr global not found")]
     #[error("S-expr global not found")]
     SExprGlobalNotFound = 32,
     SExprGlobalNotFound = 32,
+
+    #[error("Graphics window closed")]
+    GfxWindowClosed = 33,
 }
 }

+ 404 - 0
bin/darkwallet/src/gfx2.rs

@@ -0,0 +1,404 @@
+use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable};
+use freetype as ft;
+use log::{debug, LevelFilter};
+use miniquad::{
+    conf, window, Backend, Bindings, BlendFactor, BlendState, BlendValue, BufferId, BufferLayout,
+    BufferSource, BufferType, BufferUsage, Equation, EventHandler, KeyCode, KeyMods, MouseButton,
+    PassAction, Pipeline, PipelineParams, RenderingBackend, ShaderMeta, ShaderSource, TextureId,
+    UniformDesc, UniformType, VertexAttribute, VertexFormat,
+};
+use std::{
+    array::IntoIter,
+    collections::HashMap,
+    fmt,
+    io::Cursor,
+    sync::{mpsc, Arc, Mutex, MutexGuard},
+    time::{Duration, Instant},
+};
+
+use crate::{
+    error::{Error, Result},
+    chatview,
+    editbox,
+    expr::{SExprMachine, SExprVal},
+    gfx::Rectangle,
+    keysym::{KeyCodeAsStr, MouseButtonAsU8},
+    prop::{Property, PropertySubType, PropertyType},
+    res::{ResourceId, ResourceManager},
+    scene::{
+        MethodResponseFn, SceneGraph, SceneGraphPtr, SceneNode, SceneNodeId, SceneNodeInfo,
+        SceneNodeType, Pimpl
+    },
+    shader,
+};
+
+#[derive(Debug, SerialEncodable, SerialDecodable)]
+#[repr(C)]
+pub struct Vertex {
+    pub pos: [f32; 2],
+    pub color: [f32; 4],
+    pub uv: [f32; 2],
+}
+
+pub struct RenderApi {
+    method_sendr: mpsc::Sender<GraphicsMethod>,
+}
+
+impl RenderApi {
+    pub fn new(
+    method_sendr: mpsc::Sender<GraphicsMethod>,
+    ) -> Arc<Self> {
+        Arc::new(Self { method_sendr })
+    }
+
+    async fn new_texture(&self, width: u16, height: u16, data: Vec<u8>) -> Result<TextureId> {
+        let (sendr, recvr) = async_channel::bounded(1);
+
+        let method = GraphicsMethod::NewTexture((width, height, data, sendr));
+
+        self.method_sendr.send(method).map_err(|_| Error::GfxWindowClosed)?;
+
+        let texture_id = recvr.recv().await.map_err(|_| Error::GfxWindowClosed)?;
+        Ok(texture_id)
+    }
+
+    fn delete_texture(&self, texture: TextureId) {
+        let method = GraphicsMethod::DeleteTexture(texture);
+
+        // Ignore any error
+        let _ = self.method_sendr.send(method);
+    }
+
+    pub async fn new_vertex_buffer(&self, verts: Vec<Vertex>) -> Result<BufferId> {
+        let (sendr, recvr) = async_channel::bounded(1);
+
+        let method = GraphicsMethod::NewVertexBuffer((verts, sendr));
+
+        self.method_sendr.send(method).map_err(|_| Error::GfxWindowClosed)?;
+
+        let buffer = recvr.recv().await.map_err(|_| Error::GfxWindowClosed)?;
+        Ok(buffer)
+    }
+
+    pub async fn new_index_buffer(&self, indices: Vec<u16>) -> Result<BufferId> {
+        let (sendr, recvr) = async_channel::bounded(1);
+
+        let method = GraphicsMethod::NewIndexBuffer((indices, sendr));
+
+        self.method_sendr.send(method).map_err(|_| Error::GfxWindowClosed)?;
+
+        let buffer = recvr.recv().await.map_err(|_| Error::GfxWindowClosed)?;
+        Ok(buffer)
+    }
+
+    pub fn delete_buffer(&self, buffer: BufferId) {
+        let method = GraphicsMethod::DeleteBuffer(buffer);
+
+        // Ignore any error
+        let _ = self.method_sendr.send(method);
+    }
+
+    pub async fn replace_draw_call(&self, loc: Vec<usize>, draw_call: DrawCall) {
+        let method = GraphicsMethod::ReplaceDrawCall((loc, draw_call));
+
+        // Ignore any error
+        let _ = self.method_sendr.send(method);
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct DrawMesh {
+    pub vertex_buffer: BufferId,
+    pub index_buffer: BufferId,
+    pub texture: Option<TextureId>,
+    pub num_elements: i32,
+}
+
+#[derive(Debug)]
+pub enum DrawInstruction {
+    ApplyViewport(Rectangle<i32>),
+    ApplyMatrix(glam::Mat4),
+    Draw(DrawMesh),
+}
+
+#[derive(Debug)]
+pub struct DrawCall {
+    pub instrs: Vec<DrawInstruction>,
+    pub dcs: Vec<DrawCall>
+}
+
+struct RenderContext<'a> {
+    ctx: &'a mut Box<dyn RenderingBackend>,
+    root_dc: &'a DrawCall,
+    uniforms_data: [u8; 128],
+    white_texture: TextureId
+}
+
+impl<'a> RenderContext<'a> {
+    fn draw(&mut self) {
+        self.draw_call(self.root_dc);
+    }
+
+    fn draw_call(&mut self, draw_call: &DrawCall) {
+        for instr in &draw_call.instrs {
+            match instr {
+                DrawInstruction::ApplyViewport(view) => {
+                    let (_, screen_height) = window::screen_size();
+
+                    let mut view = view.clone();
+                    view.y = screen_height as i32 - (view.y + view.h);
+
+                    //debug!("apply_viewport({:?})", view);
+                    self.ctx.apply_viewport(view.x, view.y, view.w, view.h);
+                    self.ctx.apply_scissor_rect(view.x, view.y, view.w, view.h);
+                }
+                DrawInstruction::ApplyMatrix(model) => {
+                    //debug!("apply_matrix({:?})", model);
+                    let data: [u8; 64] = unsafe { std::mem::transmute_copy(model) };
+                    self.uniforms_data[64..].copy_from_slice(&data);
+                    self.ctx.apply_uniforms_from_bytes(self.uniforms_data.as_ptr(), self.uniforms_data.len());
+                }
+                DrawInstruction::Draw(mesh) => {
+                    //debug!("draw(mesh)");
+                    let texture = match mesh.texture {
+                        Some(texture) => texture,
+                        None => self.white_texture
+                    };
+                    let bindings =
+                        Bindings { vertex_buffers: vec![mesh.vertex_buffer], index_buffer: mesh.index_buffer, images: vec![texture] };
+                    self.ctx.apply_bindings(&bindings);
+                    self.ctx.draw(0, mesh.num_elements, 1);
+                }
+            }
+        }
+
+        for dc in &draw_call.dcs {
+            self.draw_call(dc);
+        }
+    }
+}
+
+#[derive(Debug)]
+pub enum GraphicsMethod {
+    NewTexture((u16, u16, Vec<u8>, async_channel::Sender<TextureId>)),
+    DeleteTexture(TextureId),
+    NewVertexBuffer((Vec<Vertex>, async_channel::Sender<BufferId>)),
+    NewIndexBuffer((Vec<u16>, async_channel::Sender<BufferId>)),
+    DeleteBuffer(BufferId),
+    ReplaceDrawCall((Vec<usize>, DrawCall)),
+}
+
+struct Stage {
+    ctx: Box<dyn RenderingBackend>,
+    pipeline: Pipeline,
+    white_texture: TextureId,
+    root_dc: DrawCall,
+    last_draw_time: Option<Instant>,
+
+    method_recvr: mpsc::Receiver<GraphicsMethod>,
+}
+
+impl Stage {
+    pub fn new(
+    method_recvr: mpsc::Receiver<GraphicsMethod>,
+        ) -> Self {
+        let mut ctx: Box<dyn RenderingBackend> = window::new_rendering_backend();
+
+        let white_texture = ctx.new_texture_from_rgba8(1, 1, &[255, 255, 255, 255]);
+
+        let mut shader_meta: ShaderMeta = shader::meta();
+        shader_meta.uniforms.uniforms.push(UniformDesc::new("Projection", UniformType::Mat4));
+        shader_meta.uniforms.uniforms.push(UniformDesc::new("Model", UniformType::Mat4));
+
+        let shader = ctx
+            .new_shader(
+                match ctx.info().backend {
+                    Backend::OpenGl => ShaderSource::Glsl {
+                        vertex: shader::GL_VERTEX,
+                        fragment: shader::GL_FRAGMENT,
+                    },
+                    Backend::Metal => ShaderSource::Msl { program: shader::METAL },
+                },
+                shader_meta,
+            )
+            .unwrap();
+
+        let params = PipelineParams {
+            color_blend: Some(BlendState::new(
+                Equation::Add,
+                BlendFactor::Value(BlendValue::SourceAlpha),
+                BlendFactor::OneMinusValue(BlendValue::SourceAlpha),
+            )),
+            ..Default::default()
+        };
+
+        let pipeline = ctx.new_pipeline(
+            &[BufferLayout::default()],
+            &[
+                VertexAttribute::new("in_pos", VertexFormat::Float2),
+                VertexAttribute::new("in_color", VertexFormat::Float4),
+                VertexAttribute::new("in_uv", VertexFormat::Float2),
+            ],
+            shader,
+            params,
+        );
+
+        Stage {
+            ctx,
+            pipeline,
+            white_texture,
+            root_dc: DrawCall {
+                instrs: vec![],
+                dcs: vec![]
+            },
+            last_draw_time: None,
+            method_recvr
+        }
+    }
+
+    fn method_new_texture(&mut self, width: u16, height: u16, data: Vec<u8>,
+        sendr: async_channel::Sender<TextureId>
+        ) {
+        let texture = self.ctx.new_texture_from_rgba8(width, height, &data);
+        sendr.try_send(texture).unwrap();
+    }
+    fn method_delete_texture(&mut self, texture: TextureId) {
+        self.ctx.delete_texture(texture);
+    }
+    fn method_new_vertex_buffer(&mut self, verts: Vec<Vertex>,
+        sendr: async_channel::Sender<BufferId>
+        ) {
+        let buffer = self.ctx.new_buffer(
+            BufferType::VertexBuffer,
+            BufferUsage::Immutable,
+            BufferSource::slice(&verts),
+        );
+        sendr.try_send(buffer).unwrap();
+    }
+    fn method_new_index_buffer(&mut self, indices: Vec<u16>,
+        sendr: async_channel::Sender<BufferId>
+        ) {
+        let buffer = self.ctx.new_buffer(
+            BufferType::IndexBuffer,
+            BufferUsage::Immutable,
+            BufferSource::slice(&indices),
+        );
+        sendr.try_send(buffer).unwrap();
+    }
+    fn method_delete_buffer(&mut self, buffer: BufferId) {
+        self.ctx.delete_buffer(buffer);
+    }
+    fn method_replace_draw_call(&mut self, mut loc: Vec<usize>, new_dc: DrawCall) {
+        loc.reverse();
+        let mut dc = &mut self.root_dc;
+
+        while let Some(i) = loc.pop() {
+            dc = &mut dc.dcs[i];
+        }
+
+        std::mem::replace(dc, new_dc);
+    }
+}
+
+impl EventHandler for Stage {
+    fn update(&mut self) {
+        if self.last_draw_time.is_none() {
+            return
+        }
+
+        // Only allow 20 ms, process as much as we can during that time
+        let elapsed_since_draw = self.last_draw_time.unwrap().elapsed();
+        // We're long overdue a redraw. Exit for now
+        if elapsed_since_draw > Duration::from_millis(20) {
+            return
+        }
+        // The next redraw must happen 20ms since its last one.
+        // Calculate how much time is remaining until then.
+        let allowed_time = Duration::from_millis(20) - elapsed_since_draw;
+        let deadline = Instant::now() + allowed_time;
+
+        loop {
+            let Ok(method) =
+                self.method_recvr.recv_deadline(deadline)
+            else {
+                break
+            };
+            match method {
+                GraphicsMethod::NewTexture((width, height, data, sendr)) => self.method_new_texture(width, height, data, sendr),
+                GraphicsMethod::DeleteTexture(texture) => self.method_delete_texture(texture),
+                GraphicsMethod::NewVertexBuffer((verts, sendr)) => self.method_new_vertex_buffer(verts, sendr),
+                GraphicsMethod::NewIndexBuffer((indices, sendr)) => self.method_new_index_buffer(indices, sendr),
+                GraphicsMethod::DeleteBuffer(buffer) => self.method_delete_buffer(buffer),
+                GraphicsMethod::ReplaceDrawCall((loc, dc)) => self.method_replace_draw_call(loc, dc),
+            };
+        }
+    }
+
+    fn draw(&mut self) {
+        self.last_draw_time = Some(Instant::now());
+
+        self.ctx.begin_default_pass(PassAction::Nothing);
+        self.ctx.apply_pipeline(&self.pipeline);
+
+        // This will make the top left (0, 0) and the bottom right (1, 1)
+        // Default is (-1, 1) -> (1, -1)
+        let proj = glam::Mat4::from_translation(glam::Vec3::new(-1., 1., 0.)) *
+            glam::Mat4::from_scale(glam::Vec3::new(2., -2., 1.));
+
+        let mut uniforms_data = [0u8; 128];
+        let data: [u8; 64] = unsafe { std::mem::transmute_copy(&proj) };
+        uniforms_data[0..64].copy_from_slice(&data);
+        //let data: [u8; 64] = unsafe { std::mem::transmute_copy(&model) };
+        //uniforms_data[64..].copy_from_slice(&data);
+        assert_eq!(128, 2 * UniformType::Mat4.size());
+
+        let mut render_ctx = RenderContext {
+            ctx: &mut self.ctx,
+            root_dc: &self.root_dc,
+            uniforms_data,
+            white_texture: self.white_texture,
+        };
+        render_ctx.draw();
+
+        self.ctx.commit_frame();
+    }
+}
+
+pub fn run_gui(
+    method_recvr: mpsc::Receiver<GraphicsMethod>,
+    ) {
+    #[cfg(target_os = "android")]
+    {
+        android_logger::init_once(
+            android_logger::Config::default().with_max_level(LevelFilter::Debug).with_tag("darkfi"),
+        );
+    }
+
+    #[cfg(target_os = "linux")]
+    {
+        let term_logger = simplelog::TermLogger::new(
+            simplelog::LevelFilter::Debug,
+            simplelog::Config::default(),
+            simplelog::TerminalMode::Mixed,
+            simplelog::ColorChoice::Auto,
+        );
+        simplelog::CombinedLogger::init(vec![term_logger]).expect("logger");
+    }
+
+    let mut conf = miniquad::conf::Conf {
+        high_dpi: true,
+        window_resizable: true,
+        platform: miniquad::conf::Platform {
+            linux_backend: miniquad::conf::LinuxBackend::WaylandWithX11Fallback,
+            wayland_use_fallback_decorations: false,
+            ..Default::default()
+        },
+        ..Default::default()
+    };
+    let metal = std::env::args().nth(1).as_deref() == Some("metal");
+    conf.platform.apple_gfx_api =
+        if metal { conf::AppleGfxApi::Metal } else { conf::AppleGfxApi::OpenGl };
+
+    miniquad::start(conf, || Box::new(Stage::new(method_recvr)));
+}
+

+ 120 - 4
bin/darkwallet/src/main.rs

@@ -2,7 +2,7 @@
 #![feature(str_split_whitespace_remainder)]
 #![feature(str_split_whitespace_remainder)]
 
 
 use std::{
 use std::{
-    sync::{Arc, Mutex},
+    sync::{Arc, Mutex, mpsc},
     thread,
     thread,
 };
 };
 
 
@@ -19,6 +19,8 @@ mod expr;
 mod gfx;
 mod gfx;
 use gfx::run_gui;
 use gfx::run_gui;
 
 
+mod gfx2;
+
 mod keysym;
 mod keysym;
 
 
 mod net;
 mod net;
@@ -31,6 +33,8 @@ mod plugin;
 
 
 mod prop;
 mod prop;
 
 
+mod pubsub;
+
 mod py;
 mod py;
 
 
 mod res;
 mod res;
@@ -39,6 +43,8 @@ mod shader;
 
 
 mod text;
 mod text;
 
 
+use crate::error::{Result, Error};
+
 #[macro_use]
 #[macro_use]
 extern crate log;
 extern crate log;
 #[allow(unused_imports)]
 #[allow(unused_imports)]
@@ -61,12 +67,122 @@ fn start_sentinel(scene_graph: SceneGraphPtr) {
     });
     });
 }
 }
 
 
+async fn amain(ex: Arc<smol::Executor<'static>>, render_api: Arc<gfx2::RenderApi>) {
+    let x1 = 0.1;
+    let x2 = 0.6;
+    let y1 = 0.1;
+    let y2 = 0.6;
+    let color = [1., 0., 0., 1.];
+
+    let verts = vec![
+        gfx2::Vertex { pos: [x1, y1], color, uv: [0., 0.] },
+        gfx2::Vertex { pos: [x2, y1], color, uv: [1., 0.] },
+        gfx2::Vertex { pos: [x1, y2], color, uv: [0., 1.] },
+        gfx2::Vertex { pos: [x2, y2], color, uv: [1., 1.] },
+    ];
+    let vertex_buffer = render_api.new_vertex_buffer(verts).await.unwrap();
+
+    let indices = vec![0, 2, 1, 1, 2, 3];
+    let index_buffer = render_api.new_index_buffer(indices).await.unwrap();
+
+    let (off_x, off_y) = (0., 0.);
+    let (screen_width, screen_height) = miniquad::window::screen_size();
+    let (scale_x, scale_y) = (1./screen_width, 1./screen_height);
+    let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
+        glam::Mat4::from_scale(glam::Vec3::new(scale_x, scale_y, 1.));
+    let model = glam::Mat4::IDENTITY;
+
+    // We have to handle window resizing for viewport and matrix
+
+    let dc = gfx2::DrawCall {
+        instrs: vec![
+            //gfx2::DrawInstruction::ApplyViewport(gfx::Rectangle {
+            //    x: 0, y: 0,
+            //    w: screen_width as i32,
+            //    h: screen_height as i32,
+            //}),
+        ],
+        dcs: vec![
+            gfx2::DrawCall {
+                instrs: vec![
+                    gfx2::DrawInstruction::ApplyMatrix(model),
+                    gfx2::DrawInstruction::Draw(gfx2::DrawMesh {
+                        vertex_buffer,
+                        index_buffer,
+                        texture: None,
+                        num_elements: 6
+                    })
+                ],
+                dcs: vec![]
+            }
+        ]
+    };
+    render_api.replace_draw_call(vec![], dc).await;
+
+    smol::Timer::after(std::time::Duration::from_secs(2)).await;
+
+    let x1 = 0.1;
+    let x2 = 0.95;
+    let y1 = 0.1;
+    let y2 = 0.95;
+    let color = [0., 1., 0., 1.];
+
+    let verts = vec![
+        gfx2::Vertex { pos: [x1, y1], color, uv: [0., 0.] },
+        gfx2::Vertex { pos: [x2, y1], color, uv: [1., 0.] },
+        gfx2::Vertex { pos: [x1, y2], color, uv: [0., 1.] },
+        gfx2::Vertex { pos: [x2, y2], color, uv: [1., 1.] },
+    ];
+    let vertex_buffer2 = render_api.new_vertex_buffer(verts).await.unwrap();
+
+    let dc = gfx2::DrawCall {
+        instrs: vec![
+            gfx2::DrawInstruction::ApplyMatrix(model),
+            gfx2::DrawInstruction::Draw(gfx2::DrawMesh {
+                vertex_buffer: vertex_buffer2,
+                index_buffer,
+                texture: None,
+                num_elements: 6
+            })
+        ],
+        dcs: vec![]
+    };
+    render_api.replace_draw_call(vec![0], dc).await;
+    render_api.delete_buffer(vertex_buffer);
+
+    println!("hello!");
+}
+
 fn main() {
 fn main() {
     let scene_graph = Arc::new(Mutex::new(SceneGraph::new()));
     let scene_graph = Arc::new(Mutex::new(SceneGraph::new()));
 
 
-    start_zmq(scene_graph.clone());
-    start_sentinel(scene_graph.clone());
-    run_gui(scene_graph);
+    let (method_sender, method_recvr) = mpsc::channel();
+    let render_api = gfx2::RenderApi::new(method_sender);
+
+    let gfx_handle = thread::spawn(move || {
+        //gfx2::run_gui(method_recvr);
+    });
+
+    let n_threads = std::thread::available_parallelism().unwrap().get();
+    let ex = std::sync::Arc::new(smol::Executor::new());
+    let (signal, shutdown) = smol::channel::unbounded::<()>();
+    easy_parallel::Parallel::new()
+        // Executor threads
+        .each(1..n_threads, |_| smol::future::block_on(ex.run(shutdown.recv())))
+        // Run the main future on this thread
+        .finish(|| {
+            smol::future::block_on(async {
+                amain(ex.clone(), render_api).await;
+                drop(signal);
+                Ok::<(), Error>(())
+            });
+        });
+
+    gfx_handle.join();
+
+    //start_zmq(scene_graph.clone());
+    //start_sentinel(scene_graph.clone());
+    //run_gui(scene_graph);
 }
 }
 
 
 /*
 /*

+ 87 - 0
bin/darkwallet/src/pubsub.rs

@@ -0,0 +1,87 @@
+use crossbeam_skiplist::SkipMap;
+use rand::{rngs::OsRng, Rng};
+use std::sync::Arc;
+
+pub type SubscriptionId = usize;
+
+#[derive(Debug)]
+/// Subscription to the Publisher. Created using `publisher.subscribe().await`.
+pub struct Subscription<T> {
+    id: SubscriptionId,
+    recv_queue: smol::channel::Receiver<T>,
+    parent: Arc<Publisher<T>>,
+}
+
+impl<T: Clone + Send + 'static> Subscription<T> {
+    pub fn get_id(&self) -> SubscriptionId {
+        self.id
+    }
+
+    /// Receive message.
+    pub async fn receive(&self) -> T {
+        let message_result = self.recv_queue.recv().await;
+
+        match message_result {
+            Ok(message_result) => message_result,
+            Err(err) => {
+                panic!("Subscription::receive() recv_queue failed! {}", err);
+            }
+        }
+    }
+
+    /// Must be called manually since async Drop is not possible in Rust
+    pub fn unsubscribe(&self) {
+        self.parent.clone().unsubscribe(self.id)
+    }
+}
+
+#[derive(Debug)]
+pub struct Publisher<T> {
+    subs: SkipMap<SubscriptionId, smol::channel::Sender<T>>
+}
+
+impl<T: Clone + Send + 'static> Publisher<T> {
+    pub fn new() -> Arc<Self> {
+        Arc::new(Self { subs: SkipMap::new() })
+    }
+
+    pub async fn subscribe(self: Arc<Self>) -> Subscription<T> {
+        let (sendr, recvr) = smol::channel::unbounded();
+        let sub_id = OsRng.gen();
+        // Optional to check whether this ID already exists.
+        // It is nearly impossible to ever happen.
+        self.subs.insert(sub_id, sendr);
+
+        Subscription { id: sub_id, recv_queue: recvr, parent: self.clone() }
+    }
+
+    fn unsubscribe(self: Arc<Self>, sub_id: SubscriptionId) {
+        self.subs.remove(&sub_id);
+    }
+
+    /// Publish a message to all listening subscriptions.
+    pub fn notify_sync(&self, message_result: T) {
+        self.notify_with_exclude_sync(message_result, &[])
+    }
+
+    /// Publish a message to all listening subscriptions but exclude some subset.
+    /// Sync version.
+    pub fn notify_with_exclude_sync(&self, message_result: T, exclude_list: &[SubscriptionId]) {
+        for entry in self.subs.iter() {
+            let id = entry.key();
+            let sub = entry.value();
+
+            if exclude_list.contains(id) {
+                continue
+            }
+
+            if let Err(e) = sub.try_send(message_result.clone()) {
+                warn!(
+                    target: "system::publisher",
+                    "[system::publisher] Error returned sending message in notify_with_exclude() call! {}", e,
+                );
+            }
+        }
+    }
+}
+