Re: The Inbox: Morphic-nice.2232.mcz
Nicolas Cellier <[email protected]>
| Newsgroups | gmane.comp.lang.smalltalk.squeak.general |
|---|---|
| Message-ID | <CAKnRiT7O8S9KN3=RTg24PknSX_1AXQfYkyU3BL1B7NQbe1XW6Q@mail.gmail.com> |
Hi Marcel, my answers below. Le dim. 23 août 2026 à 13:05, Marcel Taeumel (H) via Squeak-dev < [email protected]> a écrit : > Hi Nicolas -- > > Thanks! If you would maintain this for a couple of weeks, feel free to > merge this into 6.2alpha to get feedback from others? > > > reduce the pause to zero as long as there is some user activity > > This might just mask another underlying issue. If user activity eats up > time, inter-cycle pause will be 0 anyway. Hmm... this needs more > analysis... > > In my case on mac, activity is eaten by spurious Delay wait... So yes, on a slow machine, this would not change anything, the change is dedicated to make fast machines feel fast again. > > gradually let the interCyclePause grow, up to 200ms whenever there is > no event. > > Which would be unacceptable for animations, right? :) Good for server > usage, though? > > Juan took care of stepList: if an animation is obtained by regular stepping, then the interCyclePause never exceeds the next step firing time. At least if stepList first is the next morph to be awaken... (which is normally the case because it is a Heap sorted by scheduledTime increasing) This is very intersting! I recall that we have some issues with the way > our idle process works in OSVM? > > Maybe I should inquire that too... > In general, I think there are more macOS-specific things here going on. > Yes, Cuis might not trigger some lag issues, OSVM has on macOS. Still... > > I agree that the problem is particularly apparent on mac. Still, I think that adaptive interCyclePause is good for every OS, CPU-wise. Juan designed some good tradeoffs. Best > Marcel > > Am 23.08.2026 um 12:09 schrieb [email protected]: > > Nicolas Cellier uploaded a new version of Morphic to project The Inbox: > > http://source.squeak.org/inbox/Morphic-nice.2232.mcz > > > > ==================== Summary ==================== > > > > Name: Morphic-nice.2232 > > Author: nice > > Time: 23 August 2026, 12:09:31.458567 pm > > UUID: 808b6801-2603-45a2-9e3f-7b6bc430a809 > > Ancestors: Morphic-mt.2231 > > > > Try and make the UI feel more snappy, while not burning too many CPU > cycles whenever user activity drops. > > > > The strategy used is the one adopted by Cuis Smalltalk: that is, > modulate the interCyclePause at each world cycle: > > - reduce the pause to zero as long as there is some user activity > > (hence suppress the need for #canSurrenderToOS:) > > - gradually let the interCyclePause grow, up to 200ms whenever there is > no event. > > > > This does eliminate the need for manually tuning the MinCycleLapse, and > also the Preferences higherPerformance (there is however one sender > remaining in FrameRateMorph). > > > > Also make scroll events (MouseWheelEvent) unidirectional, so as to avoid > parasitic side motions. Do this at event consumption (ScrollPane) rather > than at event creation, so that a Morph interested in diagnonal scrolling > could still have its cake. > > > > =============== Diff against Morphic-mt.2231 =============== > > > > Item was changed: > > ----- Method: HandMorph>>generateMouseWheelEvent: (in category > 'private events') ----- > > generateMouseWheelEvent: evtBuf > > "Generate the appropriate mouse wheel event for the given raw > event buffer" > > > > | buttons modifiers deltaX deltaY stamp nextEvent | > > stamp := evtBuf second. > > stamp = 0 ifTrue: [stamp := Sensor eventTimeNow]. > > deltaX := evtBuf third. > > deltaY := evtBuf fourth. > > buttons := evtBuf fifth. > > modifiers := evtBuf sixth. > > [(deltaX abs + deltaY abs < self class minimumWheelDelta) > > and: [(nextEvent := Sensor peekEvent) notNil > > and: [nextEvent first = evtBuf first > > and: [nextEvent fifth = evtBuf fifth > > and: [nextEvent sixth = evtBuf sixth > > + and: [nextEvent fourth abs >= evtBuf third abs = > (deltaY abs >= deltaX abs) "both horizontal or vertical dominated"]]]]]] > > - and: [nextEvent third isZero = evtBuf third isZero > "both horizontal or vertical"]]]]]] > > whileTrue: > > ["nextEvent is similar. Remove it from the queue, > and check the next." > > nextEvent := Sensor nextEvent. > > deltaX := deltaX + nextEvent third. > > deltaY := deltaY + nextEvent fourth]. > > ^ MouseWheelEvent new > > setType: #mouseWheel > > position: self position > > delta: deltaX@deltaY > > buttons: buttons > > hand: self > > stamp: stamp! > > > > Item was changed: > > ----- Method: HandMorph>>initForEvents (in category 'initialization') > ----- > > initForEvents > > mouseOverHandler := nil. > > + lastMouseEvent := MouseMoveEvent new setType: #mouseMove position: > 0@0 buttons: 0 hand: self. > > - lastMouseEvent := MouseEvent new setType: #mouseMove position: 0@0 > buttons: 0 hand: self. > > lastKeyDownEvent := KeyboardEvent new setType: #keyDown buttons: 0 > position: 0@0 keyValue: 0 hand: self stamp: 0. > > lastEventBuffer := {1. 0. 0. 0. 0. 0. nil. nil}. > > self resetClickState. > > self addKeyboardCaptureFilter: self. "to convert unusual VM > events"! > > > > Item was changed: > > ----- Method: HandMorph>>processEvents (in category 'event handling') > ----- > > processEvents > > "Process user input events from the local input devices." > > > > + | evtBuf hadAny hadAnyMouse | > > - | evtBuf hadAny | > > self currentEvent ~= lastMouseEvent ifTrue: [ > > "Meaning that we were invoked from within an event > response. > > Make sure z-order is up to date." > > self mouseOverHandler processMouseOver: lastMouseEvent]. > > > > + hadAnyMouse := hadAny := false. > > - hadAny := false. > > [(evtBuf := Sensor nextEvent) isNil] whileFalse: [ > > | evt | > > evt := evtBuf first "type" > > caseOf: { > > [EventTypeMouse] -> [self > generateMouseEvent: evtBuf]. > > [EventTypeMouseWheel] -> [self > generateMouseWheelEvent: evtBuf]. > > [EventTypeKeyboard] -> [self > generateKeyboardEvent: evtBuf]. > > [EventTypeDragDropFiles] -> [self > generateDropFilesEvent: evtBuf]. > > [EventTypeWindow] -> [self > generateWindowEvent: evtBuf] } > > otherwise: [nil "All other events are ignored"]. > > > > evt ifNotNil: [ > > "Finally, handle it" > > self handleEvent: evt. > > hadAny := true. > > + evt isMouse ifTrue: [hadAnyMouse := true] ] ]. > > - > > - "For better user feedback, return immediately > after a mouse event has been processed." > > - evt isMouse ifTrue: [^ self] ] ]. > > > > "note: if we come here we didn't have any mouse events" > > + mouseClickState ifNotNil: [hadAnyMouse ifFalse: [ > > - mouseClickState ifNotNil: [ > > "No mouse events during this cycle. Make sure click states > time out accordingly" > > + mouseClickState handleEvent: lastMouseEvent asMouseMove > from: self] ]. > > - mouseClickState handleEvent: lastMouseEvent asMouseMove > from: self]. > > hadAny ifFalse: [ > > "No pending events. Make sure z-order is up to date" > > + self mouseOverHandler processMouseOver: lastMouseEvent]. > > + ^hadAny! > > - self mouseOverHandler processMouseOver: lastMouseEvent].! > > > > Item was added: > > + ----- Method: HandMorph>>waitingForMoreClicks (in category 'double > click support') ----- > > + waitingForMoreClicks > > + "Answer true " > > + > > + ^mouseClickState notNil! > > > > Item was added: > > + ----- Method: MouseWheelEvent>>beUnidirectional (in category > 'initialization') ----- > > + beUnidirectional > > + "only preserve dominant direction and annihilate the dominated one" > > + delta := (delta y abs >= delta x abs) > > + ifTrue: [0 @ delta y] > > + ifFalse: [delta x @ 0]. > > + direction := 2r0000. > > + self setDirection! > > > > Item was changed: > > ----- Method: ScrollPane>>mouseWheel: (in category 'event handling') > ----- > > mouseWheel: evt > > + evt beUnidirectional. "this is to avoid parasitic displacements > on the other axis" > > - > > evt isWheelUp ifTrue: [scrollBar scrollUp: (evt > verticalScrollDelta: self class verticalScrollDeltaPerMouseWheelNotch)]. > > evt isWheelDown ifTrue: [scrollBar scrollDown: (evt > verticalScrollDelta: self class verticalScrollDeltaPerMouseWheelNotch)]. > > evt isWheelLeft ifTrue: [hScrollBar scrollUp: (evt > horizontalScrollDelta: self class horizontalScrollDeltaPerMouseWheelNotch)]. > > evt isWheelRight ifTrue: [hScrollBar scrollDown: (evt > horizontalScrollDelta: self class > horizontalScrollDeltaPerMouseWheelNotch)].! > > > > Item was changed: > > Object subclass: #WorldState > > + instanceVariableNames: 'hands viewBox canvas damageRecorder > stepList lastStepTime lastStepMessage lastCycleTime commandHistory alarms > lastAlarmTime remoteServer lastCycleHadAnyEvent interCycleDelay > interCyclePause' > > + classVariableNames: 'CanSurrenderToOS DeferredUIMessages > DisableDeferredUpdates LastCycleTime' > > - instanceVariableNames: 'hands viewBox canvas damageRecorder > stepList lastStepTime lastStepMessage lastCycleTime commandHistory alarms > lastAlarmTime remoteServer multiCanvas interCycleDelay' > > - classVariableNames: 'CanSurrenderToOS DeferredUIMessages > DisableDeferredUpdates LastCycleTime MinCycleLapse' > > poolDictionaries: '' > > category: 'Morphic-Worlds'! > > > > !WorldState commentStamp: 'ls 7/10/2003 19:30' prior: 0! > > The state of a Morphic world. (This needs some serious > commenting!!!!) > > > > > > The MinCycleLapse variable holds the minimum amount of time that a > morphic cycle is allowed to take. If a cycle takes less than this, then > interCyclePause: will wait until the full time has been used up.! > > > > Item was changed: > > ----- Method: WorldState class>>canSurrenderToOS: (in category > 'accessing') ----- > > canSurrenderToOS: aBoolean > > + "Since the interCyclePause is now adaptative, it is no longer > necessary to ask for more UI responsiveness thru this message" > > + self deprecated.! > > - > > - CanSurrenderToOS := aBoolean! > > > > Item was changed: > > ----- Method: WorldState class>>initialize (in category 'class > initialization') ----- > > initialize > > "WorldState initialize" > > > > - MinCycleLapse := 20. "allows 50 frames per second..." > > DisableDeferredUpdates := false. > > DeferredUIMessages := SharedQueue new.! > > > > Item was added: > > + ----- Method: WorldState>>doInterCyclePause (in category 'update > cycle') ----- > > + doInterCyclePause > > + "Handle the delay between consecutives Morphic cycle loops in an > adaptative fashion: > > + - provide a quick response as long as user activity is seen. > > + - In order to lower the cpu usage, increase this delay whenever > there is no user activity (no event delivered). > > + If the preference #serverMode is enabled, always do a complete > delay of 50ms. > > + This prevents the freezing problem described in Mantis #6581" > > + > > + | millisecondsToWait nextWakeUpTime | > > + (lastCycleHadAnyEvent or: [DeferredUIMessages isEmpty not]) > > + ifTrue: > > + [interCyclePause := 20. > > + millisecondsToWait := 0] > > + ifFalse: > > + ["Adjust the interCyclePause between 20 and 200 > milliseconds" > > + (hands anySatisfy: [:hand | hand > waitingForMoreClicks]) > > + ifTrue: [interCyclePause := 20] > > + ifFalse: [(interCyclePause ifNil: [20]) < > 200 ifTrue: [interCyclePause := interCyclePause * 21//20]]. > > + nextWakeUpTime := lastCycleTime + interCyclePause. > > + "Wake up earlier if steps" > > + stepList isEmpty ifFalse: [nextWakeUpTime := > nextWakeUpTime min: stepList first scheduledTime]. > > + "Wake up earlier if alarms" > > + alarms ifNotNil: [alarms alarmsDoSafely: [:alarm | > nextWakeUpTime := nextWakeUpTime min: alarm scheduledTime]]. > > + millisecondsToWait := (nextWakeUpTime - Time > millisecondClockValue max: 0) min: interCyclePause ]. > > + "Always wait at least a bit on servers, even if this makes the UI > slow." > > + Preferences serverMode > > + ifTrue: [millisecondsToWait := millisecondsToWait max: 50]. > > + millisecondsToWait = 0 > > + ifTrue: [Processor yield] > > + ifFalse: > > + [(interCycleDelay isNil or: [ interCycleDelay > beingWaitedOn]) > > + ifTrue: [ interCycleDelay := Delay > forMilliseconds: millisecondsToWait] > > + ifFalse: [ interCycleDelay delayDuration: > millisecondsToWait]. > > + interCycleDelay wait]. > > + lastCycleTime := Time millisecondClockValue! > > > > Item was changed: > > ----- Method: WorldState>>doOneCycleFor: (in category 'update cycle') > ----- > > doOneCycleFor: aWorld > > "Do one cycle of the interaction loop. This method is called > repeatedly when the world is running. > > > > This is a moderately private method; a better alternative is usually > either to wait for events or to check the state of things from #step > methods." > > > > + self doInterCyclePause. > > - self interCyclePause: (Preferences higherPerformance ifTrue: [1] > ifFalse: [MinCycleLapse]). > > self doOneCycleNowFor: aWorld.! > > > > Item was changed: > > ----- Method: WorldState>>doOneCycleNowFor: (in category 'update > cycle') ----- > > doOneCycleNowFor: aWorld > > "Immediately do one cycle of the interaction loop. > > This should not be called directly, but only via doOneCycleFor:" > > > > | capturingGesture | > > DisplayScreen checkForNewScreenScaleFactor; checkForNewScreenSize. > > capturingGesture := false. > > "self flag: #bob. " "need to consider remote hands in > lower worlds" > > > > "process user input events" > > LastCycleTime := Time millisecondClockValue. > > self handsDo: [:hand | > > hand becomeActiveDuring: [ > > + lastCycleHadAnyEvent := hand processEvents. > > - hand processEvents. > > capturingGesture := capturingGesture or: [hand > isCapturingGesturePoints]]]. > > > > "The gesture recognizer needs enough points to be accurate. > > Therefore morph stepping is disabled while capturing points for > the recognizer" > > capturingGesture ifFalse: [ > > aWorld becomeActiveDuring: [ > > aWorld runStepMethods "there are currently some > variations here". > > self displayWorldSafely: aWorld]].! > > > > Item was changed: > > ----- Method: WorldState>>initialize (in category 'initialization') > ----- > > initialize > > > > hands := Array new. > > damageRecorder:= DamageRecorder new. > > stepList := Heap sortBlock: self stepListSortBlock. > > lastStepTime := 0. > > + lastAlarmTime := 0. > > + lastCycleHadAnyEvent := false. > > + interCyclePause := 20. "milliseconds" > > + ! > > - lastAlarmTime := 0.! > > > > Item was removed: > > - ----- Method: WorldState>>interCyclePause: (in category 'update > cycle') ----- > > - interCyclePause: milliSecs > > - "delay enough that the previous cycle plus the amount of delay > will equal milliSecs. If the cycle is already expensive, then no delay > occurs. However, if the system is idly waiting for interaction from the > user, the method will delay for a proportionally long time and cause the > overall CPU usage of Squeak to be low. > > - If the preference #serverMode is enabled, always do a complete > delay of 50ms, independant of my argument. This prevents the freezing > problem described in Mantis #6581" > > - > > - | millisecondsToWait | > > - millisecondsToWait := Preferences serverMode > > - ifTrue: [ 50 ] > > - ifFalse: [ > > - (lastCycleTime isNil or: [ CanSurrenderToOS == > false ]) > > - ifTrue: [ 0 ] > > - ifFalse: [ milliSecs - (Time > millisecondsSince: lastCycleTime) ] ]. > > - (Preferences serverMode or: [millisecondsToWait > 0 and: > [millisecondsToWait <= milliSecs]]) > > - ifTrue: [ > > - (interCycleDelay isNil or: [ interCycleDelay > beingWaitedOn ]) > > - ifTrue: [ interCycleDelay := Delay > forMilliseconds: millisecondsToWait ] > > - ifFalse: [ interCycleDelay delayDuration: > millisecondsToWait ]. > > - interCycleDelay wait ]. > > - lastCycleTime := Time millisecondClockValue. > > - CanSurrenderToOS := true.! > > > > Squeak-dev mailing list -- [email protected] > > To unsubscribe send an email to > [email protected] > Squeak-dev mailing list -- [email protected] > To unsubscribe send an email to > [email protected] > Squeak-dev mailing list -- [email protected] To unsubscribe send an email to [email protected]