Sunday 23 June 2013

ME-non-MAPPER

Johdanto

Tjaha, kirjoitinkin tämän englanniksi huomaamattani. No, ei voi mitään, ˜1000 sanaa MERPGistä, toivottavasti joku tykkää :P

Idle but integrated thoughts

As I have written earlier (have I?), I am integrating MEMAPPER and MERPG-engine more tightly than probably is good for anyone's health. While running the engine, which has loaded the map set of test environment, one could press a well-documented key-shortcut, which fires up the map editor with the current map-set and current map. After doing whatever you were doing for the map, you press a button (or another shortcut) that sends the map back to the engine, which shows the thing "in the real world" and serializes it unto the disk.

Sooner or later I'll have to write a REPL-interface for actually creating the new maps, but since currently there's no real engine (only a Quil-based experimentation) the editor is a standalone program, with which you create new maps by restarting the thing. The unresetable nature of the program is an unbelievable pain in the arse when trying to do incremental development in the REPL, but tough luck, the alternative is to rewrite the old-as-hell state management in Clojure instead of Java... Which reminds me, if someone knows who to cheat leiningen to compile first (gen-class)'d clojure classes, then :java-sources, then the rest, feel free to answer my question in Javaranch.

Today while cycling to our local beach I kept thinking this mapeditor-engine - project which I have seriously developed for the last week (see the changelog's timestamps, Pröng's development seems to be postponed to the indefinite future...). Editor and engine sharing a map-set atom is a cool concept, but can this concept be extended? Could the user press one keyboard shortcut and enter the animation editor (Tässi? What kind of animations are we using? Some colourless gif-monster or png-series of images?), one to enter the NPC-editor, one to enter the Quest editor, one to enter the character-editor, one to enter the... whatever-editor?

Of course most of these are natural extensions of the map-editor. For example the NPC-editor would present an UI (for those uneducated pagans not fond of Emacs) for controlling enemy-spawning and scripting the artificial intelligence of the enemies and allies on the current map. I have no idea how the NPC:s are supposed to be scripted, but I think when the time comes, I'll have some sort of Clojure-based DSL ready. Character-editor will present tools to script your allies (remember: you have all the time two NPC-friends playing alongside you, and you can play any of them) on either global and per-map scale. One can also edit both the starting values and the values present in the engine's copy of the ally-atoms. Character-editor will also be able to assign animations to the all of the game's character classes in the game, and it'll maybe allow creating the whole new classes. We'll see, this system is meant to be open for poking, and I have grown fond of the Lispy idea of having unbelievably small kernel to build the things around, so probably all the game's character classes will be implemented through this system instead of hardcoding them.

Before I forget, a very important property of all the character classes is: singleton. This property ensures, as all those who have read their GoFs know, that there is only one concrete character of this class ever present. All the allies (err, main characters) will have this flag set to true, although this could present problems with some characters if taken to extremes :P

Quest-editor sounds also nice. Quest-data structure should probably have a :completed? - function, which is called every time player does something that could end a quest... or no. It is maybe nicer to make possible for adding a "Quest ready" - zone into maps. When the player wanders into the zones, all active quests are queried for their :completed? - function, and in case there's any, they are marked as such and archived. Unfortunately this approach will have some issues down the road, for example: who is going to host all the resources needed for specific quests. (@fatima-fought :completed?) - function will need to know the current HP of the Tutima-Fatima - singleton-character, but if one is nowhere near the fatima-fight (for example where? I have no idea! Let's just declare this situation hypothetical, so I don't have to open up the spec and begin searching for a needle in a haystack), it can't go loading the character from the filesystem every time player takes a step that ends up to the Zone.

Because RAM is cheap nowadays (and the JVM doesn't hoard it anyway, if you didn't catch the joke), I'll probably begin with an implementation that has instance of every singleton-character in the memory all the time. Their AI is just killed and they are not drawn when they are not needed, so there is no slow io required accessing them. They are maybe serialized to the same object as the set-of-maps. Or maybe they deserve their own slot in the global space, which unfortunately would require a whole new filetype next to the .MEMAP.

And this whole thing has to be documented somehow. For the relevant parts I'll follow the Quil's way of having a couple of functions listing all the relevant stuff, and telling everyone to (doc 'them-symbols). Opening up the doc-problem however would need twice this long text, so we shall not go there today.

tl:dr

The memapper (downloadable here) will be integrated to the engine, and will get a whole lot of functionality, which maybe disqualifies it to be a map editor, but it will still be cool. Let's hope I'll be able to write this "coolness" I feel into this software :P

Saturday 1 June 2013

Mallintamista ja PHP:tä

Tämä teksti alkoi mallinnuksen pohdintana, mutta päätyi abstraktiksi suunnitelmaksi Pröngin tulevaisuudesta. Nauttikaa kohtuudella, ja muistakaa olla olettamatta että tämän tekstin olevan faktaa. Tämä on hakemista; minulla on abstrakti aavistus tekstistäni, jonka muotoa yritän hakea paperille, ja joskus onnistun siinä hyvin, joskus huonosti. Tämä vaikuttaa, ainakin vielä Emacsista katsottuna, ensimmäiseen kategoriaan kuuluvalta.

Mallintaminen on uskomattoman vaikeaa. Harjaantumattomalle silmälle se voi näyttää helpolta, mutta ei. Turha toivo. Osa mallintamisen haasteista perustuu siihen, että kaikki ohjelmoidessa kohdattavat ongelmat voidaan jalostaa pienemmiksi ja pienemmiksi, kunnes omataan enää mallinnusongelmia. Yritätkö kirjoittaa N-ulotteista peliä? Noh, algoritmisi riippuvat siitä miten mallinnat pelihahmot, niiden liikkeet, niiden piirtämiset ja sellaiset. Yritätkö kirjoittaa nettisivua, johon tallennat blogitekstejä, kuvia, tai, noh, luonnoksia? Silkkaa mallintamista! En ole kohdannut Pröngin kanssa ongelmaa, joka ei olisi ratkennut muuttamalla mallinnuksessa käytettyä lähestymistapaa.

Tämän tekstin lukijoille suositellaan Universaaliin Designpatterniin tutustumista, ja Helloworldiä jollain Lispillä. Käytännössä kuitenkin riittää että ymmärtää Javan ja PHP:n oliomallit riittävän syvällä tasolla.

Miltä näyttää mallinnusongelma?

Tällaisen tunnistaa jokainen, joka joskus on luokkapohjaisella kielellä ohjelmoinut. Itse asiassa olen ilmeisesti lähestynyt aihetta aiemminkin. Teksti on luonnollisesti roskaa, mutta kun oikein siristelee silmiään, teksti puhuu ihan asiaa. Toteuttaakko (suorituskyvyltään ja laajennettavuudeltaan naurettavan \o/) skriptimoottorin suorittava osa iteroimalla rivien ensimmäisiä avainsanoja tuhannen rivin switch-hirviötä vasten, vai rakentaakko jokaista avainsanaa varten oma luokkansa, ja hakeakko vaikka reflektiolla avainsanan perusteella oikean luokan instanssi, jota vasten kutsutaan polymorfista execute() - metodia? Vai käyttääkkö kieltä, joka

  1. Tuntee Closure-käsitteen
  2. Omaa siistin syntaksin closureille
, ja laittaakko näitä hashmappiin keyword-string => closure - järjestykseen, jolloin "polymorfisen execute() - metodin" haku on vakio-mittainen operaatio (en kyllä tiedä mitään Javan ja C#:n reflektioiden big-O - laadusta, joten en tiedä onko tämä suorituskykyisempää kuin luokkien hakeminen niiden kautta...), ja saadaan yhä kaikki luokkien edut (toisinsanoen saadaan funktion ympärille tila) ilman kankeaa syntaksia.

Tältä, hyvät ystävät, näyttää mallinnusongelma. Kerroin kolma tapaa mallintaa skriptimoottorin runtimeä: proseduaalinen switch-helvetti (enpä tiedä ratkaisisivatko edes C:n ja Pascalin kaltaisiin juuttuneet tätä ongelmaa näin), luokkaperäinen toteutus (jota voisi odottaa mm. Java-, C#-, ja ehkä PHP-ohjelmoijiltakin?), ja funktionaalisuutta havitteleva closure-ratkaisu, jota Lisp-väki soveltaisi oikopäätä.

Aivan yksinkertaista

Onko? Otetaan esimerkki: Pröng. Pröngissä on luonnoksia, joilla on paljon erilaisia ominaisuuksia:

  • Teksti
  • Lähettäjä
  • Lähetysaika
  • Kommentit
  • Tagit
Tämä on jo monimutkainen rakenne. Oletetaan ettei kukaan tekisi omaa taulukkoaan jokaiselle näistä ominaisuuksista, ja määrittäisi yksittäistä oliota indeksiksi näihin viiteen taulukkoon, vaikka se voisikin tehdä relaatimalliin mappauksesta ehkä jopa helppoa. Ei, nykyaikainen koodari, joka on opiskellut ketteriä OO-metodologioita koko pienen ikänsä, mallintaa tämän hetkessä java-luokkana:

public class Luonnos
{
    private String Teksti;
    public String getTeksti()
    {
 return Teksti;
    }

    public void setTeksti(String value)
    {
 Teksti = value;
    }

    private String Lähettäjä;
    public String getLähettäjä()
    {
 return Lähettäjä;
    }

    public void setLähettäjä(String value)
    {
 Lähettäjä = value;
    }

    private String Lähetysaika;
    public String getLähetysaika()
    {
 return Lähetysaika;
    }

    public void setLähetysaika(String value)
    {
 Lähetysaika = value;
    }

    private List<String> Kommentit;
    public List<String> getKommentit()
    {
 return Kommentit;
    }

    private List<String> Tagit;
    public List<String> getTagit()
    {
 return Tagit;
    }
}

Jätän vielä huomiotta tietokannan. Oikeassa elämässä tämä perisi jonkin DAO-luokan, joka generoi SQL:n tallentamiseen, päivittämiseen ja hakemiseen. Tämä on perinteinen, enkapsulointia suojeleva tapa mallintaa dataolioita. C#ssä get- ja set-metodit korvattaisiin {get;set;} - loitsuilla julkisten kenttien perässä. PHPllä luokka mallinnettaisiin täysin samoin kuin Javalla. Ilmeisesti PHP 5.4n myötä PHP:n setterit ja getterit voisi korvata C#n kaltaisilla {get;set;} - kaksikoille.

Miten tämä mallinnettaisiin Clojurella? Hyvä että kysyitte!

  (defn new-luonnos [Teksti Lähettäjä Lähetysaika Kommentit Tagit]
     {:teksti Teksti :lähettäjä Lähettäjä :lähetysaika :kommentit Kommentit :tagit Tagit})

Sanokaa minua höpsöksi, mutta Clojuren tulkinta, josta näytin varsin verboosin esimerkin, on paljon mukavampi (toisinsanoen lyhyempi) kirjoitettava. Toki se rikkoo enkapsuloinnin, mutta Clojuren kanssa sillä ei ole väliä, koska mapin arvoja ei voi lisätä eikä muuttaa. Vaikka Clojuren java.util.Map - toteutuksen .put() käyttäytyisikin samoin kuin java.util.HashMap.put(), ei enkapsuloinnin puuttumisella välttämättä olisi väliä. Dokumentaatio on tärkeää.

Pröng

Otetaan tuo rakkain sivustoni lähempään tarkasteluun. Mainitsin aiemmin että PHPllä dataluokat voidaan mallintaa Javan tavoin, mutta oikeasti, miksi ihmeessä kukaan haluaisi tehdä näin? Yritä tehdä SQL:ää generoiva kerros javamaisille luokille, joissa luokkaskeemoissa on listattu jokainen property, ja niille erikseen setterit ja getterit. Joku reflektio-apina löytyy myös PHP:stä, mutta Java jätti ainakin minulle niin kovat traumat, etten edes tutkinut tätä mahdollisuutta. Sen sijaan, PHP tarjoaa joukon taikametodeja, joista tämän keskustelun kontekstissa kiintoisat ovat __get ja __set.

Mutta eivätkö nämä metodit hajota tyyppiturvallisuuden? Totta kai! Ne myös tarjoavat todella yksinkertaisen do-it-yourself - reflektion, jos __set()in asettaa asettamaan arvonsa mappiin, ja __getin asettaisi palauttamaan arvonsa tästä mapista. Tästä voi sitten generoida helposti SQL:t kaikkiin tarpeisiin... Jos niin haluat. Persistenssin datan mallinnus nimittäin monimutkaistaa yleisiä mallinnusongelmia todella pahasti. Yleensä persistenssillä datalla tarkoitetaan relaatiokantaan tallennettavaa dataa, mutta on olemassa myös ratkaisuja, jotka ottavat sisäänsä JSONää, XMLää ja jotain täysin omia ratkaisuja. Koska esimerkkinämme on Pröng, jonka persistenssimetodit ovat MySQL ja tiedostojärjestelmä, oletettakoon tämän teksin ajan ettei NoSQLää ole olemassa, ja etten ole siirtymässä viuhkasta pois lähiaikoina.

Tärkeä fakta, jonka jokainen harrastelija ja ammattilainen oppii, yleensä vaikeimman kautta: O/R-mappaus on vaikeaa. Alkuun kokeileva koodari kirjoittaa haku- ja syöttöfunktiot käsin (hauskaa! ensimmäisen viuhka-pröngin käsinkirjoitettu datakerros vei suhteelliselta rivimäärältään eniten tilaa koodipohjasta, ja oli täynnä SQL-reikiä, koska kukaan ei ollut kertonut hra koodarille PDOsta...), myöhemmin hän kuulee ORM-kirjastoista (Hibernate, EF, "ne mitä PHP:lle onkaan kirjoitettu"), ja kokeiltuaan niitä, hän toteaa näiden olevan vaikeita käyttää: kirjoitettakoon oma! Ei liene vaikea päätellä tuliko tästä omasta ORMista helppokäyttöinen, tai edes ongelmaton.

Miksi olioiden ja relaatiomallin välinen mappaus on niin vaikeaa? Ei se olekaan, jos luokka sisältää vain primitiivityyppisiä propertyjä, mutta jo listan mallinnus vaatii relaatioympäristössä säätämistä. Listalle pitää luoda oma taulunsa, johon asetetaan vierasavaimena tieto siitä mikä alkuperäisen taulun rivi nämä rivit omistaakaan. Muinaiset hakkerijumalat sinua auttakoon, jos haluat tallentaa oliopropertyjä kantaan.

Sivuhuomiona haluan sanoa, että uskoisin näiden ongelmien olevan paljon helpompia ratkottavia Clojurella, koska SQL on siellä ensiluokkainen kansalainen, joten SQL-stringien ja geneeristen prepared statementtien täyttäjien sijaan tietokantakoodin voi generoida ihan lispinä, niinkuin kaiken muunkin. En kuitenkaan ole vielä tehnyt tietokantakoodia, saati nettisivuja, Clojurella, joten jätän nämä uskonasioiksi vielä.

Mikä ratkaisuksi persistenssiin?

En minä tiedä! Helpointa olisi sijoittaa muutama lantti ympäristöön, joka oikeasti tukee Clojurea, ja johon voi asentaa minkä tietokannan haluaa, mutta sellaiset ympäristöt eivät ole halpoja, enkä tiedä onko tuotoiltaan olematonta sivustoa järkeä siirtää sellaiseen ympäristöön. Jos viuhkan tietokanta olisi SQL Server, voisi luonnoksia ja muita olioita tallentaa XML:nä sinne. MySQL ei ymmärtääkseni anna upottaa XPathia/XQueryä kyselyihin, toisinkuin SQL Server, ja tämä helpottaisi kyselyiden tekoa relaatiokantaan upotettuja XML-sarakkeita vasten.

Vai helpottaisiko?

Emmekö juuri todenneet O/R - mappauksen olevan vaikeaa? Miksi siis O <-> R <-> X - mappaus olisi yhtään helpompaa!?

Voisin kuitenkin väittää O/X-mappauksen olevan suhteellisen helppoa, niin kauan kuin XML:n ulkoasulle ei ole keinotekoisia rajoitteita. Yllättäen en ole tehnyt tätä PHPlla laisinkaan (okei, Pröngin XML-toteutukset ovat teoriassa olleet OX-mappausta, koska ne ovat olleet stringien katenointiin perustuvaa purkkaa, niitä ei lasketa), mutta Javalla olioista saa aika helposti XML:ää, ja C#llä vaaditaan 10 riviä jotta jokaiselle oliolle saadaan täysin geneerinen XMLSerialize() ja Stringeille XMLDeserialize<T>(). Jos Pröngiin tekisi tiedostopohjaisen "säilön" luonnoksille, ja tarpeen tullen laajentaisi tätä keskustelun pohjalle? Tai jos ensin saisi edes tagien mukaisen ryhmittelyn toimimaan kunnolla...

Mallinnuksesta kun tuli puhe, haluatteko kuulla miten mbnetin, jossa ei tosiaan ole tietokantaa saatavilla, aikaisessa Pröngissä luonnokset tallennettiin? XML:nä tiedostoon? Ha! Luonnokset löytyivät tiedostosta ./Luonnokset/$lid.php, jonne ne tallennettiin formaattiin:

    <?php
      $luonnos = "Luonnos";
      $lähettäjä = "Feuer";
      $lähetysaika = "01-06-2013";
    ?>
    
Näitä tiedostoja sitten include()tettiin luupin sisällä, ja työstettiin selaimelle, tai johonkin.

Joten mitä Pröngille tapahtuu

Pröngin persistenssiongelmat pitäisi vähitellen ratkaista. Marraskuusta käytössä ollut DAO oli hauska rakentaa, mutta suorituskyvyltään se oli niin urpo, että siihen piti rakentaa purkasta kakku ympärille, jottei tarvitsisi käydä tietokannassa kuin kerran per sivulataus. Pystyttänen macille jonkinlaisen kehitysympäristön, jossa testata tämän teksin konsepteja, ja päivittynyt koodi valuu viuhkaan kun ehtii.