Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: Pascal: Metodipointterit Delphissä (TKE)

Sivun loppuun

Janezki [22.09.2008 17:37:36]

#

Taas sitä mennään. Näyttää siltä että yksi suuri ongelman ydin on kokoajan ollut (väärä) tapani käsitellä metodien pointtereita. Taas hieman pohjustusta ennen kysymystä:

Kun peliä "prosessoidaan", esin renreöintimoduuli hoitaa piirtämisen ja sitten antaa mahdollisuuden entiteeteille omien touhuilujensa suorittamiseen. Mutta synkronoinnista johtuen entiteetit eivät voi prosessoida itse itseään (kun sanon prosessoida niin tarkoitan että entiteetin Processor() -metodi ajetaan). Sen sijaan entiteetti voi lisätä oman Processor() -metodinsa erityiselle listalle, josta renderöintimoduuli yksi kerrallaan kutsuu sitten näitä metodeja. Koodia tähän väliin:

Type
 TSimpleEvent = procedure of object; {Yksinkertainen tyyppimääritys}
...
var
 SceneProcessProcs  : Array of TSimpleEvent; {Tämä on prosessorilista}
...

{Tällä metodilla entiteetit voivat lisätä oman Processor() -metodinsa listaan}
procedure AddSceneProgressCall(PCallProc: TSimpleEvent);
var
 i : Integer;
begin
 {Varmistetaan että metodi ei ole valmiiksi listassa}
 for i := 0 to High(SceneProcessProcs) do
  if @SceneProcessProcs[i] = @PCallProc then {VIITE 1.}
    Exit;

 {Jos listassa on vapaa paikka, eli joku solu on Nil sitten lisätään parametrina annettu metodi prosessorilistaan}
 for i := 0 to High(SceneProcessProcs) do
  if not Assigned(SceneProcessProcs[i]) then
   begin
    SceneProcessProcs[i] := PCallProc;
    Exit;
   end;

 {Koska suoritus päätyi tänne tarkoittaa se sitä että metodi pitää lisätä mutta sille ei ole vapaata paikkaa listassa --> laajennetaan listaa}
 SetLength(SceneProcessProcs, High(SceneProcessProcs) + 2);
 SceneProcessProcs[High(SceneProcessProcs)] := PCallProc;
end;


{Tätä kutsumalla entiteetti poistaa metodin listasta}
procedure DeleteSceneProgressCall(PCallProc: TSimpleEvent);
var
 i : Integer;
begin
 {Käydään läpi prosessorilistaa...}
 for i := 0 to High(SceneProcessProcs) do
  if @SceneProcessProcs[i] = @PCallProc then {Verrataan osoitteita}
   begin
    {Nyt indeksillä i tärppäsi. Nollataan prosessorilistan alkio, ettei kyseistä metodia enää kutsuttaisi}
    SceneProcessProcs[i] := Nil;
    Exit;
   end;
end;

Olen ihan varma että en käytä oikein tuota @ - merkkiä. Mainittakoon vielä että Processor() -metodi, joka siis kuuluu tyypille TMapEntity ja joka luonnollisesti perityy kaikille sen lapsille on virtuaalinen.

Esimerkiksi jos yritän lisätä 2 entiteettiä, joiden tyyppi on TSplatSprite, vain ensimmäinen pääsee prosessilistalle, toinen jää kiinni tuplatarkastukseen (katso koodista VIITE 1.)

Tuossa vielä koodi missä TMapEntity käyttää näitä kahta metodia:

procedure TMapEntity.SetNeutral(Value: Boolean);
begin
 if Value <> FNeutral then
  begin
   FNeutral := Value;
   if FNeutral then
    DeleteSceneProgressCall(Self.Processor)
   else
    AddSceneProgressCall(Self.Processor);
  end;
end;

Metabolix [22.09.2008 19:35:40]

#

Normaali funktio-osoitin on vain yksi osoitin. Sen sijaan metodiosoitin sisältää kaksi osoitinta, osoittimen itse metodiin ja osoittimen Self-olioon. (Ks. System-unitin TMethod.) Ilmeisesti @-merkki tuottaa näistä ensimmäisen, jolloin luonnollisesti kaikki saman luokan instanssit tuottavat saman tuloksen.

program PtrSize;
var
  Ptr: Pointer;
  MethodPtr: procedure of object;
begin
  WriteLn(SizeOf(Ptr), ' < ', SizeOf(MethodPtr));
  { Esim. '4 < 8' }
end.

Jos funktion nimi on aina Processor ja kaikki oliot ovat TMapEntityn alaluokkia, suosittelisin ennemmin taulukollista TMapEntity-osoittimia, koska virtuaalinen funktio löytyy kyllä kutsuvaiheessakin. Sitähän varten se on virtuaalinen.

Jos kuitenkin haluat pysyä valitsemallasi tiellä tai mainitut ehdot eivät täyty, joudut hankkimaan käsiisi osoittimien osoitteet ja vertailemaan niiden sisältä dataa. Tässä tilanteessa Pascalin toimintatavat @-merkin ja ^-merkin kanssa ovat minulle tuntemattomia, mutta jos muuta tapaa ei löydy, niin ainakin wrapperi luultavasti pelastaa (en kokeillut):

type
  TSimpleEventCmp = record
    ptr: TSimpleEvent;
  end;

function SimpleEventEqual(S1, S2: TSimpleEvent): Boolean;
var
  A, B: TSimpleEventCmp;
begin
  A.ptr = S1;
  B.ptr = S2;
  Result := (A = B); { Vertaillaan recordeja, pitäisi toimia paremmin }
end;

Janezki [22.09.2008 21:00:53]

#

Metabolix kirjoitti:

Normaali funktio-osoitin on vain yksi osoitin. Sen sijaan metodiosoitin sisältää kaksi osoitinta, osoittimen itse metodiin ja osoittimen Self-olioon. (Ks. System-unitin TMethod.) Ilmeisesti @-merkki tuottaa näistä ensimmäisen, jolloin luonnollisesti kaikki saman luokan instanssit tuottavat saman tuloksen.

Jos funktion nimi on aina Processor ja kaikki oliot ovat TMapEntityn alaluokkia, suosittelisin ennemmin taulukollista TMapEntity-osoittimia, koska virtuaalinen funktio löytyy kyllä kutsuvaiheessakin. Sitähän varten se on virtuaalinen.

Jos kuitenkin haluat pysyä valitsemallasi tiellä tai mainitut ehdot eivät täyty, joudut hankkimaan käsiisi osoittimien osoitteet ja vertailemaan niiden sisältä dataa. Tässä tilanteessa Pascalin toimintatavat @-merkin ja ^-merkin kanssa ovat minulle tuntemattomia, mutta jos muuta tapaa ei löydy, niin ainakin wrapperi luultavasti pelastaa (en kokeillut):--

Tuo TMethod saattaisi olla pelastava tekijä. Näin sen aikaisemminkin mutta en ymmärtänyt sen merkitystä.
Valitettavasti en oikein voi/halua tehdä TMapEntity-osoittimia, koska esim. paikallista pelaajaa kuvaavalla kameralla on myös oma Processor, mutta se on liian poikkeava olio että sitä voisi yleensä liittää luokkastruktuuriin. No periaatteessa voisi, mutta kaikkien uusien luokkien sitouttaminen TMapEntityyn söisi vain modulaarisuutta.

Pitää lukea vielä tuosta ^-merkistä lisää ja katsoa joskos TMethod pystyisi auttamaan.

Metabolix [22.09.2008 23:48:22]

#

Ainahan voit periyttää molemmat jostain vielä yksinkertaisemmasta:

type
  TProcessorClass = class
    procedure Processor; virtual;
  end;
  TMapEntity = class (TProcessorClass)
    procedure Processor; override;
  end;
  TCamera = class (TProcessorClass)
    procedure Processor; override;
  end;

Tässä vielä ainakin Free Pascalilla toimiva versio vertailusta, menee kyllä purkkakoodin puolelle mutta tuottaa oikean tuloksen.

type
        TProcOfObj = procedure of object;
        TProcOfObjW = record
                P: TProcOfObj;
        end;

function ProcOfObjCmp(A, B: TProcOfObj): Boolean;
var
        AA, BB: TProcOfObjW;
        AP, BP: ^Byte;
        I: Integer;
begin
        AA.P := A;
        BB.P := B;
        AP := @AA;
        BP := @BB;
        for I := 1 to SizeOf(TProcOfObjW) do begin
                if (AP^ <> BP^) then begin
                        Result := False;
                        Exit;
                end;
                Inc(AP);
                Inc(BP);
        end;
        Result := True;
end;

Jos periyttäminen yhteisestä kantaluokasta ei sovi, suosittelisin sitä vaihtoehtoa, että teet koodista bugitonta ja selviät aivan ilman vertailuja.

function AddCallToList(C: procedure of object): Integer;
begin
  { Lisää kutsu taulukkoon, ei tarvitse tarkistaa olemassaoloa }
  Result := indeksi;
end;

procedure RemoveCallFromList(I: Integer);
begin
  { Poista kutsu taulukon kohdasta I, se on varmasti oikea kohta }
end;

constructor TMapEntity.Create;
begin
  { Objekti säilyttää oman indeksinsä; -1 = ei indeksiä }
  Self.CallNumber := -1;
end;

procedure TMapEntity.AddCall;
begin
  { Lisätään vain, jos ei ole vielä lisätty }
  if Self.CallNumber < 0 then Self.CallNumber := AddCallToList(Self.Processor);
end;

procedure TMapEntity.RemoveCall;
begin
  { Poistetaan, jos ei ole vielä poistettu }
  if not (Self.CallNumber < 0) then RemoveCallFromList(Self.CallNumber);
  Self.CallNumber := -1;
end;

Janezki [23.09.2008 08:06:08]

#

Metabolix kirjoitti:

--Jos periyttäminen yhteisestä kantaluokasta ei sovi, suosittelisin sitä vaihtoehtoa, että teet koodista bugitonta ja selviät aivan ilman vertailuja.--

Jep, tuossa olisi taas ratkaisun makua. En toteuttanut tuolla systeemillä alun perin koska lista toimi silloin vielä dynaamisesti eli tyhjiä soluja ei syntynyt, eli silloin indeksitkin muuttuivat. Mutta en tosiaan jaksa painia enempää tämän parissa, koko ongelma on niin vienyt koodaushalut pois, minulla kun on vielä sellainen perfektionistin asenne että kaikki pitää tehdä kirjan mukaan.

User137 [23.09.2008 09:27:21]

#

Tyhjiä solujahan ei synny listoihin ollenkaan jos siirtää viimeisen indeksin poistetun paikalle ja pienentää listan kokoa yhdellä. Tuossa vaiheessa kyseiseen indeksiin siirrettyyn objektiin viittaavat muut "asiat" pitää huolehtia päivittää. Sopisiko se tapaukseen?

Janezki [23.09.2008 11:58:56]

#

User137 kirjoitti:

Tyhjiä solujahan ei synny listoihin ollenkaan jos siirtää viimeisen indeksin poistetun paikalle ja pienentää listan kokoa yhdellä. Tuossa vaiheessa kyseiseen indeksiin siirrettyyn objektiin viittaavat muut "asiat" pitää huolehtia päivittää. Sopisiko se tapaukseen?

Alun perin systeemi toimi niin että solua poistetaessa kaikki seuraavat solut "nakattiin" askeleen eteenpäin. Mutta ei tyhjät solut sinällään ole mikään ongelma -suurissakaan määrin- ja Metabolixin ehdottama malli sallisi yksinkertaisemman rajapinnan renderöintimoduulin ja entiteettien välille, kun renderöintimoduulin ei tarvisi huolehtia viitteistä.

Janezki [23.09.2008 13:11:01]

#

Kaikki näyttäisi toimivan nyt niinkuin pitää. Keksin muuten että listaahan voi tietysti pitää pienenä kutistamalla sitä loppupäästä aina kun sinne syntyy tyhjiä soluja. Kaikki pitkään elävät entiteetit ovat kuitenkin listan alkupäässä.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta