Jump to content
IGNORED

New GUI for the Atari 8-bit


flashjazzcat

Recommended Posts

Now all you need is a time machine so you can kick Apple and DRI's ass and be a billionaire.

Yes, the vast fortune part of the puzzle remains elusive. :) If I had a time machine, I'd just travel forward three hours for tonight's winning lottery numbers. ;)

 

How about using the SELECT button for switching? Those console keys tend to be ignored by most interfaces.

Yeah, I was thinking about the console keys for mouse modifiers too, since we can't detect Ctrl on its own (although Shift+Click is no trouble).

 

Really cool that plotting the list control is already working! I remember that it was quite a lot of work, as its one of the most complex control compared to the other ones. Are these already real values, which are displayed in the screenshot?

I agree: the list control is quite complex (probably second only to the multi-line text control in terms of complexity), and I was dragging my feet with it a little. Those are actual values, but I'm now wrestling with a bug in the sampling code which results in the UI's usage not going up sufficiently when the Idle Process's usage reduces. The UI yields a lot, so I figure that's where the bug must be. Nevertheless, the list redraws every couple of seconds at the moment and it's quite cool to see those figures changing, even if they are a bit skewed at the moment.

Link to comment
Share on other sites

The latest screenshot shows the idle process with 94% of the cpu time. So the system usage has been reduced from 30% originally to only 6% now? Or am I reading that wrong?

 

 

I already posted about the reduced CPU usage here. Prior to that, idle usage was reduced from c. 30 per cent to 15-17. The discrepancy between 8% and 6% is explained by the fact we are looking at a different tab of the dialog box, for which the UI must render a list control instead of a bitmap (the graph). So depending on which tab we're looking at, the UI redraw instigated by the task manager will consume a different amount of CPU time at the very beginning of the sample phase following the one it has just displayed on the screen.

Edited by flashjazzcat
Link to comment
Share on other sites

Was really up against a brick wall for hours tonight with the sampling bug. When moving the window around, the Idle Process's percentage would drop but the UI process's percentage wouldn't increase correspondingly. I left it for a couple of hours and watched TV, and finally found the problem when I came back to it. To recap: the scheduler calculates the difference between VCOUNT at the start and end of the process's time slice. There was code to handle the situation of VCOUNT being lower at the end of the quantum than at the start, but crucially I'd forgotten to check for Z=1 as well as C=0. The result was that the UI process - since it doesn't yield or get pre-empted until it has finished a lengthy redraw - was constantly adding 0 to its counter (VCOUNT being the same on entry and exit).

 

So, with that fixed, and subject to some rounding errors...

 

http://youtu.be/pUBOlLzoRl4

 

The task manager itself takes much less than one per cent of the CPU, since it (currently) sleeps until it gets a buffer of sample data, and then it does no more than rotate a bitmap and send a few messages to the UI.

  • Like 8
Link to comment
Share on other sites

It works! Really cool!

 

The task manager itself takes much less than one per cent of the CPU, since it (currently) sleeps until it gets a buffer of sample data, and then it does no more than rotate a bitmap and send a few messages to the UI.

 

Makes sense! Running the one in SymbOS takes 2% total, but this includes GUI cpu time, which is probably the most part of the work.

Btw, this weekend the "Classic Computing" meeting took place again, which is (one of, or probably) the biggest retro computing meeting in Germany. I couldn't attend there, but Stefan Both (who is writing here, too, and already got some help from FJC :) ) managed to build up his Atari 800XL and his Amstrad CPC and ran the A8Gui + SymbOS at the same time nearby, which was really cool! Here are two pictures he sent me today:

 

post-34635-0-34994000-1412535722_thumb.jpgpost-34635-0-32381400-1412535729_thumb.jpg

Edited by Prodatron
  • Like 6
Link to comment
Share on other sites

Have you considered using that smaller font you show in the task monitor text area for the system and app menus? I know that's probably an aesthetic downgrade, but it would add a lot of space to the menu system. Personally I'd rather have the space.

 

Can't. No way to gray out fonts that aren't bold (at least no way that we're comfortable with). Of course these things have been tested out several times. We do have a bold version of that small font though, which is smaller than the current system bold font. The space savings wouldn't be as great, but we're going to check it out and see what we think. You can see that smaller font in some of the mockups I recently did for some variations on the task manager (although not used in any menus there). This smaller font will most likely end up at least being an optional system font configurable/selectable by the user.

Edited by MrFish
Link to comment
Share on other sites

As discussed, the potential for space-saving is slight, but there's potential for improved visual balance using a slightly smaller bold font. As MrFish infers, greyed out "light" (1px verticals) fonts look pretty bad compared to greyed out fonts of a higher weight, although there will inevitably be times that light fonts are also greyed out in dialogs. But the greyed out items in menus are somehow much more conspicuous (and commonplace) than the odd greyed out control label in a dialog.

Link to comment
Share on other sites

Added a sleep function which seems to be working well. This is different to MessageSleepReceive, which simply puts the process to sleep indefinitely until it receives a message. Sleep puts the process to sleep for a specified number of system ticks (from 1/50 or 1/60 second to around three days), or until it receives a message from another process. Sleep is called with a 24-bit value in A,X,Y (the sleep duration), and it returns (in A,X,Y) the unspent ticks if the process was woken by a message, or zero if the process was woken by the elapsed timer. In that sense it behaves rather like the Linux Sleep function.

 

To avoid a mess of 24-bit counters, a queue of sleeping processes is maintained. When a process is put to sleep via Sleep, it is placed in chronological order in the wake-up queue (with the process to be woken first at the head of the queue). Each process's sleep time is relative to that of the previous process in the queue. This way, only the 24-bit timer of the process at the head of the wake-up queue must be decremented every interrupt. When this timer reaches zero, the process at the head of the queue and any consecutive processes whose counters are also zero (i.e. all processes scheduled to wake up at the exact same time) are woken up and removed from the queue. Any process in the wake-up queue which receives a message before its timer has elapsed is immediately removed from the queue and woken up. Any subsequent process in the queue with a non-zero counter becomes the head process in the wake-up queue and its counter is then decremented during the interrupt.

 

This should be a reasonably good way of making the task manager's graphs update at periodic intervals (currently the kernel spits out a message to the caller every couple of seconds), and will allow multiple processes to obtain sample data. I think the final mutlitasking primitive to implement here will be some kind of semaphore system which (ideally) manages sleep states in a similar fashion. This is primarily to cope with the situation when the GUI and application both require access to the same resource at the same time, which usually isn't such a problem. Spinning is probably OK for short waits, and at least avoids the sleep overhead and the response time when the resource is released is lower. Will have to experiment a bit.

Edited by flashjazzcat
  • Like 2
Link to comment
Share on other sites

Good feature and solution! I am currently thinking about, where this could be usefull (beside the task manager, which really only needs CPU time every x seconds). Most other tasks which need CPU time on a regular base (which are mostly not the usual ones) need them permanently (like video player) or every 1/50 second (like music player). But it's right, there are probably some apps which only need to update something every x seconds as well!...

 

One question: Where do you handle the timer test? While switching tasks (so that means inside the scheduler) or in a separate routine, which is called every 1/50 second?

The reason I ask is, if it is placed in the scheduler, the test would be made multiple times within a V-blank (1/50s) if multiple tasks are going into idle. This wouldn't make sense as only after 1/50s there is happning something with the timers. I was thinking about making this test in the separated "kernel timer" process which is called constantly each 1/50s (this already handles counter variables for processes, so it's there anyway).

 

---

 

**EDIT** lol, that was a stupid question :D It's probably best to make the test when restarting the list of processes inside the scheduler...

Edited by Prodatron
  • Like 1
Link to comment
Share on other sites

No - you make a good point. I was testing in the scheduler (well, the VBLANK), but it makes good sense to wait until we start at the top of the run queue again. :)

 

Another use for the sleep function would be the system clock, which I'm considering running as a separate application (with its own pull-down menu, dialogs, etc - similar to the OS X menu bar clock). Certainly some applications which run periodically could maintain their own timers and then go idle, but since context switches are a little more expensive on the 6502, I figure it's best to put processes to sleep wherever possible.

