-
Notifications
You must be signed in to change notification settings - Fork 20
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
Added mutability checks for Gfx #22
Conversation
I'm not sure I follow this. I think you're pointing out that the There also seem to be some functions that can run while the console is around, and they work kind of as expected (ex. wide mode toggle, though the width doesn't get updated in console so it wraps at the old width). Is this something we need to support, or is it not how the libctru API is expected to be used? I guess those might be the methods we add under Though also we should make sure we're still supporting non-Screen based graphics work. For example set_framebuffer_format is probably not just for Screens, so we shouldn't hide it behind owning a Screen object. Edit:
Maybe there's a missing "no" before "changes", and that was tripping me up? |
Ok, I wasn't clear. By my "assumes changes", I mean a Console makes some changes to the This is mainly about: the screen's framebuffer format (the framebuffers are screen specific, so putting those methods under them isn't an issue), the screen's double buffering mode (it must be off for a Console to work properly), and the wide mode check (as you said, it doesn't work properly because the Console cannot detect a change after init). These are all screen specific features, that must not change in state while the Console exists. This is why I want to use the mutability checks in the first place. Though, as I stated, right now they are effected globally by a single Gfx instance, and that's why I believe we should separate them. |
I was typing up something here to argue that these are not Maybe we could do something like this: Add |
That was what I thought of too. Giving methods to the Screen enum will be enough I think. |
Never mind. If we only expose it via methods on |
Why do that, can't we just make 2 screen instances as private members of the |
With that approach, how would a user set a graphics option on a screen, like double buffering? |
One idea: pub struct Gfx {
pub top_screen: Screen,
pub bottom_screen: Screen,
}
// Make it impossible for users to create a screen of their own
pub struct Screen(ScreenKind);
pub enum ScreenKind { Top, Bottom }
let mut gfx: Gfx = Gfx::init().unwrap();
// Gets a &mut Screen and calls a method on it.
gfx.top_screen.set_wide_mode(true);
// Initializes consoles using both screens.
let top_console = Console::init(&mut gfx.top_screen);
let bottom_console = Console::init(&mut gfx.bottom_screen); It might be nicer to use public fields on Edit: I changed |
I don't even know if you can have two consoles for one screen to be honest. At this point I just wish their API didn't fall apart for a slightly different order of operations. But ranting is useless, so off we go. |
@AzureMarker I've made these changes. Sadly the borrow splitting idea won't work because taking a mutable reference to a member of a struct is still like taking a mutable reference to the whole struct, and thus calls to This changes aren't clean, nor pretty to be honest. Tell me what you think. We'll still have some stuff to do before pushing. |
Here's a few changes to simplify things and fix the wide_screen example (The other examples also need fixing, but they're straightforward): diff --git a/ctru-rs/examples/gfx_wide_mode.rs b/ctru-rs/examples/gfx_wide_mode.rs
index f4ba365..8510dc5 100644
--- a/ctru-rs/examples/gfx_wide_mode.rs
+++ b/ctru-rs/examples/gfx_wide_mode.rs
@@ -1,7 +1,6 @@
extern crate ctru;
use ctru::console::Console;
-use ctru::gfx::Screen;
use ctru::services::hid::KeyPad;
use ctru::services::{Apt, Hid};
use ctru::Gfx;
@@ -11,9 +10,9 @@ fn main() {
let apt = Apt::init().unwrap();
let hid = Hid::init().unwrap();
let gfx = Gfx::default();
- let _console = Console::init(&gfx, Screen::Top);
+ let mut console = Console::init(gfx.top_screen.borrow());
- println!("Press A to enable/disable wide screen mode.");
+ println!("Press A to enable/disable wide screen mode. Extra words here so the text wraps.");
while apt.main_loop() {
hid.scan_input();
@@ -23,7 +22,17 @@ fn main() {
}
if hid.keys_down().contains(KeyPad::KEY_A) {
- gfx.set_wide_mode(!gfx.get_wide_mode());
+ drop(console);
+
+ let mut top_screen = gfx.top_screen.borrow_mut();
+ let wide_mode = top_screen.get_wide_mode().unwrap();
+ top_screen.set_wide_mode(!wide_mode).unwrap();
+ drop(top_screen);
+
+ console = Console::init(gfx.top_screen.borrow());
+ println!(
+ "Press A to enable/disable wide screen mode. Extra words here so the text wraps."
+ );
}
gfx.flush_buffers();
diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs
index 13be457..a18a9ac 100644
--- a/ctru-rs/src/gfx.rs
+++ b/ctru-rs/src/gfx.rs
@@ -1,6 +1,6 @@
//! LCD screens manipulation helper
-use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut};
+use std::cell::RefCell;
use std::default::Default;
use std::ops::Drop;
@@ -11,8 +11,10 @@ use crate::services::gspgpu::{self, FramebufferFormat};
///
/// The service exits when this struct is dropped.
pub struct Gfx {
- top_screen: RefCell<Screen>,
- bottom_screen: RefCell<Screen>,
+ pub top_screen: RefCell<Screen>,
+ pub bottom_screen: RefCell<Screen>,
+ // We don't want to allow users to create this struct manually
+ _private: ()
}
/// Available screens on the 3DS
@@ -51,29 +53,10 @@ impl Gfx {
Gfx {
top_screen: RefCell::new(Screen::Top),
bottom_screen: RefCell::new(Screen::Bottom),
+ _private: ()
}
}
- /// Try to get an immutable reference to the Top screen
- pub fn get_top_screen(&self) -> Result<Ref<'_, Screen>, BorrowError> {
- self.top_screen.try_borrow()
- }
-
- /// Try to get a mutable reference to the Top screen
- pub fn get_top_screen_mut(&self) -> Result<RefMut<'_, Screen>, BorrowMutError> {
- self.top_screen.try_borrow_mut()
- }
-
- /// Try to get an immutable reference to the Bottom screen
- pub fn get_bottom_screen(&self) -> Result<Ref<'_, Screen>, BorrowError> {
- self.bottom_screen.try_borrow()
- }
-
- /// Try to get a mutable reference to the Bottom screen
- pub fn get_bottom_screen_mut(&self) -> Result<RefMut<'_, Screen>, BorrowMutError> {
- self.bottom_screen.try_borrow_mut()
- }
-
/// Flushes the current framebuffers
pub fn flush_buffers(&self) {
unsafe { ctru_sys::gfxFlushBuffers() };
@@ -203,6 +186,7 @@ impl Default for Gfx {
Gfx {
top_screen: RefCell::new(Screen::Top),
bottom_screen: RefCell::new(Screen::Bottom),
+ _private: ()
}
}
} |
Another idea I had is making separate types for the top and bottom screens. Both could implement one trait for common behavior, but the top screen would have extra methods like |
Made the split. It works nicely now, and the structs that want a generic reference to a Screen can use |
Fixed the problems with the latest commit, including struct access. I had thought |
ctru-rs/src/console.rs
Outdated
/// Initialize a console on the chosen screen, overwriting whatever was on the screen | ||
/// previously (including other consoles). The new console is automatically selected for | ||
/// printing. | ||
pub fn init(_gfx: &'gfx Gfx, screen: Screen) -> Self { | ||
pub fn init(screen: Ref<'screen, impl Screen>) -> Self { |
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.
This should be just be a reference since it doesn't need to be a reference-counted handle:
pub fn init(screen: Ref<'screen, impl Screen>) -> Self { | |
pub fn init(screen: &'screen impl Screen) -> Self { |
Also could use dyn
here instead of impl
, but it doesn't really matter.
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 believe you haven't understood my code, since it's the second time you talked about an Rc
implementation. This isn't reference counting, this is internal mutability. Reference counting would bring us to have multiple owners for the Screen
, which isn't what we want. This is a RefCell
, which is a smart pointer for a mutating object. It's used only for internal mutability inside Gfx
, so that we may use the screens/gfx separately.
Also, without using a Ref
object, the rules for RefCell
's borrowing aren't correctly applied. We may even have to store the reference, now that I think about it.
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.
Sorry, the types are named pretty similar and sometimes I mix them up, especially since Rc<RefCell<_>>
is pretty common. Thanks for pointing that out though, I just unconsciously swapped around RefCell
and Rc
. Also, technically RefCell
does actually count references via handles 🙃, just not in the way "reference-counted" implies:
https://doc.rust-lang.org/stable/src/core/cell.rs.html#687
The code as it was when I reviewed it would have functioned the same if you used Ref
or &
. At that time, the Ref
wasn't used, so the only functional thing happening was associating the 'screen
lifetime with the returned Console<'screen>
instance. Which would work the same with Ref
or &
.
Now the code has been updated to hold the Ref
. But that doesn't really change anything. There's no internal mutability happening here, so removing the RefCell
doesn't break anything - Yes, I tested this locally. We should use RefMut
instead. That would actually utilize the RefCell
by letting you take mutable references to the fields of Gfx
(preventing future updates) but still allow &self
calls on Gfx
.
Co-authored-by: Mark Drobnak <[email protected]>
Yes, using the lifetime alone led to being able to mutably borrow the @AzureMarker Since the Console holds the Screen anyways, you could also add the |
The point of passing the screen to the Console via a I did some experiments for if we did decide to do this: If we did do this, it should be by exposing a method on pub fn screen(&mut self) -> &mut dyn Screen {
self.screen.deref_mut()
} Alternatively, we could include the screen type as a generic on pub struct Console<'screen, S: Screen> {
context: Box<PrintConsole>,
screen: RefMut<'screen, S>,
}
impl<'screen, S: Screen> Console<'screen, S> {
// ...
pub fn screen(&mut self) -> &mut S {
self.screen.deref_mut()
}
}
let mut console = Console::init(gfx.top_screen.borrow_mut());
// If we used the bottom screen this wouldn't compile
console.screen().set_wide_mode(true); The downside of these two approaches is we can't limit the functions users can call. So if libctru says calling X gfx/screen function is bad if a console is around, but the other functions aren't so bad, then we have limited options. We could instead return an immutable reference to the |
Sorry :(, but I think it's really close now, just a few more tweaks, plus making the examples compile. |
Co-authored-by: Mark Drobnak <[email protected]>
I was thinking at something like a |
@AzureMarker fixed and added examples. I put |
Yeah, I haven't looked into this to see if it's possible to re-init the console or manually adjust it, but that should work. |
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.
Very close!!! I'm looking forward to this merging. Just a few small nits. The example nits could be done separately like you mentioned. I can open a PR for these to ease the burden on you :).
Solved all the suggestions |
Related to #19. @AzureMarker
The
consoleInit
process actually depends (and assumes) changes in thegfx
environment, mainly related to framebuffers. By using Rust's mutable reference checks, we can prevent the environment from changing while aConsole
object is alive. This change has however a problem, in that (as it is written right now) the 2 screens (which would normally be worked with separately in this contexts) are actually both stuck together, since thectru-rs
Gfx
implementation puts their control under one struct.I believe we should separate the
Screen
s, thus to not block theGfx
mutability as a whole, but just singular screen controllers. Maybe internal mutability and moving some of theGfx
methods to the singular screens (likeset_framebuffer_format
) is the way.