app: reduce JNI overhead when adjusting a selection by creating a special setSelection() fn so we dont have to pass the entire state over the bridge.

This commit is contained in:
jkds
2026-01-03 11:22:03 +01:00
parent 7d589fd2ac
commit d131828285
7 changed files with 110 additions and 53 deletions

View File

@@ -45,7 +45,6 @@ import androidx.core.view.WindowInsetsCompat;
import textinput.GameTextInput.Pair;
public class InputConnection extends BaseInputConnection implements View.OnKeyListener {
private static final String TAG = "gti.InputConnection";
private final InputMethodManager imm;
private final View targetView;
private final Settings settings;
@@ -53,6 +52,10 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
private Listener listener;
private boolean mSoftKeyboardActive;
private void log(String text) {
//Log.d("darkfi", text);
}
/*
* This class filters EOL characters from the input. For details of how InputFilter.filter
* function works, refer to its documentation. If the suggested change is accepted without
@@ -100,7 +103,7 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
*/
public InputConnection(Context ctx, View targetView, Settings settings) {
super(targetView, settings.mEditorInfo.inputType != 0);
Log.d(TAG, "InputConnection created");
log("InputConnection created");
this.targetView = targetView;
this.settings = settings;
@@ -143,7 +146,7 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
* https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#showSoftInput(android.view.View,%20int)
*/
public final void setSoftKeyboardActive(boolean active, int flags) {
Log.d(TAG, "setSoftKeyboardActive, active: " + active);
log("setSoftKeyboardActive, active: " + active);
this.mSoftKeyboardActive = active;
if (active) {
@@ -171,7 +174,7 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
* @param editorInfo The EditorInfo to use
*/
public final void setEditorInfo(EditorInfo editorInfo) {
Log.d(TAG, "setEditorInfo");
log("setEditorInfo");
settings.mEditorInfo = editorInfo;
// Depending on the multiline state, we might need a different set of filters.
@@ -195,7 +198,7 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
public final void setState(State state) {
if (state == null)
return;
Log.d(TAG,
log(
"setState: '" + state.text + "', selection=(" + state.selectionStart + ","
+ state.selectionEnd + "), composing region=(" + state.composingRegionStart + ","
+ state.composingRegionEnd + ")");
@@ -232,7 +235,7 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
// From View.OnKeyListener
@Override
public boolean onKey(View view, int i, KeyEvent keyEvent) {
Log.d(TAG, "onKey: " + keyEvent);
log("onKey: " + keyEvent);
if (!getSoftKeyboardActive()) {
return false;
}
@@ -251,22 +254,22 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
// From BaseInputConnection
@Override
public Editable getEditable() {
Log.d(TAG, "getEditable");
log("getEditable");
return mEditable;
}
// From BaseInputConnection
@Override
public boolean setSelection(int start, int end) {
Log.d(TAG, "setSelection: " + start + ":" + end);
log("setSelection: " + start + ":" + end);
return super.setSelection(start, end);
}
// From BaseInputConnection
@Override
public boolean setComposingText(CharSequence text, int newCursorPosition) {
Log.d(
TAG, String.format("setComposingText='%s' newCursorPosition=%d", text, newCursorPosition));
log(
"setComposingText='" + text + "' newCursorPosition=" + newCursorPosition);
if (text == null) {
return false;
}
@@ -275,40 +278,40 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
@Override
public boolean setComposingRegion(int start, int end) {
Log.d(TAG, "setComposingRegion: " + start + ":" + end);
log("setComposingRegion: " + start + ":" + end);
return super.setComposingRegion(start, end);
}
// From BaseInputConnection
@Override
public boolean finishComposingText() {
Log.d(TAG, "finishComposingText");
log("finishComposingText");
return super.finishComposingText();
}
@Override
public boolean endBatchEdit() {
Log.d(TAG, "endBatchEdit");
log("endBatchEdit");
stateUpdated();
return super.endBatchEdit();
}
@Override
public boolean commitCompletion(CompletionInfo text) {
Log.d(TAG, "commitCompletion");
log("commitCompletion");
return super.commitCompletion(text);
}
@Override
public boolean commitCorrection(CorrectionInfo text) {
Log.d(TAG, "commitCompletion");
log("commitCompletion");
return super.commitCorrection(text);
}
// From BaseInputConnection
@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
Log.d(TAG,
log(
(new StringBuilder())
.append("commitText: ")
.append(text)
@@ -321,21 +324,21 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
// From BaseInputConnection
@Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
Log.d(TAG, "deleteSurroundingText: " + beforeLength + ":" + afterLength);
log("deleteSurroundingText: " + beforeLength + ":" + afterLength);
return super.deleteSurroundingText(beforeLength, afterLength);
}
// From BaseInputConnection
@Override
public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
Log.d(TAG, "deleteSurroundingTextInCodePoints: " + beforeLength + ":" + afterLength);
log("deleteSurroundingTextInCodePoints: " + beforeLength + ":" + afterLength);
return super.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
}
// From BaseInputConnection
@Override
public boolean sendKeyEvent(KeyEvent event) {
Log.d(TAG, "sendKeyEvent: " + event);
log("sendKeyEvent: " + event);
return super.sendKeyEvent(event);
}
@@ -346,16 +349,16 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
if (result == null) {
result = "";
}
Log.d(TAG, "getSelectedText: " + flags + ", result: " + result);
log("getSelectedText: " + flags + ", result: " + result);
return result;
}
// From BaseInputConnection
@Override
public CharSequence getTextAfterCursor(int length, int flags) {
Log.d(TAG, "getTextAfterCursor: " + length + ":" + flags);
log("getTextAfterCursor: " + length + ":" + flags);
if (length < 0) {
Log.i(TAG, "getTextAfterCursor: returning null to due to an invalid length=" + length);
Log.i("darkfi", "getTextAfterCursor: returning null to due to an invalid length=" + length);
return null;
}
return super.getTextAfterCursor(length, flags);
@@ -364,9 +367,9 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
// From BaseInputConnection
@Override
public CharSequence getTextBeforeCursor(int length, int flags) {
Log.d(TAG, "getTextBeforeCursor: " + length + ", flags=" + flags);
log("getTextBeforeCursor: " + length + ", flags=" + flags);
if (length < 0) {
Log.i(TAG, "getTextBeforeCursor: returning null to due to an invalid length=" + length);
Log.i("darkfi", "getTextBeforeCursor: returning null to due to an invalid length=" + length);
return null;
}
return super.getTextBeforeCursor(length, flags);
@@ -375,39 +378,39 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
// From BaseInputConnection
@Override
public boolean requestCursorUpdates(int cursorUpdateMode) {
Log.d(TAG, "Request cursor updates: " + cursorUpdateMode);
log("Request cursor updates: " + cursorUpdateMode);
return super.requestCursorUpdates(cursorUpdateMode);
}
// From BaseInputConnection
@Override
public void closeConnection() {
Log.d(TAG, "closeConnection");
log("closeConnection");
super.closeConnection();
}
@Override
public boolean setImeConsumesInput(boolean imeConsumesInput) {
Log.d(TAG, "setImeConsumesInput: " + imeConsumesInput);
log("setImeConsumesInput: " + imeConsumesInput);
return super.setImeConsumesInput(imeConsumesInput);
}
@Override
public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
Log.d(TAG, "getExtractedText");
log("getExtractedText");
return super.getExtractedText(request, flags);
}
@Override
public boolean performPrivateCommand(String action, Bundle data) {
Log.d(TAG, "performPrivateCommand");
log("performPrivateCommand");
return super.performPrivateCommand(action, data);
}
private void immUpdateSelection() {
Pair selection = this.getSelection();
Pair cr = this.getComposingRegion();
Log.d(TAG,
log(
"immUpdateSelection: " + selection.first + "," + selection.second + ". " + cr.first + ","
+ cr.second);
settings.mEditorInfo.initialSelStart = selection.first;
@@ -428,8 +431,8 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
return false;
}
int keyCode = event.getKeyCode();
Log.d(
TAG, String.format("processKeyEvent(key=%d) text=%s", keyCode, this.mEditable.toString()));
log(
"processKeyEvent(key=" + keyCode + ") text=" + this.mEditable.toString());
// Filter out Enter keys if multi-line mode is disabled.
if ((settings.mEditorInfo.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0
&& (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER)
@@ -503,7 +506,7 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
}
if (selection.first != selection.second) {
Log.d(TAG, String.format("processKeyEvent: deleting selection"));
log("processKeyEvent: deleting selection");
this.mEditable.delete(selection.first, selection.second);
}
@@ -525,7 +528,7 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
this.setComposingRegion(composingRegion.first, composingRegion.second);
int new_cursor = selection.first + charsToInsert.length();
setSelection(new_cursor, new_cursor);
Log.d(TAG, String.format("processKeyEvent: exit, text=%s", this.mEditable.toString()));
log("processKeyEvent: exit, text=" + this.mEditable.toString());
return true;
}
@@ -594,7 +597,7 @@ public class InputConnection extends BaseInputConnection implements View.OnKeyLi
*/
@Override
public boolean performEditorAction(int action) {
Log.d(TAG, "performEditorAction, action=" + action);
log("performEditorAction, action=" + action);
if (action == IME_ACTION_UNSPECIFIED) {
// Super emulates Enter key press/release
return super.performEditorAction(action);

View File

@@ -173,8 +173,11 @@ impl GameTextInput {
let mut new_focus = state.lock();
// Mark new state as active
new_focus.is_active = true;
let new_state = new_focus.state.clone();
drop(new_focus);
// Push changes to the Java side
self.push_update(&new_focus.state);
self.push_update(&new_state);
}
pub fn push_update(&self, state: &AndroidTextInputState) {
@@ -192,6 +195,21 @@ impl GameTextInput {
}
}
pub fn set_select(&self, start: i32, end: i32) -> Result<(), ()> {
let Some(input_connection) = *self.input_connection.read() else {
w!("push_update() - no input_connection set");
return Err(())
};
let is_success = unsafe {
let env = android::attach_jni_env();
call_bool_method!(env, input_connection, "setSelection", "(II)Z", start, end)
};
if is_success == 0u8 {
return Err(())
}
Ok(())
}
pub fn set_input_connection(&self, input_connection: ndk_sys::jobject) {
unsafe {
let env = android::attach_jni_env();

View File

@@ -87,4 +87,22 @@ impl AndroidTextInput {
gti.push_update(&state);
}
}
pub fn set_select(&self, select_start: usize, select_end: usize) {
//t!("set_select({select_start}, {select_end})");
// Always update our own state.
let mut ours = self.state.lock();
let state = &mut ours.state;
assert!(select_start <= state.text.len());
assert!(select_end <= state.text.len());
state.select = (select_start, select_end);
let is_active = ours.is_active;
drop(ours);
// Only update java state when this input is active
if is_active {
let gti = GAME_TEXT_INPUT.get().unwrap();
gti.set_select(select_start as i32, select_end as i32).unwrap();
}
}
}

View File

@@ -1559,13 +1559,19 @@ pub async fn make(
let atom = &mut render_api.make_guard(gfxtag!("edit select task"));
if editz_select_text.is_null(0).unwrap() {
info!(target: "app::chat", "selection changed: null");
actions_is_visible.set(atom, false);
pasta_is_visible2.set(atom, false);
// Avoid triggering unecessary redraws
if actions_is_visible.get() {
actions_is_visible.set(atom, false);
pasta_is_visible2.set(atom, false);
}
} else {
let select_text = editz_select_text.get_str(0).unwrap();
info!(target: "app::chat", "selection changed: {select_text}");
actions_is_visible.set(atom, true);
pasta_is_visible2.set(atom, false);
// Avoid triggering unecessary redraws
if !actions_is_visible.get() {
actions_is_visible.set(atom, true);
pasta_is_visible2.set(atom, false);
}
}
}
});

View File

@@ -529,15 +529,23 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
let chatdb_path = get_chatdb_path();
let db = sled::open(chatdb_path).expect("cannot open sleddb");
//for channel in CHANNELS {
chat::make(app, content.clone(), "dev", &db, i18n_fish, emoji_meshes.clone(), is_first_time)
for channel in CHANNELS {
chat::make(
app,
content.clone(),
channel,
&db,
i18n_fish,
emoji_meshes.clone(),
is_first_time,
)
.await;
//}
//menu::make(app, content.clone(), i18n_fish).await;
}
menu::make(app, content.clone(), i18n_fish).await;
// @@@ Debug stuff @@@
let chatview_node = app.sg_root.lookup_node("/window/content/dev_chat_layer").unwrap();
chatview_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
//let chatview_node = app.sg_root.lookup_node("/window/content/dev_chat_layer").unwrap();
//chatview_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
//let menu_node = app.sg_root.lookup_node("/window/content/menu_layer").unwrap();
//menu_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
}

View File

@@ -132,9 +132,10 @@ impl Editor {
let cursor_idx = cursor.index();
t!(" move_to_pos: {cursor_idx}");
assert!(cursor_idx <= self.state.text.len());
assert_eq!(self.state.text, self.text.get());
self.state.select = (cursor_idx, cursor_idx);
self.state.compose = None;
self.input.set_state(self.state.clone());
self.input.set_select(cursor_idx, cursor_idx);
}
pub async fn select_word_at_point(&mut self, pos: Point) {
@@ -216,10 +217,12 @@ impl Editor {
parley::Selection::new(anchor, focus)
}
pub async fn set_selection(&mut self, select_start: usize, select_end: usize) {
self.state.text = self.text.get();
assert!(select_start <= self.state.text.len());
assert!(select_end <= self.state.text.len());
assert_eq!(self.state.text, self.text.get());
self.state.select = (select_start, select_end);
self.state.compose = None;
self.input.set_state(self.state.clone());
self.input.set_select(select_start, select_end);
}
#[allow(dead_code)]

View File

@@ -1363,16 +1363,17 @@ impl UIObject for BaseEdit {
fn init(&self) {
let mut guard = self.editor.lock_blocking();
assert!(guard.is_none());
let mut editor = Editor::new(
let editor = Editor::new(
self.text.clone(),
self.font_size.clone(),
self.text_color.clone(),
self.window_scale.clone(),
self.lineheight.clone(),
);
let atom = &mut PropertyAtomicGuard::none();
self.text.set(atom, "the quick brown fox jumped over the");
smol::block_on(editor.on_text_prop_changed());
// For Android you can do this:
//let atom = &mut PropertyAtomicGuard::none();
//self.text.set(atom, "the quick brown fox jumped over the");
//smol::block_on(editor.on_text_prop_changed());
*guard = Some(editor);
}