Edited by flashjazzcat
Link to comment
Share on other sites

Turned out well: CPU usage is more or less the same, but the task manager no longer dictates the sample duration. Instead, whenever it wakes up it just issues a "GetProcessInfo" Syscall and then displays the CPU load information the scheduler has already computed.

 

I didn't respond to the previous observation very accurately. Only the NMI handles the countdown timers every 1/50s, but no attempt is made to delegate wake-ups until we wrap around to the top of the run-queue (I figure a pending wake-up might be lower down the priority chain anyway).

	ldx Process.WakeUpHead ; see if we need to wake up any sleeping processes on timers
	beq NoWakeUps

	lda Process.WakeUpLo,x
	bne DLo
	lda Process.WakeUpMid,x
	bne DMid
	lda Process.WakeUpHi,x
	bne DHi
	jsr WakeUpSleepTimers ; wake up all the tasks whose sleep timers have reached zero
	jmp NoWakeUps
DHi
	dec Process.WakeUpHi,x
DMid
	dec Process.WakeUpMid,x
DLo
	dec Process.WakeUpLo,x
	
NoWakeUps

That's the extra code required in the NMI. If no sleeping tasks are timed (WakeUpHead = 0), it's skipped. It's easier to count up to zero when using counters with 16 or more bits on the 6502, but this requires the timers to be in twos complement form, and my head can't handle what this would do to the already complex routine which inserts a new timer into the wake-up list at the correct position and adjusts the initial counter of the new and successive timer so that all timers are relative to the previous one in the list. :)

Edited by flashjazzcat
  • Like 1
Link to comment
Share on other sites

	ldx Process.WakeUpHead ; see if we need to wake up any sleeping processes on timers
	beq NoWakeUps
...

 

Isn't this equivalent but shorter?

 

ldx Process.WakeUpHead ; see if we need to wake up any sleeping processes on timers

beq NoWakeUps

 

dec Process.WakeUpLo,x

bne NoWakeUps

dec Process.WakeUpMid,x

bne NoWakeUps

dec Process.WakeUpHi,x

bne NoWakeUps

 

jsr WakeUpSleepTimers ; wake up all the tasks whose sleep timers have reached zero

 

NoWakeUps

Link to comment
Share on other sites

Seems to work? You will have to add 1 to each counter byte first.

 

Yes you are right. The moment I posted it I noticed I was wrong. Sorry - too early for me... ;)

 

Best solution (without loosing a mid and high byte count) would consist in forming the complement and counting upwards. Makes initialisation a little bit more complicated but would save the cycles over the time...

Edited by Irgendwer
Link to comment
Share on other sites

This appears to work:

; ---------------------------------------------------------------------------------
; Supervisor call: Send task to sleep and put it in the wake-up list
; ---------------------------------------------------------------------------------

	.local SleepProcess
	pla ; get Y
	sta SleepTime+2
	pla ; get X
	sta SleepTime+1
	pla ; get A
	sta SleepTime
	jsr Sleep
	ldy Process.Active
	mva #128 Process.WakeUpFlag,y ; say process is on a wake-up timer
	mva #ProcessState.Sleep Process.State,y ; say process is asleep
	lda #> TaskWakeReturn ; scheduler will send task straight to TaskWakeReturn when it wakes up
	pha
	lda #< TaskWakeReturn
	pha
	php
	jmp ForceContextSwitch ; yield to next process
	.endl

	
; ---------------------------------------------------------------------------------
; Exit wrapper for waking up process
; Returns unspent sleep time in A,X,Y
; ---------------------------------------------------------------------------------
	
	.local TaskWakeReturn
	ldx Process.Active
	lda Process.WakeUpLo,x ; we need to twos complement any remaining sleep time
	eor #$ff
	clc
	adc #1
	pha
	lda Process.WakeUpMid,x
	eor #$ff
	adc #0
	pha
	lda Process.WakeUpHi,x
	eor #$ff
	adc #0
	tay
	pla
	tax
	pla
	rti
	.endl
	
	
; ---------------------------------------------------------------------------------
; Send task to sleep and put it in the wake-up list
; ---------------------------------------------------------------------------------
	
	.local Sleep
	lda SleepTime ; get twos complement of sleep time
	eor #$FF
	clc
	adc #1
	sta SleepTime
	lda SleepTime+1
	eor #$FF
	adc #0
	sta SleepTime+1
	lda SleepTime+2
	eor #$FF
	adc #0
	sta SleepTime+2
	
	ldx Process.WakeUpHead ; get head of wake-up queue
	bne PendingWakeUps

	ldy Process.Active ; if the queue is empty, just set our sleep time
	lda SleepTime
	sta Process.WakeUpLo,y
	lda SleepTime+1
	sta Process.WakeUpMid,y
	lda SleepTime+2
	sta Process.WakeUpHi,y
	lda #0
	sta Process.NextWakeUp,y
	sta Process.PrevWakeUp,y
	sty Process.WakeUpHead
	rts
		
PendingWakeUps
	lda #0
	sta SleepTotal
	sta SleepTotal+1
	sta SleepTotal+2
	
Loop ; find place in wake-up list for this task
	lda Process.WakeUpLo,x
	clc
	adc SleepTotal
	sta NextWakeUp
	lda Process.WakeUpMid,x
	adc SleepTotal+1
	sta NextWakeUp+1
	lda Process.WakeUpHi,x
	adc SleepTotal+2
	sta NextWakeUp+2
	
	cmp SleepTime+2
	bcc Here
	lda NextWakeUp+1
	cmp SleepTime+1
	bcc Here
	lda NextWakeUp
	cmp SleepTime
	bcc Here

	lda NextWakeUp
	sta SleepTotal
	lda NextWakeUp+1
	sta SleepTotal+1
	lda NextWakeUp+2
	sta SleepTotal+2
	
	lda Process.NextWakeUp,x
	beq ListEnd
	tax
	bne Loop

ListEnd ; we reached the end of the list
	jsr SetProcessSleepTime
	txa ; get old end of list
	sta Process.PrevWakeUp,y
	tya
	sta Process.NextWakeUp,x
	lda #0
	sta Process.NextWakeUp,y
	rts
		

Here ; we found place in wake-up list
	lda NextWakeUp ; first, figure out ticks following our wake-up till next task's wake-up
	sec
	sbc SleepTime
	sta Process.WakeUpLo,x
	lda NextWakeUp+1
	sbc SleepTime+1
	sta Process.WakeUpMid,x
	lda NextWakeUp+2
	sbc SleepTime+2
	sta Process.WakeUpHi,x
	
	jsr SetProcessSleepTime ; now set our sleep time

	txa
	sta Process.NextWakeUp,y
	lda Process.PrevWakeUp,x
	sta Process.PrevWakeUp,y
	pha
	tya
	sta Process.PrevWakeUp,x
	pla
	beq AtHead
	tay
	txa
	sta Process.NextWakeUp,y
	bne @+
AtHead
	sty Process.WakeUpHead
@
	rts
	.endl
	
; ---------------------------------------------------------------------------------
; Set task's wake-up counter
; ---------------------------------------------------------------------------------
	
	.local SetProcessSleepTime
	ldy Process.Active
	lda SleepTime
	sec
	sbc SleepTotal
	sta Process.WakeUpLo,y
	lda SleepTime+1
	sbc SleepTotal+1
	sta Process.WakeUpMid,y
	lda SleepTime+2
	sbc SleepTotal+2
	sta Process.WakeUpHi,y
	rts
	.endl
	
	
; ---------------------------------------------------------------------------------
; Remove a sleeping process from the wake-up list
; Pass IPID in Y
; Remaining sleep ticks for this task are added to the next one in the queue
; ---------------------------------------------------------------------------------
	
	.local WakeProcess
	ldx Process.NextWakeUp,y
	beq LastSleepingTask
	lda Process.WakeUpLo,y
	clc
	adc Process.WakeUpLo,x
	sta Process.WakeUpLo,x
	lda Process.WakeUpMid,y
	adc Process.WakeUpMid,x
	sta Process.WakeUpMid,x
	lda Process.WakeUpHi,y
	adc Process.WakeUpHi,x
	sta Process.WakeUpHi,x	
LastSleepingTask
	lda #0
	sta Process.WakeUpFlag,y
	ldx Process.PrevWakeUp,y ; is this the first task in the wake-up queue?
	bne NotHead
	ldx Process.NextWakeUp,y ; if it is, point the head to the next sleeping task
	stx Process.WakeUpHead ; set head pointer
	bne NotTail
	rts
NotHead
	lda Process.NextWakeUp,y ; not at head, so set previous node's next pointer to our next pointer
	sta Process.NextWakeUp,x
	ldx Process.NextWakeUp,y ; is this the last task in the queue?
	beq IsTail
NotTail
	lda Process.PrevWakeUp,y ; not queue tail, so get our prev pointer
	sta Process.PrevWakeUp,x ; and store in next node's prev pointer
IsTail
	rts
	.endl
	
	

; ---------------------------------------------------------------------------------
; When global wake-up counter hits zero, wake up all tasks whose sleep
; counters are zero
; ---------------------------------------------------------------------------------

	.local WakeUpSleepTimers
Loop
	ldy Process.WakeUpHead
	beq Done
	lda Process.WakeUpLo,y
	ora Process.WakeUpMid,y
	ora Process.WakeUpHi,y
	bne Done
	jsr WakeProcess.LastSleepingTask ; ensure we skip update of subsequent task timers in the queue
	mva #ProcessState.Ready Process.State,y
	mva #0 Process.WakeUpFlag,y
	jmp Loop
Done
	rts
	.endl

NMI code is simplified to:

	ldx Process.WakeUpHead ; see if we need to wake up any sleeping processes
	beq NoWakeUps

	inc Process.WakeUpLo,x
	bne NoWakeUps
	inc Process.WakeUpMid,x
	bne NoWakeUps
	inc Process.WakeUpHi,x
	bne NoWakeUps
	jsr WakeUpSleepTimers ; wake up all the tasks whose sleep timers have reached zero
NoWakeUps

Got the task manager and another test process both sleeping for different durations, and they both wake up and return zero counters. Apart from conversion to 2s complement, all I did was change the comparison branch in Sleep.Loop from BCS to BCC when searching through the wake-up list, since we're now looking for an accumulated total which is lower than the wake-up counter of the process being put to sleep. That means equality is no longer tested, but I guess in that case the new entry will just end up last in a group of equal timers, which should make no difference.

Edited by flashjazzcat
  • Like 1
Link to comment
Share on other sites

Well: the above does work fine. Dragging my heels over the demo a little, mainly because I'm running out of steam on this stint (since May?), and partly because I wanted to get at least a few more things working. The desktop redraw bug I fixed a while back plagued every build of the GUI since I rewrote the window manager, so now it's rectified everything seems to redraw faster than I remember. There's still a lot of old window manager code which needs bringing into line with the new application layering, etc, and I need to sit down and do some rewrites on that (likely after something's released: I'll just bodge it for now). I just got the contextual application menu bar working with a rather perfunctory secondary application:

 

http://youtu.be/5CtUmRk44as

 

I'll put something more meaningful in a couple of extra windows, allow the applications to be shut down and restarted, then folks can have a play. And yes: unfocused window drag bars will eventually show that they're inactive (again).

Edited by flashjazzcat
  • Like 10
Link to comment
Share on other sites

Well: the above does work fine. Dragging my heels over the demo a little, mainly because I'm running out of steam on this stint (since May?), and partly because I wanted to get at least a few more things working. The desktop redraw bug I fixed a while back plagued every build of the GUI since I rewrote the window manager, so now it's rectified everything seems to redraw faster than I remember. There's still a lot of old window manager code which needs bringing into line with the new application layering, etc, and I need to sit down and do some rewrites on that (likely after something's released: I'll just bodge it for now). I just got the contextual application menu bar working with a rather perfunctory secondary application:

 

http://youtu.be/5CtUmRk44as

 

I'll put something more meaningful in a couple of extra windows, allow the applications to be shut down and restarted, then folks can have a play. And yes: unfocused window drag bars will eventually show that they're inactive (again).

 

Is that the new high-performance invisible font I sent you the other day that you're displaying? :D

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   1 member

×
×
  • Create New...