Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

sanbox-irl
Copy link
Member

Hello,

This shouldn't be merged in its current form.

I'm looking for some comments and ideas on how we can make this better.

Right now, the API for drag/drop with nothing in the payload is basically good to go -- however, that's not very useful! So we get into a more complicated situation with handling drag/drop with data in it.

ImGui will copy (with just a simple memcpy) the fat pointer (ptr + size) we provide to it. There are tons of edge cases (I'm not even sure they're "edge", really just unsafety is everywhere), so I've made only the uber unsafe version right now. There are, however, some safe variants we could claw back if we were willing to wrap this API. Specifically, I'm imagining:

enum Payload {
   Number(usize), // god, how many numbers do we allow? not pleasant...
   F32(f32), // i mean, do we allow f64 too...i'd say no, but this is very arbitrary for our users here.
   Unsafe(*const ffi::c_void), // ready to panic!
}

This though, isn't really what we want. Really, we want to say "gimme some Pod". We might be able to use bytemuck's Pod trait here, but that would be another dependency.

The case we really want to be able to handle isn't arbitrary data though, but Strings (since users can encode arbitrary data in strings), and we COULD handle this manually, reducing a string to raw parts and then reconstructing it in accept_payload. However, that's got a serious downside -- that string will never be deallocated by ImGui. This isn't memory unsafety, but it is a huge problem that we can't accept.

Imo, the solution I would gravitate towards is using an AnyMap type struct and then encoding usize's of TypeId and putting that in the payload. Users would just get the typeid out of accept_payload and handle getting the "real" data from there. That should probably be in UserLand code, not in imgui, although I'm not against ImGui handling that either.

Anyway, I'm curious what you think. I haven't done much rust FFI, so I may have made some newbie mistakes. Looking forward to any pointers! (I've left a few @fixme throughout the code for weird issues I ran into)

@sanbox-irl sanbox-irl mentioned this pull request Jan 24, 2021
@thomcc
Copy link
Member

thomcc commented Jan 24, 2021

bytemuck is a cheap enough dep that I don't mind it if it would make it safe. I also am one of the major contributors to bytemuck (although github has seemingly forgotten this because it was under my @mozilla.com email)

@sanbox-irl
Copy link
Member Author

sanbox-irl commented Jan 24, 2021

Okay then, I think there are roughly three ways to use this API that we should support.

  1. DragDrop with no Payload. Since writing this PR, I've realized this is actually almost certainly the best way for users to handle this code in conjunction with OnceCell. It should at the least be documented, though we might even consider directly implementing it (probably not unless it becomes the dominant pattern). Here's how you do it:
fn show_ui(ui: &imgui::Ui<'_>) {
    static NAME_PAYLOAD_HANDLER: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));

    ui.button(im_str!("Drag me!"));

    if imgui::DragDropSource::new(im_str!("Test Drag")).begin(ui).is_some() {
        *NAME_PAYLOAD_HANDLER.lock() = Some("Drag me!".to_string());
    }

    ui.button(im_str!("Target me!"));

    // drag drop TARGET
    if let Some(target) = imgui::DragDropTarget::new(ui) {
        if target
            .accept_drag_drop_payload(im_str!("Test Drag"), imgui::DragDropFlags::empty())
            .is_some()
        {
            if let Some(msg) = NAME_PAYLOAD_HANDLER.lock().take() {
                println!("Message is {}", msg);
            }
        }

        target.pop();
    }
}

Effectively, the payload is just a signal that the Lazy has data in it. The Lazy is not necessary, but convenient.

  1. Writing bytes directly. This can be very useful for passing around AssetIds. For that, I'll add bytemuck as a dep, which will make this a safe operation.

  2. I guess being a crazy person and just yeeting your data into C++ and accepting that it's probably going to leak? I think we should at least let people be wild if they'd like to be wild.

@thomcc
Copy link
Member

thomcc commented Jan 25, 2021

I guess being a crazy person and just yeeting your data into C++ and accepting that it's probably going to leak? I think we should at least let people be wild if they'd like to be wild.

Yeah it's fine if stuff leaks so long as that's properly signposted in the API and it's either safe (or requires the unsafe keyword), but it wouldn't be great if it's accidental leakage.

@sanbox-irl
Copy link
Member Author

