Skip to content

Commit

Permalink
native-activity: track saved state as a Vec<u8>
Browse files Browse the repository at this point in the history
This simplifies the tracking of saved state by simply using a Vec and
only copying into a `malloc()` allocation when passing the saved state
to `ANativeActivity`.

This also ensures saved state persists (in Rust) between a
`MainEvent::SaveState` event and a `Resume` event - otherwise the saved
state would only be restored in the situation where `onCreate` is called
again for a new process.
  • Loading branch information
rib committed Oct 11, 2022
1 parent 2e25e2e commit ca78397
Showing 1 changed file with 36 additions and 88 deletions.
124 changes: 36 additions & 88 deletions android-activity/src/native_activity/glue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,14 @@ pub struct NativeActivityState {
pub msg_read: libc::c_int,
pub msg_write: libc::c_int,
pub config: super::ConfigurationRef,
pub saved_state: *mut libc::c_void,
pub saved_state_size: libc::size_t,
pub saved_state: Vec<u8>,
pub input_queue: *mut ndk_sys::AInputQueue,
pub window: Option<NativeWindow>,
pub content_rect: ndk_sys::ARect,
pub activity_state: State,
pub destroy_requested: bool,
pub running: bool,
pub state_saved: bool,
pub app_has_saved_state: bool,
pub destroyed: bool,
pub redraw_needed: bool,
pub pending_input_queue: *mut ndk_sys::AInputQueue,
Expand All @@ -222,10 +221,6 @@ impl NativeActivityState {
1 => {
let cmd = AppCmd::try_from(cmd_i);
return match cmd {
Ok(AppCmd::SaveState) => {
self.free_saved_state();
Some(AppCmd::SaveState)
}
Ok(cmd) => Some(cmd),
Err(_) => {
log::error!("Spurious, unknown NativeActivityGlue cmd: {}", cmd_i);
Expand Down Expand Up @@ -272,14 +267,6 @@ impl NativeActivityState {
}
}

fn free_saved_state(&mut self) {
if self.saved_state != ptr::null_mut() {
unsafe { libc::free(self.saved_state) };
self.saved_state = ptr::null_mut();
self.saved_state_size = 0;
}
}

pub unsafe fn attach_input_queue_to_looper(
&mut self,
looper: *mut ndk_sys::ALooper,
Expand Down Expand Up @@ -310,7 +297,6 @@ impl Drop for WaitableNativeActivityState {
log::debug!("WaitableNativeActivityState::drop!");
unsafe {
let mut guard = self.mutex.lock().unwrap();
guard.free_saved_state();
guard.detach_input_queue_from_looper();
guard.destroyed = true;
self.cond.notify_one();
Expand Down Expand Up @@ -338,21 +324,7 @@ impl WaitableNativeActivityState {
}
}

// NB: The implementation for `ANativeActivity` explicitly documents that save_state must
// be tracked via `malloc()` and `free()`, since `ANativeActivity` may need to free state
// that it is given via its `onSaveInstanceState` callback.
let mut saved_state = ptr::null_mut();
unsafe {
if saved_state_in != ptr::null() && saved_state_size > 0 {
saved_state = libc::malloc(saved_state_size);
assert!(
saved_state != ptr::null_mut(),
"Failed to allocate {} bytes for restoring saved application state",
saved_state_size
);
libc::memcpy(saved_state, saved_state_in, saved_state_size);
}
}
let saved_state = unsafe { std::slice::from_raw_parts(saved_state_in as *const u8, saved_state_size as _) };

let config = unsafe {
let config = ndk_sys::AConfiguration_new();
Expand All @@ -371,15 +343,14 @@ impl WaitableNativeActivityState {
msg_read: msgpipe[0],
msg_write: msgpipe[1],
config,
saved_state,
saved_state_size,
saved_state: saved_state.into(),
input_queue: ptr::null_mut(),
window: None,
content_rect: Rect::empty().into(),
activity_state: State::Init,
destroy_requested: false,
running: false,
state_saved: false,
app_has_saved_state: false,
destroyed: false,
redraw_needed: false,
pending_input_queue: ptr::null_mut(),
Expand Down Expand Up @@ -482,73 +453,54 @@ impl WaitableNativeActivityState {
}
}

unsafe fn request_save_state(&self) -> (*mut libc::c_void, libc::size_t) {
fn request_save_state(&self) -> (*mut libc::c_void, libc::size_t) {
let mut guard = self.mutex.lock().unwrap();

guard.state_saved = false;
// The state_saved flag should only be set while in this method, and since
// it doesn't allow re-entrance and is cleared before returning then we expect
// this to be None
debug_assert!(guard.app_has_saved_state == false, "SaveState request clash");
guard.write_cmd(AppCmd::SaveState);
while guard.state_saved == false {
while guard.app_has_saved_state == false {
guard = self.cond.wait(guard).unwrap();
}

let saved_state = std::mem::replace(&mut guard.saved_state, ptr::null_mut());
let saved_state_size = std::mem::take(&mut guard.saved_state_size);
if saved_state != ptr::null_mut() && saved_state_size > 0 {
(saved_state, saved_state_size)
guard.app_has_saved_state = false;

// `ANativeActivity` explicitly documents that it expects save state to be
// given via a `malloc()` allocated pointer since it will automatically
// `free()` the state after it has been converted to a buffer for the JVM.
if guard.saved_state.len() > 0 {
let saved_state_size = guard.saved_state.len() as _;
let saved_state_src_ptr = guard.saved_state.as_ptr();
unsafe {
let saved_state = libc::malloc(saved_state_size);
assert!(
saved_state != ptr::null_mut(),
"Failed to allocate {} bytes for restoring saved application state",
saved_state_size
);
libc::memcpy(saved_state, saved_state_src_ptr as _, saved_state_size);
(saved_state, saved_state_size)
}
} else {
(ptr::null_mut(), 0)
}
}

pub fn saved_state(&self) -> Option<Vec<u8>> {
let guard = self.mutex.lock().unwrap();

unsafe {
if guard.saved_state != ptr::null_mut() && guard.saved_state_size > 0 {
let buf: &mut [u8] = std::slice::from_raw_parts_mut(
guard.saved_state.cast(),
guard.saved_state_size as usize,
);
let state = buf.to_vec();
Some(state)
} else {
None
}
if guard.saved_state.len() > 0 {
Some(guard.saved_state.clone())
} else {
None
}
}

pub fn set_saved_state(&self, state: &[u8]) {
let mut guard = self.mutex.lock().unwrap();

// ANativeActivity specifically expects the state to have been allocated
// via libc::malloc since it will automatically handle freeing the data.

unsafe {
// In case the application calls store() multiple times for some reason we
// make sure to free any pre-existing state...
if guard.saved_state != ptr::null_mut() {
libc::free(guard.saved_state);
guard.saved_state = ptr::null_mut();
guard.saved_state_size = 0;
}

let buf = libc::malloc(state.len());
if buf == ptr::null_mut() {
panic!("Failed to allocate save_state buffer");
}

// Since it's a byte array there's no special alignment requirement here.
//
// Since we re-define `buf` we ensure it's not possible to access the buffer
// via its original pointer for the lifetime of the slice.
{
let buf: &mut [u8] = std::slice::from_raw_parts_mut(buf.cast(), state.len());
buf.copy_from_slice(state);
}

guard.saved_state = buf;
guard.saved_state_size = state.len() as _;
}
guard.saved_state.clear();
guard.saved_state.extend_from_slice(state);
}

////////////////////////////
Expand Down Expand Up @@ -620,13 +572,9 @@ impl WaitableNativeActivityState {
}
AppCmd::SaveState => {
let mut guard = self.mutex.lock().unwrap();
guard.state_saved = true;
guard.app_has_saved_state = true;
self.cond.notify_one();
}
AppCmd::Resume => {
let mut guard = self.mutex.lock().unwrap();
guard.free_saved_state();
}
_ => {}
}
}
Expand Down

0 comments on commit ca78397

Please sign in to comment.