@@ -138,25 +138,33 @@ public bool HandleMotionEvent(MotionEvent e)
138138 {
139139 if ( ! Enabled . Value ) return false ;
140140
141- if ( e . ActionMasked == MotionEventActions . HoverExit || e . ActionMasked == MotionEventActions . Up || e . ActionMasked == MotionEventActions . Cancel )
141+ // Cache ActionMasked once: each `e.ActionMasked` access is a JNI call into
142+ // MotionEvent#getActionMasked. On a busy stylus drag the previous code did
143+ // 3 reads per event (here + 2 in handlePointer) and HistorySize+1 calls to
144+ // handlePointer; folding to a single read trims the per-event JNI crossings
145+ // by ~2 + 2*(HistorySize+1) at no cost.
146+ var actionMasked = e . ActionMasked ;
147+
148+ if ( actionMasked == MotionEventActions . HoverExit || actionMasked == MotionEventActions . Up || actionMasked == MotionEventActions . Cancel )
142149 {
143150 if ( lastLeftDown ) { PendingInputs . Enqueue ( new MouseButtonInput ( MouseButton . Left , false ) ) ; lastLeftDown = false ; }
144151
145- if ( e . ActionMasked != MotionEventActions . HoverExit )
152+ if ( actionMasked != MotionEventActions . HoverExit )
146153 return true ;
147154 }
148155
149156 // Process all batched historical events for maximum accuracy.
150- for ( int i = 0 ; i < e . HistorySize ; i ++ )
151- handlePointer ( e , i ) ;
157+ int historySize = e . HistorySize ;
158+ for ( int i = 0 ; i < historySize ; i ++ )
159+ handlePointer ( e , i , actionMasked ) ;
152160
153- handlePointer ( e , - 1 ) ;
161+ handlePointer ( e , - 1 , actionMasked ) ;
154162
155163 return true ;
156164 }
157165
158166 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
159- private void handlePointer ( MotionEvent e , int historyIndex )
167+ private void handlePointer ( MotionEvent e , int historyIndex , MotionEventActions actionMasked )
160168 {
161169 const int pointer_index = 0 ;
162170 if ( e . PointerCount <= pointer_index ) return ;
@@ -210,8 +218,12 @@ private void handlePointer(MotionEvent e, int historyIndex)
210218
211219 // Button state: pressure-based click (primary) with action overrides.
212220 // Uses the cached threshold field rather than `PressureThreshold.Value` to skip the
213- // per-event bindable read.
214- var actionMasked = e . ActionMasked ;
221+ // per-event bindable read. `actionMasked` is a parameter (cached once at the top of
222+ // HandleMotionEvent) so we avoid the JNI crossing for `e.ActionMasked` here.
223+ // ButtonState is a single JNI read per pointer (vs. desktop mouse which we already
224+ // hoist) — Move-with-Primary is the only path that needs it and stylus side-buttons
225+ // are intentionally NOT mapped to right/middle (see comment block below), so a single
226+ // read is unavoidable but bounded.
215227 var buttonState = e . ButtonState ;
216228 bool isLeftDown = pressure >= cachedPressureThreshold ;
217229 if ( actionMasked == MotionEventActions . Down || actionMasked == MotionEventActions . ButtonPress ) isLeftDown = true ;
0 commit comments