Notes

Notes et pensées qui ne trouvent pas leur place ailleurs.

Factorisation édition champ/ligne.

Il y a 3 routines pour rentrer du texte, chacune avec ces qualités.

  • Éditeur : saisie lignes
    • Utilise un système de table plus compacte qu'une série de CP/JR.
  • Éditeur : saisie nom de fichier
    • Permet de partir d'un nom pré-rempli.
  • Monogams : saisie commandes
    • Réaffichage complet du tampon à chaque action, puis on place le curseur nous-même.

Cette dernière manière de faire est bonne :

  • code est court, simple, robuste.
  • plus besoin de distinguer les cas 'insertion'/'délétion'/…
  • simplifie aussi la gestion curseur (évite de jongler avec le système, introduisant moult incohérences dès lors qu'on utilise nos propres routines d'affichage).
  • ne ralentit en rien (afficher 80 caractères prend moins de 100 lignes raster. Donc on a largement le temps, même avec plein de rasters).

L'idée est de tout fusionner en une routine réutilisable (ça me servirait pour le tracker de BSC).

best_field_editor_ever
----------------------

In : HL = NT text buffer.
          !!WARNING!! for empty string, set first byte to 0.
     IX = I/O dispatch address, see below
     IY = custom client context (not used by routine, but passed to I/O dispatch)
     D bit 7 = Pre-selection 
         0 : No selection (i.e. default insertion mode)
         1 : Text is pre-selected (i.e. any key other than cursor keys erase current text)
     D bit 6-0 = Field length in screen. If 0, will use B+1
     E = String offset. Position in string from witch display begins.
     B = Max string length.   
     C = Initial cursor position in string (0 = First char, FF = Past last char).

Preconditions (to be checked by client) :
    * B >= len      (initial string cannot be longer than indicated max length)
    * C <= len      (cursor must be in string, or just after it)
    * E <= C < E+D  (cursor must be in field)
    * E <  len      (display cannot begin after string's end)
    where len is initial string length (not counting byte 0).

Out : Returns anyway. All registers have the same meaning than at input. E.g. :
      HL, IX, IY, D[0:6], B  preserved
      D[7] (possibly new) flag 'text selected'
      E    (possibly new) string offset (may have scrolled if B>D)
      C    (possibly new) cursor position in string

      Carry & Z  Nothing processed, because no key pressed.
               A destroyed. 
      Carry & NZ Key processed.
               A = keycode. D[7], E and C updated.
               Text buffer updated.
      NC & Z    Key not processed, cause unhandled special key (ESC, CONTROL-?, ...).
               A = keycode.
      NC & NZ   Key not processed, cause invalid action (e.g. buffer full, or DEL at pos 0).

      So, in case of Carry, no special action have to be taken
io_dispatch
-----------

   If A=0    io_read_key
   If A=1    io_print_cursor
   If A>=&10 io_print_char

io_read_key  (like km_test_key &BB1B)
-----------

 Tests whether a key is available from the keyboard.

In: N/A
Out: If a key is available, then Carry is true, and A contains the character.
     Otherwise Carry is false, and A is corrupt.
     In either case, the other registers are preserved.

io_print_cursor
--------------- 

 Transparently (no overwrite) display cursor at OFFSET + E, where OFFSET is an implicitly defined position

In: E = x position (0 is leftmost)
Out: All registers preserved except AF.

io_print_char
-------------

 Print char at OFFSET + E, possibly in video inverse, where OFFSET is an implicitly defined position

In: A = char to print. Use inverse video if bit 7 is set.
    E = x position (0 is leftmost)
Out: All registers preserved except AF.
Typically, client will use it like that :

   ld ix,iodispatch
   ld iy,&c0a0         ; screen offset
   ld hl,filename
   ld de,0
   ld bc,filename_maxlen*&100 + &ff
edit_loop
   call best_field_editor_ever
; animation possible ici
   jr c,edit_loop
; gère special key (exit, valid, ...)
   [...]
   jr edit_loop

io_dispatch
    or a
    jp z,km_read_key  ; &bb1b
    cp 1
    jr nz,io_print_char

io_print_cursor  ; Put underscore

    push hl
    push de
    push iy:pop hl  ; HL= screen offset
    ld d,&38        ; last line
    add hl,de       ; HL= cursor position
    ld  a,(hl)
    cpl
    ld (hl),a
    pop de
    pop hl
    ret

io_print_char   ; left as exercice

Motivation :

  • case D=0 correspond to classic case, when field width in screen is equal to max string length + 1 (for cursor position after last char).
  • C à FF permet de se positionner après une chaine pré-remplie (cf CONTROL-S de l'éditeur).
  • Le custom key fetcher permet de ré-utiliser la routine dans différents contextes :
    • Avec test de touche système
    • Avec gestion clavier faite maison
    • Avec filtre (e.g. saisie champ numérique : on inhibe toutes autres touches que chiffres)
    • Pour les tests unitaires (une routine ad-hoc envoie une séquence de touches prédéterminées, et on peut vérifier que le tampon est rempli comme attendu).
  • La routine n'est pas bloquante (utilise km_read_key plutôt que km_test_key), afin de pouvoir l'entrelacer facilement avec des "tâches de fond".
  • La routine renvoie la touche, afin que l'appelant décide ensuite comment interpréter ESC, RETURN, etc…
  • Le flag "selection" permet de reproduire le comportement de saisie label/nom de fichier.

Factorisation des routines d'affichage : plan de route. (They have an agenda !)

Phase 0

Le code appelant ne doit plus manipuler lui-même curseur ou toute autre variable relative à l'affichage. Tout remplacer par des CALLs vers des routines de même préfixe.
Eg :

  • disp_inline : affiche texte placée après le call
  • disp_nl : retour à la ligne.

Cela permet de détourner facilement le pack de routines.

Phase 1

Pour les bouts de textes tenant sur une ligne, remplacer :

 ld hl,txt_wrong_parameters:call disp_text_nl

Par A/ (si la chaîne n'est utilisée qu'une fois) :

 call disp_inline_nl:BYTE "Wrong parameters.",0

Ou B/

 call disp_message_nl:byte msg_wrong_parameters

Les deux solutions économisent la place.
La A/ économise en outre un label, et évite un déplacement pour aller voir le contenu de la chaîne.
La B/ permet de regrouper les messages dans une même ROM et autorise leur compression.

Phase 2 (Plus tard)

Utiliser échappement et "interpolation" pour générer la chaîne à afficher.

E.g. "Start: ",txt_esc,txt_hl_hexa," youpi", 0 affichera "Start: #BABE youpi" si hl contient #BABE.

Système sous tuteur [c'est fait, pièges compris. Voir détails paragraphe suivant]

Afin de rendre le moniteur le plus complet possible, la présence du système est souhaitable (accès aux RSX, chargement/sauvegarde, …) : point de vue "Hackeur".
Cela va à l'encontre d'un autre impératif : rendre le moniteur le moins intrusif possible, permettant d'étudier toute la mémoire telle que rendue par le programme : point de vue "Multiface".

Afin de concilier ces desiderata, deux solutions :

  • installer le système en bank supplémentaire (C2, ou plutôt D2, E2, …).
    • peu pratique, car les sorties écran atterriraient en bank (à moins de détourner BDD3-TXT WRITE CHAR et amis).
  • installer le système en mémoire de base, après avoir sauvegardé la zone correspondante (en gros 00-3A et A000-BFFF). Lors d'un dump en B000 par exemple, le moniteur irait chercher dans ce back-up. Cette acrobatie est similaire à la lecture ROM / BANK, mais peut se révéler plus piégeur.

Mécanisme miroir firmware.

On distingue quatre pages #8000 (en fait, concerne seulement la zone #9800-#BFFF, comme approximation économe de la mémoire "système" à réinitialiser, en comptant les espaces de travail réservés par les ROMs, plus la zone 00-3F).

  • Page avant exécution du programme (I comme Initial) qui sera restaurée.
  • Page de travail pour Orgams (O comme Orgams)
  • Page lors de l'exécution du programme (E comme Exec)
  • La bank qui sert de copie (B)

La page O doit pour l'instant contenir le firmware, car l'éditeur l'utilise, ne serait-ce que pour les accès disc.
En l'état actuel, page I et O sont confondues. Avant exécution (Jump), elle est copiée en B. Au retour, elle est swappée avec B, de sorte que le moniteur puisse l'analyser comme si de rien n'était.
Premier problème (bug #63) : pour l'instant, l'assemblage n'est pas dérouté dans la page E.
Ce qu'il faut faire :

  • Copier avant assemblage I vers B (si la zone n'est pas touchée, on la retrouvera telle quelle)
  • Assembler en déroutant vers B
  • Swapper (O et B)
  • Exécuter
  • Au retour, Swapper (O et B)

Pourquoi une copie ?

La zone 'système' est copiée en bank plutôt que restaurée via le firmware.

  • Avantages
    • Beaucoup plus rapide (cf latence de certaines ROMs qui font des checksums)
    • Pas besoin de hacker pour empêcher l'affichage du boot et des roms
    • Il s'agit de l'état tel que défini par l'utilisateur avant l'appel d'Orgams. Permet patches.

Inconvénients

* Plus gourmand en mémoire
* Moins robuste
* Ce point peut être allégé via un checksum (cf todo #29)

A venir

On compte se passer de plus en plus du firmware, et exploiter la page #8000 :

  • Pour affichage supplémentaire
  • Pour routines pivot (pour l'instant, réelles acrobaties dans le switch de bank, car il était prévu de pouvoir mettre le pile en BANK - cela reste nécessaire pour la rapidité de la trace).

Cela signifie de distinguer page I et O. [Edit: en fait non, voir point suivant]

Modèle revu pour version 'Debugging Deligths'

(notes perso pour modifications à venir)

On re-précise les notations :

  • I: zone HIMEM-&BFFF avant exécution du programme (zone firmware)
  • O: zone &8000-&9800 utilisée comme zone de travail par Orgams.
  • E: zone &8000-&BFFF lors de l'exécution du programme.
  • M: page &8000-&BFFF en mémoire centrale.
  • B: bank qui sert de backup

Sous l'éditeur et Monogams, O+I sont "connectées" en M, tandis que lors de l'exécution, c'est E qui se voit connecté.

Flux:

  • |O: on copie &8000-&9800 de M vers B, de sorte que la zone 'utilisateur' est préservée, et Orgams peut utiliser cette plage pour lui-même (O).
  • Assemblage :
    • On copie I de M vers B. On 'reset' donc l'état mémoire sur cette plage. C'est le but recherché : l'idée est qu'à chaque assemblage, la zone firmware soit valide (grosso-modo, telle que lors du premier assemblage).
    • On assemble (les données assemblées en page &8000 sont déroutées vers B, ce qui permet d'assembler sur toute la page &8000-&BFFF, sans conflit avec le firmware ou Orgams).
    • On coupe le système ('DI')
    • On swape B et M, de sorte que le firmware et les données Orgams sont backupées, tandis que les données assemblées sont bien replacée.
    • On saute au programme.
  • Retour à Orgams :
    • On swape B et M, de sorte que le firmware et les données Orgams sont restaurées, tandis que E reste observable de façon transparente (e.g. si le programme a créée une table en &BC00, on peut la lire par un simple m&BC00.)

Cas particulier de la trace.

ASIS:
Pour assurer la rapidité de la trace (mode rapide, N), E est connecté en M, et la pile n'est pas utilisable.
Pour la partie UI, on reconnecte la bank de travail et la pile se trouve en &8000.

TOBE:
Au retour de l'émulation Z80, on se libère une zone de travail de &8000 à &87ff (le swap prend 8*48 lignes rasters, soit ~1.23 frame, ce qui est considéré comme négligeable devant le temps pris pour le rafraîchissement écran).
En outre, pas de swap à faire pour la navigation (désassemblage).

Flux:

  • Passage Monogams -> Trace (commmande d):
    • on swap &8800-&bfff entre B et M ("connection" E en M, mis à part la zone travail spécial trace)
    • pile en &8800
  • Pour chaque commande de trace :
    • swap &8000-&87ff entre B et M (connecte complétement E)
    • pile en &8000
    • appel émulation Z80
    • swap &8000-&87ff entre B et M (rétabli zone travail)
    • pile en &8800
    • appel routines localisation et affichage source.
  • Passage Trace -> Monogams/Editeur : swap &88800-&bfff entre B et M.
  • BRK -> Trace :
    • swap &8000-&87ff entre B et M
    • pile en &8800

TODO:

  • Recenser l'ensemble des zones utilisées par le parcours du source en mode 0 et 4 et par l'affichage du dit source et par le désassemblage.
  • Vérifier le niveau de récursion (stack overflow ?).

Transcendance code éditeur miou-miou

Je propose ici une méthode qui :

  • simplifie le code.
  • facilite les améliorations ergonomiques et graphiques à venir (touche OvL!).
  • corrige 7 bugs d'un coup.

Tout comme l'édition et la gestion du source (encodage/décodage) se vont vues isolées avec succès, il serait judicieux de séparer le côté "navigation+édition" (ie : aller en ligne 140, supprimer une ligne…) de l'affichage.

Il y a au moins 5 excellentes raisons d'opérer ainsi (cf machine à café).

Alors, descendre d'une ligne se résumerait à incrémenter POS_LINE (si on est pas déjà sur la dernière). Toute la partie affichage (déplacement curseur, éventuel scrolling) s'exécuterait après coup.

Toute la gestion se contenterait de 5 routines externes :

  • valide_line (de = numéro ligne source). Echange avec codec qui refournit la ligne formatée. NB cette routine doit être appelée dès qu'on "quitte" la ligne. Non seulement en cas de déplacement Y, mais dès lors qu'une opération est lancée (assemblage, sauvegarde, …).
  • goto_line (de = numéro ligne source). Se résume en fait à assigner POS_LINE.
  • refresh_from (de = numéro ligne source). Notifie qu'il faut rafraichir à partir de telle ligne (cas insertion / suppression). Cette routine n'est pas obligé de réaliser le refresh elle-même. Dans l'idéal, elle met tout en place de sorte que 'update_screen' le fasse.
  • refresh_all : après chargement ou toute opération ayant effacé l'écran.
  • update_screen. Amène le curseur sur la ligne sélectionnée par goto_line, en scrollant éventuellement. Rafraichi ce qui doit l'être.

La partie navigation n'a pas à manipuler le curseur ou l'offset écran. La seule exception est pour l'édition de la ligne en cours, où l'on "prend la main". On notera :

  • on ne touche pas au curseur Y (lecture seule)
  • on pourrait aussi découpler ici, en ne modifiant que le buffer, et en laissant une routine externe rafraichir l'affichage. Ce serait pousser le concept un peu loin à première vue, mais cela apporte des possibilités intéressantes :
    • facilite fenêtrage quand ligne éditée plus grande que ligne affichée (symétriquement à ce qui se passe en Y, où il y a plus de lignes qu'affichable).
    • facilite réutilisation. Par exemple, dans l'éditeur, pourquoi ne profite-on pas du confort "flèches gauche et droite" dans l'édition des noms de fichiers ?

Cent discussions

Quelques décisions unilatérales soumises à commentaires.

Miniscule espace

Je compte afficher les mnémoniques en minuscules et sans padding. C'est plus compact, et je trouve ça plus lisible, particulièrement quand on enchaine les opcodes.

ASIS

      LD   A,(HL)
      INC  L
      CALL raymond
      LD   A,(HL):INC  L:LD   (DE),A:INC  E
      LD   A,(BC):INC  C:LD   (DE),A:INC  E

TOBE

      ld a,(hl)
      inc l
      call raymond
      ld a,(hl):inc l:ld (de),a:inc e
      ld a,(bc):inc c:ld (de),a:inc e

Fameux 3 ROMs.

Je m'étale sur 3 ROMs. Une fois tout fonctionnel, il serait possible d'alléger le code. Mais çela ne m'intéresse même pas ! Je prévois ensuite de nombreux outils supplémentaires (pour le moniteur et l'assembleur), qui redéborderaient de toute façon sur 3 ROMs.

Vrac

Gestion touches.

La saisie dans le moniteur et dans l'éditeur devrait être mutualisée ("factorisée" dans le jargon informatique), ne serait-ce que pour assurer l'homogénéité d'utilisation.
A une différence prêt : Hicks ne peut pas utiliser les vecteurs système.

Une gestion complète du clavier n'est pas triviale : gestion de l'auto-répétition, des touches mortes (CONTROL, SHIFT)…. D'ailleurs Zik s'était un peu cassé les dents dessus pour son SoundTracker+. Je pense qu'il ne faudrait pas partir de zéro, mais dans un premier temps copier les routines du firmware en déroutant les zones de travail aux endroits souhaités.
C'est un peu de boulot, mais un gros bénéfice collatéral serait la possibilité d'utiliser ce code dans le développement de démos, pour tester des paramètres en live de manière souple.

On gagnerait aussi en flexibilité. Avec les routines systèmes, il est très malaisé de détourner COPY et CAPS-LOCK de leur usage afin de les exploiter comme touches mortes (pour combinaisons COPY+Lettre par exemple).

Sur l'affichage du numéro de ligne

(TODO? Déplacer dans page VisuelEditeur?)

tl;dr : écarté pour la v1 ! Mais il faut limiter la largeur d'édition à 71 chr.

La motivation est décrite [[lien] là]. On se penche ici sur la réalisation.

Il est malin de ne réserver que le strict nécessaire (2 chr jusqu'à ligne 99, 3 jusqu'à 999).

 7 ; Tu triches ? tu pré-calcules ?
 8 sin_table = #4000
 9 log_table = #4100
10 exp_table = #4200

Mais à trop faire le malin on se mord les doigts.

  • En effet, dans la v1 on ne va pas gérer l'édition de lignes plus longues que la largeur d'écran. Or, avec cette approche, que faire si on copie la ligne 13 vers la ligne 2014 ? Les deux derniers caractères disparaitraient !
  • Cela complique le scrolling : quand la ligne 100 apparait pour la 1ère fois, il faut tout décaler !

En seconde approche, on peut donc réserver 5 chiffres.
On aurait donc :

XXnnnnn TTTTTTTTTTTTTTTTTTTTTT_

Avec XX : goutière pour signalétique.
nnnnn : numéro de ligne
TTTTTTTT : le texte lui-même, largeur max = 71.
_ : espace de rab pour le curseur, s'il est gérè comme dans DAMS.

Reste à voir si tout cet espace réservé à gauche n'est pas trop pertubant visuellement !

Auquel cas, on pourra envisager de :

  • Réaliser l'affichage compact du numéro de ligne. (Il faut tout de même limiter la largeur d'édition afin d'éviter l'écueil évoqué plus haut).
  • Ne pas réserver de goutière (Affichage les signes à la place du numéro de ligne, ou encore à droite).

C'est aux détails qu'on reconnait l'ouvrage !

Mais tout cela est à mettre de côté (ahah on rigole on s'amuse) ; en bref, pour la v1 :

  • pas de numéro de ligne (ce n'est pas une fonctionnalité indispensable). Méthode YAGNI : le strict nécessaire pour éditer le source de manière au moins aussi confortable qu'avec Dams, afin de pouvoir basculer.
  • on limite l'édition à 71 caractères, afin de prévoir l'évolution.

Mince j'avais pensé à autre chose (de grandiose !), et j'ai oublié.

Zones de travail

La version Debuging delight offre &8000-&97ff comme zone de travail.
Une partie sera utilisée comme mémoire vidéo supplémentaire, et l'autre pour tout ce qui est temporaire.
Quelques exemples de la distinction à opérer:

  • Buffers et variables temporaires
    • Tampon DISC I/O
    • Zones de travail lors assemblage
    • Tout ce qu'on réinitialise de toute façon à chaque utilisation
  • Buffers et variables persistents
    • [monogams] Texte affiché (afin de le retrouver en revenant de l'aide, mais aussi du moniteur, et même après un reset)
    • [monogams] Historique des commandes
    • [editeur] Toutes les variables de positionnement dans le code, les chaines de recherche…
    • bref tout ce qu'on doit retrouver après un reset.

Déplacer tout ce qui est temporaire en &8000-&97ff permet de libérer de la place en bank pour une utilisation plus judicieuse (historiques etc).
On pourrait par ailleurs fixer une convention pour distinguer les variables persistentes des temporaires (préfixe '_', préfixe 'tmp_', distinction par casse, …).

Suggestions connexes

Mode de distribution

Serait-il possible d'avoir une pensée pour les gens qui n'ont ni PC ni HxC, et de diffuser les ROM simplement sous forme de fichiers ?
Le HFE est une impasse sans HxC ni PC, et le DSK est une plaie (reformater une disquette complète à chaque fois juste pour transférer 3 fichiers c'est violent). Merci !

Sauf mention contraire, le contenu de cette page est protégé par la licence Creative Commons Attribution-ShareAlike 3.0 License