Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add get_sprite_count, remove_sprite, get_userdata and set_userdata #60
Add get_sprite_count, remove_sprite, get_userdata and set_userdata #60
Changes from 3 commits
d86e344
859c727
297ed96
519cb64
File filter
Filter by extension
Conversations
Jump to
There are no files selected for viewing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you can do this safely, since the type is not
Rc<Box<dyn Userdata>>
, it'sRc<Box<T>>
.The right way to solve the problem with the
T
generic is moving it into a parameter ofSpriteInner
:But this may not be easy to consolidate with the
Sprite
type that owns it.(As a bonus, making the
SpriteInner
hold anOption<Rc<Box<T>>
would eliminate the need to add this extra drop logic.)I don't really know of any other way to do this. Type erasure can't work because the
get/set
API takesRc
. It would have to take full ownership (not just shared ownership) of the type to safely erase the type.But maybe that is good enough? The caller can always get a userdata pointer after moving it to the sprite. It just depends on if this is acceptable for your use case, e.g. you don't require the
Rc
to exist before the userdata is set on the sprite. An example where this ownership model breaks down is that sprites will only be able to share userdata with double-indirection.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I wasn't 100% certain about this one, because I figured it would be aligned to the box size, not specifically T. If I'm wrong about that, if I'm understanding correctly, I think it could become a problem if replacing the userdata with something else that has the trait but is not the same T. On that note though, I'm realizing that I should probably check the userdata ptr in set_userdata and drop the reference before setting it again if it's not null (same cleanup logic as in the drop).
Yeah this is a bit of a weird one because for my use case, the userdata exists as a component in an ECS model, the sprite just needs to reference it in the draw call. I could avoid using userdata completely by querying my world in the draw call based on the sprite's tag, but this saves a bit of work there since I can set the userdata when setting up the sprite itself.
Similarly in most cases, I'd expect that most people would find safer ways of handling something that would otherwise be userdata through the Game state object itself, bypassing Playdate's C API entirely.
I also considered
SpriteInner<T>
, but I figured it might be a pain to have to specify it where the majority of cases might call for no userdata at all. Sprite would have to beSprite<T>
, and you'd need to callSpriteManager::new_sprite::<T>()
(Possibly just using()
for when it's not needed, but still). With adding theOption<Rc<Box<T>>
like you said you could just bypass the C API too and just return the owned data. Maybe that could be more universally useful to others? It'd be a breaking change for sure thoughThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a thing called object safety which requires that constructing a trait object must always be possible for every
T: Trait
. And because that isn't the case here (there is no trait bound onT
), I think this implementation violates at least object safety.The bigger problem, though, is that nothing implements the
UserData
trait. So I don't see how the compiler can correctly drop the type behind theRc
.I actually do like the idea of type erasing the userdata safely. I think it can be done with
core::any::Any
to downcast out ofget_userdata
. But if it's behind a smart pointer andAny
, I don't really know what benefit you are getting out of the FFI calls, sending a raw pointer to the SDK. You can just do the userdata in pure safe Rust at this point. And you don't need the type parameter onSprite
for it, either.Yep! Basically this! 😂 I'm using an ECS, too. I would expect the
Sprite
to be a component as well. The problem might be that it's!Send
and!Sync
. Which ... basically prevents adding it to any ECS that exists, at least as a component. What I ended up doing, even though I am not usingSprite
, is putting my Playdate resources into ashipyard::Unique
, which can be!Send
and!Sync
. And they can just be put into aBTreeMap
with a&'static str
,String
, oru32
key... Some type that can trivially be put into a component.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wound up forking edict which is supposed to be no_std, but modifying it a lot 😂. I've basically just been stripping out any Send + Sync trait bounds where I can.
I'll close this because I'm sold on the possibility of doing this safely. I'm going to look into a safe implementation that uses
Any
, because like you said, we could avoid the type parameter which prevents any breaking changes, and I think it's just as good to sort of mimic the functionality of the C API without having to directly make the ffi call