mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 13:51:30 +11:00
Remove T
from EventLoopTargetWindow
(#3081)
Co-authored-by: nerditation <12248559+nerditation@users.noreply.github.com>
This commit is contained in:
parent
d68d9eab38
commit
7a2a2341c2
|
@ -1,4 +1,5 @@
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::mpsc::{self, Receiver, Sender};
|
||||||
|
|
||||||
use crate::error::EventLoopError;
|
use crate::error::EventLoopError;
|
||||||
use crate::event::Event;
|
use crate::event::Event;
|
||||||
|
@ -16,6 +17,8 @@ pub use window_target::EventLoopWindowTarget;
|
||||||
|
|
||||||
pub struct EventLoop<T: 'static> {
|
pub struct EventLoop<T: 'static> {
|
||||||
elw: RootEventLoopWindowTarget<T>,
|
elw: RootEventLoopWindowTarget<T>,
|
||||||
|
user_event_sender: Sender<T>,
|
||||||
|
user_event_receiver: Receiver<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
@ -23,11 +26,14 @@ pub(crate) struct PlatformSpecificEventLoopAttributes {}
|
||||||
|
|
||||||
impl<T> EventLoop<T> {
|
impl<T> EventLoop<T> {
|
||||||
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
|
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
|
||||||
|
let (user_event_sender, user_event_receiver) = mpsc::channel();
|
||||||
Ok(EventLoop {
|
Ok(EventLoop {
|
||||||
elw: RootEventLoopWindowTarget {
|
elw: RootEventLoopWindowTarget {
|
||||||
p: EventLoopWindowTarget::new(),
|
p: EventLoopWindowTarget::new(),
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
},
|
},
|
||||||
|
user_event_sender,
|
||||||
|
user_event_receiver,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,8 +47,18 @@ impl<T> EventLoop<T> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// SAFETY: Don't use `move` to make sure we leak the `event_handler` and `target`.
|
// SAFETY: Don't use `move` to make sure we leak the `event_handler` and `target`.
|
||||||
let handler: Box<dyn FnMut(_, _)> =
|
let handler: Box<dyn FnMut(Event<()>, _)> = Box::new(|event, flow| {
|
||||||
Box::new(|event, flow| event_handler(event, &target, flow));
|
let event = match event.map_nonuser_event() {
|
||||||
|
Ok(event) => event,
|
||||||
|
Err(Event::UserEvent(())) => Event::UserEvent(
|
||||||
|
self.user_event_receiver
|
||||||
|
.try_recv()
|
||||||
|
.expect("handler woken up without user event"),
|
||||||
|
),
|
||||||
|
Err(_) => unreachable!(),
|
||||||
|
};
|
||||||
|
event_handler(event, &target, flow)
|
||||||
|
});
|
||||||
// SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe
|
// SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe
|
||||||
// because this function will never return and all resources not cleaned up by the point we
|
// because this function will never return and all resources not cleaned up by the point we
|
||||||
// `throw` will leak, making this actually `'static`.
|
// `throw` will leak, making this actually `'static`.
|
||||||
|
@ -68,13 +84,24 @@ impl<T> EventLoop<T> {
|
||||||
};
|
};
|
||||||
|
|
||||||
self.elw.p.run(
|
self.elw.p.run(
|
||||||
Box::new(move |event, flow| event_handler(event, &target, flow)),
|
Box::new(move |event, flow| {
|
||||||
|
let event = match event.map_nonuser_event() {
|
||||||
|
Ok(event) => event,
|
||||||
|
Err(Event::UserEvent(())) => Event::UserEvent(
|
||||||
|
self.user_event_receiver
|
||||||
|
.try_recv()
|
||||||
|
.expect("handler woken up without user event"),
|
||||||
|
),
|
||||||
|
Err(_) => unreachable!(),
|
||||||
|
};
|
||||||
|
event_handler(event, &target, flow)
|
||||||
|
}),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||||
self.elw.p.proxy()
|
EventLoopProxy::new(self.elw.p.runner.clone(), self.user_event_sender.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
|
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
|
||||||
|
|
|
@ -1,24 +1,30 @@
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
use super::runner;
|
use super::runner;
|
||||||
use crate::event::Event;
|
use crate::event::Event;
|
||||||
use crate::event_loop::EventLoopClosed;
|
use crate::event_loop::EventLoopClosed;
|
||||||
use crate::platform_impl::platform::r#async::Channel;
|
use crate::platform_impl::platform::r#async::Channel;
|
||||||
|
|
||||||
pub struct EventLoopProxy<T: 'static> {
|
pub struct EventLoopProxy<T: 'static> {
|
||||||
runner: Channel<runner::Shared<T>, T>,
|
// used to wake the event loop handler, not to actually pass data
|
||||||
|
runner: Channel<runner::Shared, ()>,
|
||||||
|
sender: Sender<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> EventLoopProxy<T> {
|
impl<T: 'static> EventLoopProxy<T> {
|
||||||
pub fn new(runner: runner::Shared<T>) -> Self {
|
pub fn new(runner: runner::Shared, sender: Sender<T>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
runner: Channel::new(runner, |runner, event| {
|
runner: Channel::new(runner, |runner, event| {
|
||||||
runner.send_event(Event::UserEvent(event))
|
runner.send_event(Event::UserEvent(event))
|
||||||
})
|
})
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
sender,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||||
self.runner.send(event);
|
self.sender.send(event).unwrap();
|
||||||
|
self.runner.send(());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +33,7 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
runner: self.runner.clone(),
|
runner: self.runner.clone(),
|
||||||
|
sender: self.sender.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,11 @@ use wasm_bindgen::prelude::Closure;
|
||||||
use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent};
|
use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent};
|
||||||
use web_time::{Duration, Instant};
|
use web_time::{Duration, Instant};
|
||||||
|
|
||||||
pub struct Shared<T: 'static>(Rc<Execution<T>>);
|
pub struct Shared(Rc<Execution>);
|
||||||
|
|
||||||
pub(super) type EventHandler<T> = dyn FnMut(Event<T>, &mut ControlFlow);
|
pub(super) type EventHandler = dyn FnMut(Event<()>, &mut ControlFlow);
|
||||||
|
|
||||||
impl<T> Clone for Shared<T> {
|
impl Clone for Shared {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Shared(self.0.clone())
|
Shared(self.0.clone())
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,11 @@ impl<T> Clone for Shared<T> {
|
||||||
|
|
||||||
type OnEventHandle<T> = RefCell<Option<EventListenerHandle<dyn FnMut(T)>>>;
|
type OnEventHandle<T> = RefCell<Option<EventListenerHandle<dyn FnMut(T)>>>;
|
||||||
|
|
||||||
pub struct Execution<T: 'static> {
|
pub struct Execution {
|
||||||
runner: RefCell<RunnerEnum<T>>,
|
runner: RefCell<RunnerEnum>,
|
||||||
suspended: Cell<bool>,
|
suspended: Cell<bool>,
|
||||||
event_loop_recreation: Cell<bool>,
|
event_loop_recreation: Cell<bool>,
|
||||||
events: RefCell<VecDeque<EventWrapper<T>>>,
|
events: RefCell<VecDeque<EventWrapper>>,
|
||||||
id: RefCell<u32>,
|
id: RefCell<u32>,
|
||||||
window: web_sys::Window,
|
window: web_sys::Window,
|
||||||
document: Document,
|
document: Document,
|
||||||
|
@ -57,19 +57,19 @@ pub struct Execution<T: 'static> {
|
||||||
on_touch_end: OnEventHandle<web_sys::Event>,
|
on_touch_end: OnEventHandle<web_sys::Event>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RunnerEnum<T: 'static> {
|
enum RunnerEnum {
|
||||||
/// The `EventLoop` is created but not being run.
|
/// The `EventLoop` is created but not being run.
|
||||||
Pending,
|
Pending,
|
||||||
/// The `EventLoop` is being run.
|
/// The `EventLoop` is being run.
|
||||||
Running(Runner<T>),
|
Running(Runner),
|
||||||
/// The `EventLoop` is exited after being started with `EventLoop::run`. Since
|
/// The `EventLoop` is exited after being started with `EventLoop::run`. Since
|
||||||
/// `EventLoop::run` takes ownership of the `EventLoop`, we can be certain
|
/// `EventLoop::run` takes ownership of the `EventLoop`, we can be certain
|
||||||
/// that this event loop will never be run again.
|
/// that this event loop will never be run again.
|
||||||
Destroyed,
|
Destroyed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> RunnerEnum<T> {
|
impl RunnerEnum {
|
||||||
fn maybe_runner(&self) -> Option<&Runner<T>> {
|
fn maybe_runner(&self) -> Option<&Runner> {
|
||||||
match self {
|
match self {
|
||||||
RunnerEnum::Running(runner) => Some(runner),
|
RunnerEnum::Running(runner) => Some(runner),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
@ -77,13 +77,13 @@ impl<T: 'static> RunnerEnum<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Runner<T: 'static> {
|
struct Runner {
|
||||||
state: State,
|
state: State,
|
||||||
event_handler: Box<EventHandler<T>>,
|
event_handler: Box<EventHandler>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> Runner<T> {
|
impl Runner {
|
||||||
pub fn new(event_handler: Box<EventHandler<T>>) -> Self {
|
pub fn new(event_handler: Box<EventHandler>) -> Self {
|
||||||
Runner {
|
Runner {
|
||||||
state: State::Init,
|
state: State::Init,
|
||||||
event_handler,
|
event_handler,
|
||||||
|
@ -110,8 +110,8 @@ impl<T: 'static> Runner<T> {
|
||||||
|
|
||||||
fn handle_single_event(
|
fn handle_single_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
runner: &Shared<T>,
|
runner: &Shared,
|
||||||
event: impl Into<EventWrapper<T>>,
|
event: impl Into<EventWrapper>,
|
||||||
control: &mut ControlFlow,
|
control: &mut ControlFlow,
|
||||||
) {
|
) {
|
||||||
let is_closed = matches!(*control, ControlFlow::ExitWithCode(_));
|
let is_closed = matches!(*control, ControlFlow::ExitWithCode(_));
|
||||||
|
@ -141,7 +141,7 @@ impl<T: 'static> Runner<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> Shared<T> {
|
impl Shared {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
#[allow(clippy::disallowed_methods)]
|
#[allow(clippy::disallowed_methods)]
|
||||||
let window = web_sys::window().expect("only callable from inside the `Window`");
|
let window = web_sys::window().expect("only callable from inside the `Window`");
|
||||||
|
@ -194,7 +194,7 @@ impl<T: 'static> Shared<T> {
|
||||||
// Set the event callback to use for the event loop runner
|
// Set the event callback to use for the event loop runner
|
||||||
// This the event callback is a fairly thin layer over the user-provided callback that closes
|
// This the event callback is a fairly thin layer over the user-provided callback that closes
|
||||||
// over a RootEventLoopWindowTarget reference
|
// over a RootEventLoopWindowTarget reference
|
||||||
pub fn set_listener(&self, event_handler: Box<EventHandler<T>>) {
|
pub fn set_listener(&self, event_handler: Box<EventHandler>) {
|
||||||
{
|
{
|
||||||
let mut runner = self.0.runner.borrow_mut();
|
let mut runner = self.0.runner.borrow_mut();
|
||||||
assert!(matches!(*runner, RunnerEnum::Pending));
|
assert!(matches!(*runner, RunnerEnum::Pending));
|
||||||
|
@ -457,7 +457,7 @@ impl<T: 'static> Shared<T> {
|
||||||
|
|
||||||
pub fn request_redraw(&self, id: WindowId) {
|
pub fn request_redraw(&self, id: WindowId) {
|
||||||
self.0.redraw_pending.borrow_mut().insert(id);
|
self.0.redraw_pending.borrow_mut().insert(id);
|
||||||
self.send_events::<EventWrapper<T>>(iter::empty());
|
self.send_events::<EventWrapper>(iter::empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&self) {
|
pub fn init(&self) {
|
||||||
|
@ -485,17 +485,14 @@ impl<T: 'static> Shared<T> {
|
||||||
// Add an event to the event loop runner, from the user or an event handler
|
// Add an event to the event loop runner, from the user or an event handler
|
||||||
//
|
//
|
||||||
// It will determine if the event should be immediately sent to the user or buffered for later
|
// It will determine if the event should be immediately sent to the user or buffered for later
|
||||||
pub(crate) fn send_event<E: Into<EventWrapper<T>>>(&self, event: E) {
|
pub(crate) fn send_event<E: Into<EventWrapper>>(&self, event: E) {
|
||||||
self.send_events(iter::once(event));
|
self.send_events(iter::once(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a series of events to the event loop runner
|
// Add a series of events to the event loop runner
|
||||||
//
|
//
|
||||||
// It will determine if the event should be immediately sent to the user or buffered for later
|
// It will determine if the event should be immediately sent to the user or buffered for later
|
||||||
pub(crate) fn send_events<E: Into<EventWrapper<T>>>(
|
pub(crate) fn send_events<E: Into<EventWrapper>>(&self, events: impl IntoIterator<Item = E>) {
|
||||||
&self,
|
|
||||||
events: impl IntoIterator<Item = E>,
|
|
||||||
) {
|
|
||||||
// If the event loop is closed, it should discard any new events
|
// If the event loop is closed, it should discard any new events
|
||||||
if self.is_closed() {
|
if self.is_closed() {
|
||||||
return;
|
return;
|
||||||
|
@ -573,7 +570,7 @@ impl<T: 'static> Shared<T> {
|
||||||
// cleared
|
// cleared
|
||||||
//
|
//
|
||||||
// This will also process any events that have been queued or that are queued during processing
|
// This will also process any events that have been queued or that are queued during processing
|
||||||
fn run_until_cleared<E: Into<EventWrapper<T>>>(&self, events: impl Iterator<Item = E>) {
|
fn run_until_cleared<E: Into<EventWrapper>>(&self, events: impl Iterator<Item = E>) {
|
||||||
let mut control = self.current_control_flow();
|
let mut control = self.current_control_flow();
|
||||||
for event in events {
|
for event in events {
|
||||||
self.handle_event(event.into(), &mut control);
|
self.handle_event(event.into(), &mut control);
|
||||||
|
@ -613,7 +610,7 @@ impl<T: 'static> Shared<T> {
|
||||||
// handle_event takes in events and either queues them or applies a callback
|
// handle_event takes in events and either queues them or applies a callback
|
||||||
//
|
//
|
||||||
// It should only ever be called from `run_until_cleared`.
|
// It should only ever be called from `run_until_cleared`.
|
||||||
fn handle_event(&self, event: impl Into<EventWrapper<T>>, control: &mut ControlFlow) {
|
fn handle_event(&self, event: impl Into<EventWrapper>, control: &mut ControlFlow) {
|
||||||
if self.is_closed() {
|
if self.is_closed() {
|
||||||
*control = ControlFlow::Exit;
|
*control = ControlFlow::Exit;
|
||||||
}
|
}
|
||||||
|
@ -721,7 +718,7 @@ impl<T: 'static> Shared<T> {
|
||||||
// * The `register_redraw_request` closure.
|
// * The `register_redraw_request` closure.
|
||||||
// * The `destroy_fn` closure.
|
// * The `destroy_fn` closure.
|
||||||
if self.0.event_loop_recreation.get() {
|
if self.0.event_loop_recreation.get() {
|
||||||
crate::event_loop::EventLoopBuilder::<T>::allow_event_loop_recreation();
|
crate::event_loop::EventLoopBuilder::<()>::allow_event_loop_recreation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -779,8 +776,8 @@ impl<T: 'static> Shared<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum EventWrapper<T: 'static> {
|
pub(crate) enum EventWrapper {
|
||||||
Event(Event<T>),
|
Event(Event<()>),
|
||||||
ScaleChange {
|
ScaleChange {
|
||||||
canvas: Weak<RefCell<backend::Canvas>>,
|
canvas: Weak<RefCell<backend::Canvas>>,
|
||||||
size: PhysicalSize<u32>,
|
size: PhysicalSize<u32>,
|
||||||
|
@ -788,8 +785,8 @@ pub(crate) enum EventWrapper<T: 'static> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<Event<T>> for EventWrapper<T> {
|
impl From<Event<()>> for EventWrapper {
|
||||||
fn from(value: Event<T>) -> Self {
|
fn from(value: Event<()>) -> Self {
|
||||||
Self::Event(value)
|
Self::Event(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::cell::{Cell, RefCell};
|
||||||
use std::clone::Clone;
|
use std::clone::Clone;
|
||||||
use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque};
|
use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
use std::marker::PhantomData;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
@ -12,7 +13,6 @@ use super::{
|
||||||
super::{monitor::MonitorHandle, KeyEventExtra},
|
super::{monitor::MonitorHandle, KeyEventExtra},
|
||||||
backend,
|
backend,
|
||||||
device::DeviceId,
|
device::DeviceId,
|
||||||
proxy::EventLoopProxy,
|
|
||||||
runner,
|
runner,
|
||||||
window::WindowId,
|
window::WindowId,
|
||||||
};
|
};
|
||||||
|
@ -43,8 +43,9 @@ impl Clone for ModifiersShared {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventLoopWindowTarget<T: 'static> {
|
pub struct EventLoopWindowTarget<T: 'static> {
|
||||||
pub(crate) runner: runner::Shared<T>,
|
pub(crate) runner: runner::Shared,
|
||||||
modifiers: ModifiersShared,
|
modifiers: ModifiersShared,
|
||||||
|
_marker: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Clone for EventLoopWindowTarget<T> {
|
impl<T> Clone for EventLoopWindowTarget<T> {
|
||||||
|
@ -52,6 +53,7 @@ impl<T> Clone for EventLoopWindowTarget<T> {
|
||||||
Self {
|
Self {
|
||||||
runner: self.runner.clone(),
|
runner: self.runner.clone(),
|
||||||
modifiers: self.modifiers.clone(),
|
modifiers: self.modifiers.clone(),
|
||||||
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,14 +63,11 @@ impl<T> EventLoopWindowTarget<T> {
|
||||||
Self {
|
Self {
|
||||||
runner: runner::Shared::new(),
|
runner: runner::Shared::new(),
|
||||||
modifiers: ModifiersShared::default(),
|
modifiers: ModifiersShared::default(),
|
||||||
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn proxy(&self) -> EventLoopProxy<T> {
|
pub fn run(&self, event_handler: Box<runner::EventHandler>, event_loop_recreation: bool) {
|
||||||
EventLoopProxy::new(self.runner.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&self, event_handler: Box<runner::EventHandler<T>>, event_loop_recreation: bool) {
|
|
||||||
self.runner.event_loop_recreation(event_loop_recreation);
|
self.runner.event_loop_recreation(event_loop_recreation);
|
||||||
self.runner.set_listener(event_handler);
|
self.runner.set_listener(event_handler);
|
||||||
}
|
}
|
||||||
|
|
|
@ -463,10 +463,10 @@ impl Canvas {
|
||||||
self.animation_frame_handler.request();
|
self.animation_frame_handler.request();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_scale_change<T: 'static>(
|
pub(crate) fn handle_scale_change(
|
||||||
&self,
|
&self,
|
||||||
runner: &super::super::event_loop::runner::Shared<T>,
|
runner: &super::super::event_loop::runner::Shared,
|
||||||
event_handler: impl FnOnce(crate::event::Event<T>),
|
event_handler: impl FnOnce(crate::event::Event<()>),
|
||||||
current_size: PhysicalSize<u32>,
|
current_size: PhysicalSize<u32>,
|
||||||
scale: f64,
|
scale: f64,
|
||||||
) {
|
) {
|
||||||
|
|
Loading…
Reference in a new issue