Viele Sprünge usw - wie handhabt ihr das?

  • Viele Sprünge usw - wie handhabt ihr das?

    Servus,

    hin und wieder gibt es ja den Fall, dass man eine Variable auf zig verschiedene Werte pruefen muss, und darauf basierend dann verzweigen muss; z.B. wenn der Nutzer eine Taste drueckt und man diese auswertet. Also sowas hier (nur exemplarisch):

    Quellcode

    1. lda key
    2. cmp #'A'
    3. beq .hat_a_gedrueckt
    4. cmp #'B'
    5. beq .hat_b_gedrueckt
    6. cmp #'C'
    7. beq .hat_c_gedrueckt
    8. ...

    Nun kennt ihr bestimmt alle das Problem, dass die Sprungadressen irgendwann schnell zu weit weg liegen und er nicht mehr direkt hinspringen kann. Also wird es dann schnell sowas:

    Quellcode

    1. lda key
    2. cmp #'A'
    3. beq .hat_a_gedrueckt
    4. cmp #'B'
    5. beq .hat_b_gedrueckt
    6. cmp #'C'
    7. beq .hat_c_gedrueckt
    8. ...
    9. .hat_a_gedrueckt
    10. jmp hat_a_gedrueckt
    11. .hat_b_gedrueckt
    12. jmp hat_b_gedrueckt
    13. .hat_c_gedrueckt
    14. jmp hat_c_gedrueckt
    15. ...
    Alles anzeigen

    Sprich, man springt eigentlich zweimal, und man schreibt auch noch viel "unnoetigen" Code. Wie sehr wuerde ich mir manchmal wuenschen, es gaebe auch ein BEQ mit einer absoluten Adresse...

    Gibt es hierfuer eine elegantere Loesung? Klar, wenn die Werte linear sind (z.B. von 0-20), dann koennte man die Sprungadressen alle in eine Tabelle schreiben und per Index zugreifen. Aber auch das ist nur in manchen Situationen "schoener" als das obige Konstrukt. Und bei Dingen wie Tastencodes, die eben nicht linear sind, waere das schon gar nicht moeglich. Ist das da oben wirklich der einzige sinnvolle Weg?
    SHOTGUN - 4-Player Death Match - Website / CSDb / X-Mas
    FROGS - 4-Player Frog Pond - Website / CSDb
  • Wenn die Sprünge so einigermaßen "hintereinander" liegen und man nicht im ROM/Modul ist, benutze ich gerne Selfmod.
    Das hat den Vorteil, dass man bei der Rasterzeit immer gleich bleiben kann. Unbenutzte Bereiche fülle ich mit dem .Nothing auf.
    Worst-Case wären natürlich 2x256 Werte für die Tables. Hier habe ich die aber z.B. mit dem LSR schon verkleinert.

    Brainfuck-Quellcode

    1. lda key
    2. lsr ; only real keys from a-xxx are necesseray,
    3. lsr ; so we can keep the table smaller
    4. lsr
    5. lsr
    6. lsr
    7. tax
    8. lda .hi_key_press,x
    9. sta .dynamic_cmp_ld+1 ; patch the jump with table value
    10. lda .lo_key_press,x
    11. sta .dynamic_cmp_ld+2 ; patch the jump with table value
    12. .dynamic_jmp_key_press
    13. jmp $0000 ; this table is patched relative to the key pressed
    14. .hi_key_press
    15. !byte <(.nothing), <(.key_press_a), <(.key_press_b), <(.key_press_c), <(.nothing) ...
    16. .lo_key_press
    17. !byte >(.nothing), >(.key_press_a), >(.key_press_b), >(.key_press_c), >(.nothing) ...
    18. .nothing
    19. rts
    20. .key_press_a
    21. ...
    Alles anzeigen

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von Soulstealer () aus folgendem Grund: fehlte noch das tax ;)

  • Ich mach mir für sowas gern eine kombinierte Tabelle mit Vergleichswert und Sprungziel, und mach dann einen indirekten JMP:

    Quellcode

    1. key='B'
    2. ldx #0
    3. loop:
    4. lda tab,x
    5. cmp #key
    6. beq go
    7. inx
    8. inx
    9. inx
    10. bmi loop
    11. rts
    12. go: lda tab+1,x
    13. sta $fa
    14. lda tab+2,x
    15. sta $fb
    16. jmp ($fa)
    17. tab:
    18. .byte 'A': .word key_A
    19. .byte 'B': .word key_B
    20. .byte 'C': .word key_C
    21. key_A:
    22. key_B:
    23. key_C:
    24. lda #<text
    25. ldy #>text
    26. jsr $ab1e
    27. rts
    28. text: .byte "HELLO WORLD"
    29. .byte 13,0
    Alles anzeigen
    Wer die Ironie findet, darf sie behalten ^^
  • Die Einzelabfragen würde ich nur bei zwei oder drei verschiedenen Tasten nehmen, sonst immer Tabellen:

    Quellcode

    1. menu jsr get_key
    2. ldx #keys_end - keys_start - 1 ; max offset
    3. loop cmp keys_start, x
    4. beq found
    5. dex
    6. bpl loop
    7. jsr beep ; invalid key
    8. jmp menu
    9. found lda action_lo, x
    10. sta ptr
    11. lda action_hi, x
    12. sta ptr + 1
    13. jmp (ptr)
    14. keys_start !pet "mkdb"
    15. keys_end
    16. action_lo !by <maim, <kill, <destroy, <burn
    17. action_hi !by >maim, >kill, >destroy, >burn
    Alles anzeigen
    Diese "saubere" Variante liefe auch aus dem ROM, in der Praxis nehme ich statt dem indirekten Jump eine Selbstmodifikation.
    Yes, I'm the guy responsible for the ACME cross assembler
  • Man kann den indirekten JMP auch noch etwas anders lösen:

    Quellcode

    1. go: lda tab+2,x
    2. pha
    3. lda tab+1,x
    4. pha
    5. rts
    6. tab:
    7. .byte 'A': .word key_A-1
    8. .byte 'B': .word key_B-1
    9. .byte 'C': .word key_C-1

    Muß dann aber darauf achten, die Sprungtabelle mit jeweils -1 abzulegen!
    Wer die Ironie findet, darf sie behalten ^^
  • Lysosom schrieb:

    Könnte es so auch gehen?
    In einem konkreten Fall mit ca. 2 Dutzend wild gewürfelten Tastenabfragen war ich mir auch nicht zu schade, die "THEN"-Fälle praktisch zu in-linen. Solange die Fall-Routinen klein genug sind und immer sauber aus dem "switch" herausspringen geht das so:

    Quellcode

    1. .switch
    2. CMP #A:BNE switch_00
    3. <Hier code für A>
    4. RTS
    5. .switch_00
    6. CMP #B:BNE switch_01
    7. <Hier code für B>
    8. RTS
    9. .switch_01
    10. [...]
    Der switch wird mit JSR aus der Hauptschleife angesprungen. Das kostet also 5 Bytes pro Fall (CMP/BNE/RTS) - selbst bei einer Tabelle mit Sprungzielen und Suchroutine braucht's für jeden Fall 4 Bytes (Suchbyte/Adresse/RTS) ... hängt also vom Einzelfall ab, was kompakter ist.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von Mike ()

  • Ich nehme ACME, da kann ich per Makro schreiben:

    +beq ganz_weit

    und der Assembler ersetzt das Makro dann durch ein

    bne +
    jmp ganz_weit
    +

    Ja, zugegebenerweise nichts neues, aber dennoch lesbarer.
    ---
    Meine Github-Projekte: github.com/markusC64/
    1541 Ultimate II Firmware Releases: github.com/markusC64/1541ultimate2/releases
    1541 Ultimate II Update instructions: github.com/markusC64/1541ultimate2/wiki
  • Soulstealer schrieb:

    Wenn die Sprünge so einigermaßen "hintereinander" liegen und man nicht im ROM/Modul ist, benutze ich gerne Selfmod.
    Selbstmodifizierender Code? Das tut man nicht - das ist ganz ganz B Ö S E. :sonicht:
    Außer man will den Code bewusst verschleiern (z.B. bei Kopierschutzfunktionen).
    PET 2001 / CBM 3032 / CBM 3040+4040 / CBM 8250 (Dauerleihgabe) / C64+1541 / VC20

  • Mal aus anderer Richtung betrachtet:

    Kommt es nur hier und da mal im Programm mit ein paar Werten vor, oder wird es ein großes Projekt mit vielen Dutzend Menüpunkten und Untermenüs, wo sich auch schnell mal ein bisschen mehr Code lohnt?
    Dann kann es sich wegen der Übersichtlichkeit und auch wegen des Platzes lohnen, das alles von einem Unterprogramm erledigen zu lassen und alle Parameter direkt hinter dem Aufruf abzulegen. Mit ein bisschen Stack-Manipulation kann man dann auch wieder hinter dieses Daten zurückspringen.

    Aus einem alten Quelltext (keine Ahnung mehr, warum ich die Parameter ausgerechnet so angelegt habe):

    Quellcode

    1. +flaemenu
    2. jsr window
    3. .stext "dbkel`b bloecke_k kreise_e ellipsen_f fuellen_`@"
    4. jsr eingabe
    5. .t "bkef":.b 0
    6. .w zbloc,zfkre,zfeli,zfill
    7. beq w2
    8. jmp flaemenu
    9. !window pla
    10. sta $fa
    11. pla
    12. sta $fb
    13. jsr window_zeichnen
    14. lda $fa
    15. bne dec2
    16. dec $fb
    17. -dec2 dec $fa
    18. lda $fb
    19. pha
    20. lda $fa
    21. pha
    22. rts
    Alles anzeigen
    Vollmond war gestern!
  • Vielen Dank, sind auf jeden Fall ein paar interessante Ansaetze dabei, die man je nach Situation nutzen kann.

    @detlef, selbstmodifizierenden Code verwende ich eigentlich staendig. In der Regel fuer Schleifenzaehler oder fuer Adressen die hochgezaehlt werden. Ist in vielen Faellen einfach die simpelste Variante, und boese finde ich das auch nicht. Es wird ja auch nicht wirklich "der Code" komplett veraendert, sondern lediglich ein Parameter eines Befehls, das ist jetzt nicht unbedingt Hexerei.
    SHOTGUN - 4-Player Death Match - Website / CSDb / X-Mas
    FROGS - 4-Player Frog Pond - Website / CSDb
  • ZeHa schrieb:

    @detlef, selbstmodifizierenden Code verwende ich eigentlich staendig. In der Regel fuer Schleifenzaehler oder fuer Adressen die hochgezaehlt werden. Ist in vielen Faellen einfach die simpelste Variante, und boese finde ich das auch nicht. Es wird ja auch nicht wirklich "der Code" komplett veraendert, sondern lediglich ein Parameter eines Befehls, das ist jetzt nicht unbedingt Hexerei.

    bin ich auch kein Freund von, vorallem weil das moderne Systeme schon gar nicht mehr zulassen, und auch bei einigen Architekturen, wie z. B. AVR geht sowas nicht, daher sollte man sich das jetzt auch gar nicht mehr angewöhnen, finde ich.

    Aber wie immer, jeder wir er meint ;)
    Wer die Ironie findet, darf sie behalten ^^
  • Das hat ja nix mit "Angewöhnen" zu tun. Auf einem System wie dem C64 ist das nunmal eine der simpelsten Methoden, etwas zu erreichen, was man ansonsten nur weitaus umständlicher zustande bekommt. Das hat nix mit anderen Systemen oder Prozessoren zu tun. Deinen Satz "aber jeder wie er meint" müsste eher lauten "auf jedem System das entsprechende Vorgehen/Werkzeug".
    SHOTGUN - 4-Player Death Match - Website / CSDb / X-Mas
    FROGS - 4-Player Frog Pond - Website / CSDb
  • Anderes Beispiel: Du hast solchen Code geschrieben, und irgendwann später möchtest du den selben Code für ein anderes Projekt in einem EPROM verwenden. Jetzt musst du alles entsprechend anpassen.

    Vielleicht ist jetzt verständlich, worauf ich hinaus möchte.
    Wer die Ironie findet, darf sie behalten ^^