'From Cuis 2.3 of 22 March 2010 [latest update: #472] on 27 May 2010 at 9:52:29 am'!
!classDefinition: #WorldState category: #'Morphic-Worlds'!
Object subclass: #WorldState
	instanceVariableNames: 'hands viewBox canvas damageRecorder stepList lastStepTime lastStepMessage lastCycleTime alarms lastAlarmTime remoteServer drawingFailingMorphs waitDelay pause '
	classVariableNames: 'CanSurrenderToOS DeferredUIMessages DisableDeferredUpdates LastCycleTime MinCycleLapse MaxCycleLapse '
	poolDictionaries: ''
	category: 'Morphic-Worlds'!

!HandMorph methodsFor: 'event handling' stamp: 'jmv 5/27/2010 09:03'!
processEvents
	"Process user input events from the local input devices."

	| evt evtBuf type hadAny |
	ActiveEvent ifNotNil: [
		"Meaning that we were invoked from within an event response.
		Make sure z-order is up to date"
		self mouseOverHandler processMouseOver: lastMouseEvent].

	hadAny := false.
	[ (evtBuf := Sensor nextEvent) isNil ] whileFalse: [
		evt := nil.	"for unknown event types"
		type := evtBuf first.
		type = EventTypeMouse
			ifTrue: [ evt := self generateMouseEvent: evtBuf ].
		type = EventTypeKeyboard 
			ifTrue: [ evt := self generateKeyboardEvent: evtBuf ].
		"All other events are ignored"
		evt
			ifNil: [
				^hadAny]
			ifNotNil: [
				"Finally, handle it"
				self handleEvent: evt.
				hadAny := true.
				"For better user feedback, return immediately after a mouse event has been processed."
				evt isMouse ifTrue: [ ^hadAny ]]].
	"note: if we come here we didn't have any mouse events"
	mouseClickState 
		ifNotNil: [ 
			"No mouse events during this cycle. Make sure click states time out accordingly"
			mouseClickState handleEvent: lastMouseEvent asMouseMove from: self].
	hadAny 
		ifFalse: [
			"No pending events. Make sure z-order is up to date"
			"jmv
			Try to understand why this was ever needed.
			It is very expensive in slow hardware, for example in Cytec's STB.
			And in any case, why would this be needed if no event was processed?
			I believe that this is a bad solution for some problem.
			When we encounter that problem, we must think of a better solution!!
			"
			"
			self mouseOverHandler processMouseOver: lastMouseEvent
			"
			].
	^hadAny! !


!WorldState methodsFor: 'initialization' stamp: 'jmv 5/27/2010 09:43'!
initialize

	hands _ Array new.
	damageRecorder_ DamageRecorder new.
	stepList _ Heap sortBlock: self stepListSortBlock.
	lastStepTime _ 0.
	lastAlarmTime _ 0.
	drawingFailingMorphs _ IdentitySet new.
	pause _ MinCycleLapse! !

!WorldState methodsFor: 'update cycle' stamp: 'jmv 5/27/2010 09:52'!
doOneCycleFor: aWorld
	"Do one cycle of the interaction loop. This method is called repeatedly when the world is running.
	
	Make for low cpu usage if the ui is inactive, but quick response when ui is in use.
	However, after some inactivity, there will be a MaxCycleLapse delay before the ui gets responsive again."

	| hadAnyEvent thisPause |

	pause ifNil: [ pause _ MinCycleLapse ].		"Remove this later"
	thisPause _ pause.

	"pause is set to very short if we have events already enqueued, so they are processed quickly."
	(Preferences higherPerformance or: [ Sensor eventQueue isEmpty not ]) 
		ifTrue: [ thisPause _ 1 ].
	self interCyclePause: thisPause.

	hadAnyEvent _ self doOneCycleNowFor: aWorld.
	hadAnyEvent
		ifTrue: [  pause _ MinCycleLapse ]
		ifFalse: [
			pause < MaxCycleLapse		"No events processed? Start saving CPU!!"
				ifTrue: [
					pause _ pause * 21//20 ]]! !

!WorldState methodsFor: 'update cycle' stamp: 'jmv 5/23/2010 00:08'!
doOneCycleNowFor: aWorld
	"Immediately do one cycle of the interaction loop.
	This should not be called directly, but only via doOneCycleFor:"

	| hadAnyEvent |
	DisplayScreen checkForNewScreenSize.

	"process user input events"
	self handsDo: [ :h |
		ActiveHand _ h.
		hadAnyEvent _ h processEvents.
		ActiveHand _ nil
	].

	"the default is the primary hand"
	ActiveHand _ self hands first.

	aWorld runStepMethods.		"there are currently some variations here"
	self displayWorldSafely: aWorld.

	^hadAnyEvent! !

!WorldState methodsFor: 'update cycle' stamp: 'jmv 5/27/2010 09:42'!
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."

	| wait |
	waitDelay ifNil: [ waitDelay _ Delay forMilliseconds: 50 ].
	wait _ lastCycleTime notNil
		ifTrue: [ lastCycleTime + milliSecs - Time millisecondClockValue ]
		ifFalse: [ 0 ].
	Preferences serverMode
		ifTrue: [ wait _ wait max: 50 ].	"Always wait at least a bit on servers, even if this makes the UI slow."
	(wait > 0 and: [ wait <= milliSecs ])
		ifTrue: [
			waitDelay beingWaitedOn
				ifFalse: [ waitDelay setDelay: wait; wait ]
				ifTrue: [
					"If we are called from a different process than that of the main UI, we might be called in the main
					interCyclePause. In such case, use a new Delay to avoid 'This Delay has already been scheduled' errors"
					(Delay forMilliseconds: wait) wait ]].

	lastCycleTime _  Time millisecondClockValue! !


!WorldState class methodsFor: 'class initialization' stamp: 'jmv 5/27/2010 09:50'!
initialize
	"WorldState initialize"

	MinCycleLapse _ 20.		"allows 50 frames per second..."
	MaxCycleLapse _ 200.		"never wait more than this to react to user events"
	DeferredUIMessages _ SharedQueue new.! !

WorldState initialize!
WorldState class removeSelector: #lastCycleTime!
!classDefinition: #WorldState category: #'Morphic-Worlds'!
Object subclass: #WorldState
	instanceVariableNames: 'hands viewBox canvas damageRecorder stepList lastStepTime lastStepMessage lastCycleTime alarms lastAlarmTime remoteServer drawingFailingMorphs waitDelay pause'
	classVariableNames: 'DeferredUIMessages MaxCycleLapse MinCycleLapse'
	poolDictionaries: ''
	category: 'Morphic-Worlds'!
