DynamicSprite.CreateFromExistingSprite() problem (might be bug?)

Started by fernewelten, Sat 23/07/2022 07:18:25

Previous topic - Next topic

fernewelten

Hi folks, I'm stumped.

(I'm on AGS 3.5.1, newest official build 3.5.1.19, on Windows 10).

I'm managing a handful of trolleys that run on tracks across a room. The trolleys are called “crate” internally and the tracks “conveyor” because I've been stupid and English isn't my mother tongue.  :)

So here's how I define my trolleys:
Code: ags
struct tyCrate
{
    Object *Object;
    DynamicSprite *DS;
};

#define CrateCount 10
tyCrate Crate[CrateCount];

function room_Load()
{
    Crate[0].Object = oCrate0;
    Crate[1].Object = oCrate1;
    …
    Crate[9].Object = oCrate9;
    for (int crate = 0; crate < CrateCount; crate++)
    {
        Crate[crate].Object.Visible = false;
        Crate[crate].DS = null;
    }
}


Whenever a trolley must enter the scene, I select an unused one out of the pool above (parameter crate), let it look like a certain sprite (parameter gr) and initialize it as follows:
Code: ags

#define Crate_DispatchX 1300
#define LowerConveyerY 540
function Crate_Assign(int crate, int gr)
{
    Crate[crate].DS = DynamicSprite.CreateFromExistingSprite(gr);
    Crate[crate].Object.Graphic = Crate[crate].DS.Graphic;
    Crate[crate].Object.Visible = true;
    
    Crate[crate].Object.StopMoving();
    Crate[crate].Object.X = Crate_DispatchX;
    Crate[crate].Object.Y = LowerConveyerY;
}


I have to do it this way because the sprite will be modified while it is "on stage". In particular, a printer will write a checkmark onto its side:

Code: ags
function Printer_PrintLabel(int crate, int gr)
{
    DynamicSprite *ds = Crate[crate].DS;
    DrawingSurface *dsu = ds.GetDrawingSurface();
    dsu.DrawImage(23, 20, gr, true);
    dsu.Release();
}


When the trolleys run off-stage, I dismantle them and return them into the pool, as follows:
Code: ags

function Crate_Destroy(int crate)
{
    Crate[crate].Object.StopMoving();
    Crate[crate].Object.Graphic = kGr_CrateEmpty;
    Crate[crate].Object.Visible = false;
    if (Crate[crate].DS != null)
    {
        Crate[crate].DS.Delete();
    }
    Crate[crate].DS = null;
}


So the dynamic sprite is duly deleted â€" this means when I call  Crate_Assign() again later on, it is extremely likely that DynamicSprite.CreateFromExistingSprite() will return this newly deleted sprite.

And there's my problem: When Crate_Assign() is called the second time around, the object shows a sprite that still has the check mark from the first time written on it. So does that mean that the dynamic sprite hasn't been set correctly? Seemingly not, because the trolley goes into the printer, and when the printer does its thing, shwoosh, suddenly the correct sprite for the trolley shows up at this point. 

I haven't done that much with dynamic sprites so far, so this might be a coding error. OTOH, I might have uncovered an Engine bug, too.

Do you have any pointers where the coding has gone awry?

Find the project with the code here. The original has become quite large; I've pared down the files as much as possible. Everything is in room 6.

To demonstrate the problem:
1. Hit F5 to compile and start the game
2. Left-click on the rightmost "cinorq" (the one above the champagne bottle).
3. Right-click on the rightmost "cinorq".
4. Observe how two different trolleys make their way across the room, ultimately disappearing in the tunnel on the left-hand side.
5. Wait 5 seconds to make sure that the trolleys have been dismantled.
6. Now do it the other way round: RIGHT-click on the rightmost "cinorq", then LEFT-click on the rightmost "cinorq".
7. Observe that the trolleys that emerge still have their printed marks on them. Also, they still look like the ones created in 2. and 3., not the other way round.
8. In the printing station, however, they suddenly switch to the correct graphics.

Crimson Wizard

Quote from: fernewelten on Sat 23/07/2022 07:18:25
So the dynamic sprite is duly deleted â€" this means when I call  Crate_Assign() again later on, it is extremely likely that DynamicSprite.CreateFromExistingSprite() will return this newly deleted sprite.

To clarify: it may return same slot number, but it cannot return same sprite, as when sprite is deleted - it is deleted and there's nothing to return. When the new sprite is created from the existing sprite - the absolutely new sprite is allocated in memory and painted a copy of a requested sprite on it, so it's guaranteed to be new. There's a way to test this: try drawing newly created sprite somewhere else on screen.

The explanation that I have at the moment is that the object's cached texture is not updated. The engine updates the texture only when it detects a difference with the previous parameters. There's a theoretical assumption that if the number of the sprite stayed the same when object is visible once more, it may think that nothing has changed since it last displayed the object, and so it won't update the texture.

When the dynamic sprite is deleted, engine forces all objects using that sprite to reupdate. Therefore the workaround for this situation, although counter intuitively, to reset object's Graphic after you deleted the sprite (because in this case the engine will mark this object's cached texture for update).

That is, instead of
Code: ags

    Crate[crate].Object.Graphic = kGr_CrateEmpty;
    if (Crate[crate].DS != null)
    {
        debugit(String.Format("Deleting DS Graphic %d", Crate[crate].DS.Graphic));
        Crate[crate].DS.Delete();
    }


Do
Code: ags

    if (Crate[crate].DS != null)
    {
        debugit(String.Format("Deleting DS Graphic %d", Crate[crate].DS.Graphic));
        Crate[crate].DS.Delete();
    }
    Crate[crate].Object.Graphic = kGr_CrateEmpty;


As for the engine fix, it should reset object cache also when changing the Graphic to another sprite, probably. I only wonder if that's the only remaining case when such effect may take place. There are also Views, which frames could be dynamic sprites, so I need to investigate if there's a theoretical possibility that similar thing may happen if you delete/change the dynamic sprite of a view frame.

Crimson Wizard

After testing this case with various versions of AGS, I found that it's a regression appeared in 3.5.1, therefore I will have to fix this in 3.5.1 and release a new patch.

EDIT: you may try this temp build:
https://cirrus-ci.com/task/4844281574719488

fernewelten

Many thanks for the very fast response!

I've finished my room using your workaround which worked fine. I'll have time to look at the temp build in the next few days.


SMF spam blocked by CleanTalk