# Środowisko uruchomieniowe dla efektów
## Jak odizolować efekty od biblioteki?
Biblioteka ma być częścią loadera dostępną dla wszystkich efektów. Ma udostępniać:
* szereg funkcji, które muszą być częścią loadera lub są na tyle często używane, że nie opłaca się ich trzymać w każdym efekcie,
* możliwość komunikacji między efektami na wypadek, gdyby efekty chciały przekazywać sobie stan.
## Przygotowanie efektu
Po załadowaniu efektu do pamięci _loader_ powinien jakoś dostać wskaźnik na jego `struct Effect`. Wydaje się, że najłatwiej to osiągnąć przygotowując odpowiednie `crt0.S` dla efektu.
Innymi słowy zakładamy, że pierwszy segment pliku wykonywalnego efektu to `.text` (kod). `crt0.o` jest zawsze wrzucany na początek tej sekcji co widać w [effect.mk](https://github.com/cahirwpz/demoscene/blob/master/build/effect.mk#L38). Tam można wkleić taki kawałek kodu:
```
move.l #_Effect,d0
rts
```
Co powoduje, że _loader_ po wywołaniu tego kodu uzyska dostęp do funkcji efektu zdefiniowanych w `struct Effect`, na przykład w taki sposób:
```c
EFFECT(anim_polygons, Load, UnLoad, Init, Kill, Render);
```
## Wywołania funkcji _loadera_
IMHO nie ma się co siłować. Biblioteki _AmigaOS_ pokazują jak to łatwo osiągnąć na `m68k`. Przykład poniżej:
```c=
extern struct ExecBase *const SysBase;
#define EXEC_BASE_NAME SysBase
#define LP2(offs, rt, name, t1, v1, r1, t2, v2, r2, bt, bn) \
({ \
t1 _##name##_v1 = (v1); \
t2 _##name##_v2 = (v2); \
rt _##name##_re2 = \
({ \
register int _d1 __asm("d1"); \
register int _a0 __asm("a0"); \
register int _a1 __asm("a1"); \
register rt _##name##_re __asm("d0"); \
register void *const _##name##_bn __asm("a6") = (bn); \
register t1 _n1 __asm(#r1) = _##name##_v1; \
register t2 _n2 __asm(#r2) = _##name##_v2; \
__asm volatile ("jsr a6@(-"#offs":W)" \
: "=r" (_##name##_re), "=r" (_d1), "=r" (_a0), "=r" (_a1) \
: "r" (_##name##_bn), "rf"(_n1), "rf"(_n2) \
: "cc", "memory"); \
_##name##_re; \
}); \
_##name##_re2; \
})
#define AllocMem(___byteSize, ___requirements) \
LP2(0xc6, APTR, AllocMem, \
ULONG, ___byteSize, d0, \
ULONG, ___requirements, d1, \
, EXEC_BASE_NAME)
```
Nie ma sensu bawić się z przeniesionym wektorem przerwań – dzięki rejestrowi VBR począwszy od 68010. Wpisy w wektorze od 48 wzwyż są zarezerwowane dla użytkownika, więc można tam upchnąć dowolne dane. W związku z tym `SysBase` można zakodować jako skoki bezpośrednie w wektorze przerwań (po 6 bajtów na każdy). Wtedy `jsr a6@(offset)` zamieni się na `jsr offset.w`.
Zamiast `bsr foobar` (18 cykli), będzie para `jsr offset.w; jmp foobar.l` (18 + 12 cykli). Czyli pośrednie skoki wprowadzą trochę opóźnienia, ale nie jest źle.
## Interfejs loadera
```c=
struct API {
/* memory.h */
void (*MemCheck)(int verbose);
u_int (*MemAvail)(u_int attributes);
void *(*MemAlloc)(u_int size, u_int attributes);
void *(*MemResize)(void *ptr, u_int size);
void (*MemFree)(void *ptr);
/* interrupt.h */
void SetIntVector(u_short irq, IntHandlerT code, void *data);
void AddIntServer(u_short irq, IntServerT *is);
void RemIntServer(u_short irq, IntServerT *is);
/* file.h */
int FileRead(FileT *f, void *buf, u_int nbyte);
int FileWrite(FileT *f, const void *buf, u_int nbyte);
int FileSeek(FileT *f, int offset, int whence);
void FileClose(FileT *f);
void FilePutChar(FileT *f, char c);
int FileGetChar(FileT *f);
void FilePrintf(FileT *f, const char *fmt, ...);
FileT *SerialOpen(u_int baud, u_int flags);
/* timer.h */
CIATimerT *AcquireTimer(u_int num);
void ReleaseTimer(CIATimerT *timer);
void SetupTimer(CIATimerT *timer, CIATimeoutT timeout, u_short delay, u_short flags);
void WaitTimerGeneric(CIATimerT *timer, u_short ticks, bool spin);
/* debug.h */
void Log(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
__noreturn void Panic(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
/* keyboard.h */
void (*KeyboardInit)(void);
void (*KeyboardKill)(void);
/* mouse.h */
void (*MouseInit)(Area2D *area);
void (*MouseKill)(void);
/* event.h */
void (*PushEventISR)(EventT *event);
void (*PushEvent)(EventT *event);
bool (*PopEvent)(EventT *event);
/* effect.h */
void (*TaskWaitVBlank)(void);
void (*_ProfilerStart)(ProfileT *prof);
void (*_ProfilerStop)(ProfileT *prof);
};
```
## Środki synchronizacji
Te które mam chyba nie dadzą rady.
### Semafory
```c=
typedef struct Waiter {
TAILQ_ENTRY() node;
TaskT *task;
bool up;
} WaiterT;
typedef struct Semaphore {
u_int count;
TAILQ_ENTRY(Waiter) waiters;
} SemaphoreT;
void SemUp(SemaphoreT *sem)
{
/* wyłączyć przerwania */
if (TAILQ_EMPTY(&sem->waitList)) {
sem->count++;
} else {
SemWaiterT *waiter = TAILQ_FIRST(&sem->waitList);
TAILQ_REMOVE(&waiter->list);
waiter->up = true;
WakeupTask(waiter->task);
}
/* włączyć przerwania */
}
void SemDown(SemaphoreT *sem) {
/* wyłączyć przerwania */
if (sem->count > 0) {
sem->count--;
} else {
SemWaiterT waiter;
TAILQ_INSERT_TAIL(&sem->waiters, &waiter);
waiter.task = current;
waiter.up = false;
while (!waiter.up) {
TaskSuspend();
}
}
/* włączyć przerwania */
}
```