;здесь нет включения-выключения системной паги (надо при вызове всех системных функций)
;не реализован sleep до таймера (делать руками в программе?)

STRUCT app
BYTE priority ;приоритет (0=конец списка)
BYTE flags ;флаги (factive, ftimeslice, fusetimer)
factive=0 ;есть сообщения: SET при добавлении сообщения, RES при взятии последнего сообщения
ftimeslice=1
fusetimer=2
;ftimer=3
;fcritical=4 (чтобы не портить hl)
WORD id ;номер задачи
WORD mainpg ;главная страница задачи (там очередь сообщений)
WORD curmsg ;адрес текущего сообщения этой задаче
WORD endmsg ;адрес конца очереди сообщений этой задаче
WORD curpg ;текущая страница
WORD sp ;текущий адрес стека (можно хранить в ТЕКУЩЕЙ (не главной) странице, но это бессмысленно)
WORD next ;указатель на следущую задачу (следующая за выполняемой внутри того же приоритета)
;[номер задачи, которая ждёт готовности этой задачи]
ENDSTRUCT

STRUCT msg
WORD type ;тип сообщения (2 б)
WORD sender ;автор сообщения (2 б)
WORD par0
WORD par1
WORD par2
WORD par3
WORD par4
WORD par5
ENDSTRUCT
;16 байт (делитель числа 256)

WORD Os_curapp ;()=Os_Tapp
WORD Os_nextapp ;()=Os_Tapp
WORD Isr_SP
WORD Os_frames
BYTE Os_critical
WORD Os_targetid
ARRAY Os_msg1,SIZEOF_msg
ARRAY Os_msg2,SIZEOF_msg
Os_TIMERSENDER=55555

MACRO STARTCRITICAL
ld hl,Os_critical
inc (hl) ;с этого момента уже не задача, а шедулер
ENDM

MACRO ENDCRITICAL
ld hl,Os_critical
dec (hl) ;выключение+проверка прерванности ОДНОВРЕМЕННО
call nz,IsrEndCritical ;переход на IsrEndCritical не прервется, т.к. в этом случае di
;тут уже можно считать, что выполняется задача
ENDM

;выход из контекста задачи
MACRO RELEASECONTEXT
ld ix,(Os_curapp)
ld hl,(curpg)
PUTWORD app_curpg,HL
ld hl,0
add hl,sp
PUTWORD app_sp,HL
ld sp,lowmem
ENDM

;вход в контекст задачи
MACRO GETCONTEXT
GETWORD HL,app_sp
push bc,hl
GETWORD HL,app_curpg
SETPG
pop hl,bc
ld sp,hl
ENDM

MACRO SETFLAG
SET \0,(IX+app_flags)
ENDM

MACRO RESFLAG
RES \0,(IX+app_flags)
ENDM

MACRO TESTFLAG
BIT \0,(IX+app_flags)
ENDM


;шлет событие клавиатуре, гую, мыши, плееру (всем, кто подписан на таймер)
;подписанность на таймер - флажок fusetimer (как программа его установит?)
;страница на выходе должна должна остаться прежней!
;просто поставить флаг ftimer
;[вариант:вызвать обработчик таймера у конкретного потока (если задан адрес)]
Timer
ld ix,Os_Tapp-SIZEOF_app
ld de,SIZEOF_app
Timer0
add ix,de
ld a,(ix+app_priority)
or a
ret z
TESTFLAG fusetimer
jz Timer0
SETFLAG ftimer
jr Timer0



;делает то же, что таймслайс
Sleep
STARTCRITICAL
ld ix,(Os_curapp)
SETFLAG ftimeslice ;в контексте задачи
call Sched ;переключить задачу (стек и т.п.)
;ld ix,(Os_curapp) ;есть в Sched?
RESFLAG ftimeslice ;в контексте задачи
ENDCRITICAL
;выход из таймслайса
ret

IsrEndCritical
;прерывания отключены
;можно портить только AF,HL
dec (hl) ;1-1=0
push ...
call Isr
pop ...
;прерывания включены
ret

Isr
;регистры можно портить, но страница должна быть включена та же
ld (Isr_SP),sp
ld sp,lowmem
;определяем тип прерывания и работаем с ним (портит списки задач и сообщений)
;Если не определилось RS232, то экранное.
;Если определилось RS232, читать ВЕСЬ буфер (т.к. могло потеряться прерывание от RS232).
;Если RS232 произошло чуть позже экранного, то оно теряется.
;Если экранное произошло чуть позже RS232, то оно теряется (с этим ничего не поделать)
;fcritical=0, т.е. можно вызывать SENDMSG (но нельзя DOMSG)
call Timer
;
ld sp,(Isr_SP)
ei
ret

;случай прерванной критической секции
;сразу возврат, а перед выходом из критической секции вызвать IsrEndCritical
CriticalInt
;прерывания выключены
inc a ;=2
ld (Os_critical),a
;pop hl
pop af
ret ;в шедулер

OnInt
push af
;push hl
;todo внутри timer
;ld hl,(Os_frames)
;inc hl
;ld (Os_frames),hl
ld a,(Os_critical)
or a
jnz CriticalInt
push ...
CALL Isr ;делает ei
;включен контекст задачи, ее стек и ее страница, т.е. якобы она выполняется
call Sleep
pop ...
;pop hl
pop af
ret

GetMsg
STARTCRITICAL
call Sched
;exx
;todo при вызове Sched регистры класть сюда, а брать их на выходе (а не здесь)?
ld bc,(Os_msg1+msg_par3)
ld de,(Os_msg1+msg_par4)
ld hl,(Os_msg1+msg_par5)
exx
ld bc,(Os_msg1+msg_par0)
ld de,(Os_msg1+msg_par1)
ld iy,(Os_msg1+msg_par2)
;парамеры сообщения в hl',de',bc',iy,de,bc
ld hl,(Os_msg1+msg_type) ;обязательно в критической секции!
ld ix,(Os_msg1+msg_sender) ;
push hl
ENDCRITICAL
pop hl
ret

;парамеры сообщения взять в bc,de,iy,bc',de',hl'
MsgGetPars
GETWORD HL,app_mainpg
SETPG
GETWORD HL,app_curmsg
ld de,Os_msg1
ld bc,15
ldir
ld a,(hl)
ld (de),a
inc l
PUTWORD app_curmsg,HL
GETWORD DE,app_endmsg
or a
sbc hl,de
ret nz ;не последнее сообщение в очереди
RESFLAG factive ;последнее
ret

;переход на задачу с ID=bc
CallTask
STARTCRITICAL
RELEASECONTEXT ;портит hl,ix
ld ix,(Os_curapp)
SETFLAG ftimeslice ;в критической секции, а то вдруг до STARTCRITICAL выпадет прерывание, которое завалит задачу в таймслайс и на следующем входе в неё СНИМЕТ этот флаг
;поиск задачи (если нету такой, то любую)
ld ix,Os_Tapp-SIZEOF_app
ld de,SIZEOF_app
SchedGivenSearch
add ix,de
ld a,(ix+app_priority)
or a
jz SchedSearch ;нету такой задачи - переходим на любую
GETWORD HL,app_id
;CY=0
sbc hl,bc
jnz SchedGivenSearch
;не следует искать Os_nextapp, т.к. мы рвём последовательность выполнения задач только для одного вызова
jr SchedFound

Sched
RELEASECONTEXT ;портит hl,ix
SchedSearch
;call SchedFind ;выбираем задачу - результат в ix
;ищем первую активную/таймслайсную задачу от начала списка задач
;активная - либо в таймслайсе, либо есть сообщения, либо таймер
;SchedFind
LOCAL
ld ix,Os_Tapp-SIZEOF_app
ld de,(Os_nextapp) ;"следующая внутри приоритета"
ld bc,SIZEOF_app
_next
add ix,bc
ld a,(ix+app_flags)
and factive|ftimeslice|ftimer
jz _next ;имеет тот же приоритет, что "следующая внутри приоритета", но расположена раньше - пропускаем
ld a,(de) ;приоритет текущей/следующей внутри приоритета
cp (ix+app_priority)
jnz _ok
push ix
pop hl
;or a ;CY=0
sbc hl,de
jc _next ;расположена раньше внутри приоритета - пропускаем
_ok
GETWORD HL,app_next
ld (Os_nextapp),hl ;"следующая внутри приоритета"
ENDL
;ret
SchedFound
TESTFLAG ftimeslice
jnz SchedOk ;таймслайс - просто переходим на задачу
TESTFLAG ftimer
jz SchedGetPars ;если таймер был, обслуживаем его в первую очередь
;nz
RESFLAG ftimer
ld hl,Os_TIMERSENDER
ld (Os_msg1+msg_sender),hl ;псевдосообщение, важен только sender
;jr SchedOk
SchedGetPars
call z,MsgGetPars
SchedOk
ld (Os_curapp),ix
GETCONTEXT ;портит hl,нужен ix
ret ;задача


;CY=адресат не найден
SendMsg
;ix - ID адресата
;hl - тип сообщения
;bc,de,iy,bc',de',hl' - параметры
;Если при посылке сообщения очередь превысила лимит, то ставим текущую задачу в таймслайс и переход на задачу, которой шлём (если она не в таймслайсе)
;а если она в таймслайсе - крутимся, пока не будет готова, или до таймаута
push hl
STARTCRITICAL
pop hl
;обязательно в критической секции!
ld (Os_targetid),ix ;ID адресата
ld (Os_msg2+msg_type),hl
GETWORD HL,app_id
ld (Os_msg2+msg_sender),hl
ld (Os_msg2+msg_par0),bc
ld (Os_msg2+msg_par1),de
ld (Os_msg2+msg_par2),iy
exx
ld (Os_msg2+msg_par3),bc
ld (Os_msg2+msg_par4),de
ld (Os_msg2+msg_par5),hl
;exx
RELEASECONTEXT ;портит hl,ix
;поиск адресата (если нету, то выход)
ld bc,(Os_targetid) ;ID адресата
ld ix,Os_Tapp-SIZEOF_app
ld de,SIZEOF_app
SendMsgSearch
add ix,de
ld a,(ix+app_priority)
or a
jz SendMsgError ;конец списка задач - не найдена нужная
GETWORD HL,app_id
;CY=0
sbc hl,bc
jnz SendMsgSearch
;включаем главную страницу адресата
GETWORD HL,app_mainpg
SETPG
SendMsgLoop
;есть место в очереди?
GETWORD HL,app_curmsg
GETWORD DE,app_endmsg
or a
sbc hl,de
jz SendMsgNoRoom
;есть место {
;шлем сообщение
ld hl,Os_msg2
ld bc,15
ldir
ld a,(hl)
ld (de),a
inc e
PUTWORD app_endmsg,DE
ld ix,(Os_curapp)
GETCONTEXT
ENDCRITICAL
or a ;CY=0
ret ;отправитель
SendMsgError
ld ix,(Os_curapp)
GETCONTEXT
ENDCRITICAL
scf ;CY=1
ret ;отправитель
;}
SendMsgNoRoom
;de=curmsg=endmsg
;нет места {
TESTBIT ftimeslice ;адресат в таймслайсе?
jnz SendMsgTimeSlice
;не в таймслайсе {
;берем первое сообщение
push de
exd
ld de,Os_msg1
ld bc,15
ldir
ld a,(hl)
ld (de),a
inc l
PUTWORD app_curmsg,HL
;кладём наше сообщение
pop de
ld hl,Os_msg2
ld bc,15
ldir
ld a,(hl)
ld (de),a
inc e
PUTWORD app_endmsg,DE
;переход на адресат:
ld (Os_curapp),ix
;ищем "следующую внутри приоритета", запоминаем ее
GETWORD HL,app_next
ld (Os_nextapp),hl
;exx
ld bc,(Os_msg1+msg_par3)
ld de,(Os_msg1+msg_par4)
ld hl,(Os_msg1+msg_par5)
exx
ld bc,(Os_msg1+msg_par0)
ld de,(Os_msg1+msg_par1)
ld iy,(Os_msg1+msg_par2)
GETCONTEXT ;портит hl,нужен ix
ld hl,(Os_msg1+msg_type) ;обязательно в критической секции!
ld ix,(Os_msg1+msg_sender) ;
push hl
ENDCRITICAL
pop hl
ret ;адресат
;}
SendMsgTimeSlice
;в таймслайсе {
;берем контекст отправителя и входные регистры
;exx
ld bc,(Os_msg2+msg_par3)
ld de,(Os_msg2+msg_par4)
ld hl,(Os_msg2+msg_par5)
exx
ld bc,(Os_msg2+msg_par0)
ld de,(Os_msg2+msg_par1)
ld iy,(Os_msg2+msg_par2)
ld ix,(Os_curapp)
GETCONTEXT ;портит hl,нужен ix
ld hl,(Os_msg2+msg_type) ;обязательно в критической секции!
ld ix,(Os_targetid) ;
push hl
ENDCRITICAL
pop hl
;переключаем задачу
;желательно включить адресат
;иначе если адресат имеет приоритет ниже приоритета отправителя, то эта задача всегда будет включена снова раньше, чем хоть раз вызовется адресат - будет вечный цикл
;повиснет, если адресат сам ждет задачу, приоритет которой ниже, чем приоритет отправителя!!!
;поднять приоритет адресата до уровня отправителя?
push af
push hl
push ...
push ix
pop bc ;ID адресата
call CallTask ;переключиться на адресат (если не существует, то на любую задачу)
;выход из таймслайса
pop ...
pop hl
pop af
jp SendMsg ;новая попытка
;}
;}