@thomcc yup, sounds good. the current plan is that DragDropSourceNoPayload and DragDropAcceptNoPayload will both be safe, returning just an Option with no data in or out. I've implemneted that over lunch but haven't renamed.

Additionally, there will be DragDropSourcePodPayload, which will be generic over bytemuck::Pod and will be safe to call, and DragDropAcceptPodPayload which will be safe to call and return an Option<Result<T: Pod, PodCastError>>. I have also implemented this during my lunch.

Last up is DragDropSourceUnchecked which will be unsafe and take raw pointers, and a corresponding accept which will unsafe as well and basically just hand the user back what imgui gave us. Technically, neither are particularly unsafe for us, since we'll just ferry their probably dangling or to-be-leaked data across the FFI, but I think marking it as unsafe to show people that they don't want to be using it is in order.

A question for bytemuck here though -- should we be re-exporting Pod and PodCastError? Or make users get it themselves?

Copy link
Member

@thomcc thomcc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of comments, since this code is pretty tricky. Broadly summarizing:

  • I think the unsafe code needs to worry more about align_of::
  • PhantomData should probably be over a reference not a value
  • There are some tricks we can do to improve error reporting for debugging
  • I find what's public and non-public confusing/inconsistent in this API.
  • Several less serious nits.

P.S. thanks for the docs, made this much easier.

delivery: unsafe_payload.delivery,
})
} else {
Err(PayloadIsWrongType {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certainly here we could easily know type_name of the expected type. If we store the type_name of the actual type on TypedPayload, the debugging experience might improve as mentioned elsewhere.


/// Ends the current target. Ironically, this doesn't really do anything in ImGui
/// or in imgui-rs, but it might in the future.
pub fn pop(self) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto with end/pop concerns.

pub data: *const ffi::c_void,

/// The size of the data in bytes.
pub size: i32,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this type is not a mapping of a C++ type (hopefully! It's not #[repr(C)]!) we should probably use usize here and just handle the conversion ourselves rather than pushing it to the user.

let should_begin = sys::igBeginDragDropSource(self.flags.bits() as i32);

if should_begin {
sys::igSetDragDropPayload(self.name.as_ptr(), ptr, size, self.cond as i32);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, i think imgui c++ tends to truncate usize to 32 bits (often signed). We should probably have an assert somewhere that the size is within that... (I assume this is one of the places where that happens, since we have an i32 size later on).

... I bet our existing code mishandles this too in lots of places...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left this unimplemented for now, mostly because it's just very late -- will add a note in a second

@sanbox-irl
Copy link
Member Author

PHEW -- okay @thomcc -- addressed all of the above except handling >i32 size packets. i think we'll wnat to look at the rest of the codebase for that / also i'm very tired.

One thing to bring your attention to is like 467 and line 335 where I read off half the struct. Is that sound? I believe it is since both are repr(C), but want to make sure in your opinion if they are.

Also, let me know if this way of doing cfgs is fine. I wasn't trying to get too clever here

Copy link
Member

@thomcc thomcc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a changelog entry and module header comment for the newly-pub mod, but other than that looks great.

}

/// Indicates that an incorrect payload type was received. It is opaque,
/// but you can view questionably useful debug information with Debug formatting.
Copy link
Member

@thomcc thomcc Feb 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

questionably useful

Yeah I mean, having the type name in debug builds would helps a lot. I've had to debug "what the fuck is in this Box<dyn Any>"-issues before and it was... not fun.

That said if you don't think it's useful, feel free to push back on review comments I make in the future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah no sorry, this comment predates adding the typename -- I hadn't thought of that idea at the time. Will remove -- I was previously just dumping out the typeid which is definitely "questionably" useful at best since it's not like a user could tell what that was. Now it's definitely useful

@@ -0,0 +1,534 @@
use std::{any, ffi, marker::PhantomData};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is pub mod now: Can you add a //! that says this module is for the drag and drop stuff, but the most commonly used types are reexported at the crate root.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do along with a changelog

@sanbox-irl
Copy link
Member Author

Okay! will merge in the morning in case there are any last minute edits

@thomcc
Copy link
Member

thomcc commented Feb 4, 2021

In the future can you update branches by rebasing rather than merging (e.g. git rebase master not git merge master)? Keeps history cleaner and would allow me to apply these commits directly onto master and keeps the branch graph clean and easy to follow.

@thomcc thomcc merged commit 8b00663 into imgui-rs:master Feb 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants