Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - Crimson Wizard

#2301
Quote from: eri0o on Tue 18/04/2023 21:40:01I don't think I get quite well what circular dependency case you mean here, can you explain it with some example?

Simply when two objects reference each other, directly or indirectly.

Code: ags
// Example 1
struct LinkedItem {
    LinkedItem* next;
    LinkedItem* prev;
};

// Example 2
struct Parent {
    Child* children[];
};

struct Child {
    Parent* parent;
};



Quote from: eri0o on Tue 18/04/2023 21:40:01
QuoteSupposing we only run GC between the scripts, during the game frame update

Would the GC not need to happen on each scope change? Or would this only affect the circular dependent object?

What is "scope change"?

GC does not have a strict requirement to happen on particular events, it's okay to run it periodically.
It may be run by a timer, or under some trigger conditions based on managed pool statistics.

It also may be run on a separate thread, but only during some script-unrelated processes, like render or game update, that do not make any immediate script runs.
#2302
Speaking theoretically, the GC should be able to detect, which managed objects are referenced from within the normal script memory, and which are not. If an object is not referenced from the script mem, - such object is detached and considered garbage.

NUANCE: engine may also hold "internal" managed references, so GC must also take that into account (not certain how yet).

Supposing we only run GC between the scripts, during the game frame update. That means that the only place we need to check is global script memory (because stack will be reset at the time). If the script memory would have contained hints on the data types in it, we could know where are "dynamic object handles" among it.

Hypothetically, the GC process then may work like this:

* scan the global script memory;
* for each value in that memory, which has a "dynamic handle" type,
   * mark that handle as used;
   * retrieve the dynamic object itself, find out if there are managed handles inside too, mark these as used;
   * repeat recursively for each connected dynamic object?

* after all this, compare list of existing handles with the list of "used" ones, and dispose objects with "unused" handles.
#2303
Unfortunately, this is still not done. There's another problem which I forgot about: it's circular dependencies. The objects not referenced from script may still reference each other, which keeps them in memory until the program ends.

I never researched how this is done in other managed languages, but from a quick check they somehow are able to distinguish whether reference is from a script or internally from an object, and then consider every object that does not have a "script" reference a garbage amd remove it during garbage collection.

Need to invent something similar for AGS script now.

I really wish someone mentioned this earlier to me! Maybe it's going to be even larger work than reflection with RTTI... which means this functionality is not as close to completion as i supposed.
#2304
I do not recommend doing things like "locname = Game.GetLocationName(mouse.x,mouse.y);" and "GetObjectAtScreenXY" in unhandled_event, because that is not exactly logical, as "unhandled_event" may be run for other reasons, not related to mouse clicks.

It's more reliable to remember the last clicked object etc in on_mouse_click, right before you run interaction over it, and save it in global variables.

EDIT: But maybe this is not important for your game, if it does not have such cases...

Also, only to mention this, you could avoid using unhandled_event at all, and make your own function(s) for handling this. Each interactable object has IsInteractionAvailable function, so you could even test this in on_mouse_click:
Code: ags
Object *obj = Object.GetAtScreenXY(mouse.x, mouse.y);
if (obj != null)
{
     if (obj.IsInteractionAvailable(mouse.Mode))
          obj.RunInteraction(mouse.Mode);
     else
          UnhandledObject(obj, mouse.Mode);
}

where "UnhandledObject" will be something like
Code: ags
function UnhandledObject(Object *obj, CursorMode mode)
{
}
#2305
These events are normally fired when you interact with some object which does not have an interaction linked in. For instance, if you click "Look at", and the object does not have a "look at" reaction. The common use-case for them is to say something random, like "There's nothing of interest" or "I cannot use that".

QuoteI see that it jumps at any time when the action changes, not only on the initial click.

Could you elaborate, what do you mean by "action changes"?
#2306
Also, not sure if relevant, but

Quote from: PERXEO on Sun 16/04/2023 20:37:21
Code: ags
function btnChangeMode_OnClick(GUIControl *control, MouseButton button)
{
  player.Say("CLICK TO CHANGE MODE");
  player.ActiveInventory=null;
  mouse.Mode=eModeUseinv;
  mouse.EnableMode(eModeUseinv);
  
}

Here you are setting mouse.Mode=eModeUseinv before enabling it, which makes no sense.
#2307
If only 1 image is supposed to be on screen at the same time, then the easiest way here would be to have 1 GUI, and change its BackgroundGraphic. This way you always know if GUI with an image is visible, and which image is that.
#2308
Well, I'd like to see if this script solution would work for @Mehrdad. Also afaik @Wesley wanted to try Arabic translations in his game.

I wrote an experimental change for the engine already (found here), but I will postpone merging until it is more clear whether scripting solution works conveniently.
#2309
How do you display these images exactly, in AGS terms? Why cannot you check whether *some* image is already displayed and remove it before displaying another one?
#2310
Quote from: Snarky on Fri 14/04/2023 13:36:10
Quote from: Crimson Wizard on Fri 14/04/2023 12:43:52I guess you may try that, except you will have to then apply this everywhere throughout the game, for each existing string that may be wrapped:

Easily done with a few helper functions: DisplayRtl, SayRtl, DrawStringWrappedRtl, Label.SetTextRtl...
Yes, you might have to implement custom dialog options rendering, but again, this seems like an edge-case of an edge-case.

Well, then someone will have to help with these script functions, or write a module...

At least in case of a speech one would have to comply to the undocumented width calculations of the speech overlays. Or write completely custom speech to not depend on that.

Another thing to consider is that you will have to add these in your game if you even guess that it might have Arabic/Persian translation, because you cannot add these helper functions through the translation file itself. If the game was already done when you realized that you want such translation, then you'll have to edit all the text assignments and Say calls in script.
#2311
Quote from: Snarky on Fri 14/04/2023 12:37:23
Quote from: Crimson Wizard on Fri 14/04/2023 12:30:42Afaik this is what Mehrdad is currently doing: he puts linebreaks himself, everywhere where necessary.

Right, that's the simple solution, but what I meant was that you could also just write a "reversed-pRTL" (pseudo-left-to-right) linebreak algorithm in script (measuring line widths from the end of the string, and reordering the lines so that the end of the string is at the top), rather than implement it in the engine.

I guess you may try that, except you will have to then apply this everywhere throughout the game, for each existing string that may be wrapped:
- when assigning a string to gui labels;
- when calling Display (and variants);
- when calling DrawingSurface.DrawTextWrapped.
- when displaying a speech (so have to write custom speech function);
- when displaying dialog options (so have to write custom dialog option rendering)

This will also make adding e.g. Arabic / Persian translations to existing games quite difficult.

Implementing this as an option to text wrapping algorithm would likely cover this case for the time being (again, I cannot tell when is the new font render estimate, and which new problems that would bring).

Quote from: Snarky on Fri 14/04/2023 12:37:23(Right-alignment as an option in more GUI Controls would be useful, but shouldn't be tied to a RTL setting.)

Controls have alignment setting, but it's not that, they do not reverse text, ever. I don't know why; this is either a regression in the new 3.* engine, or that RTL feature was never complete.
This won't be an issue with this converter solution though, as it does not need text reverse.

Actually, if we have the font render that can account for direction control chars, as eri0o mentioned, maybe we won't need RTL setting in the engine at all (rather than for backwards compatibility).
#2312
Quote from: Snarky on Fri 14/04/2023 09:29:05So is it really worth it, or would it be better to devote that energy to a proper solution down the road?

Indeed, it would be better to aim a proper solution. The thing is, I don't know when this will be done, in which version etc, and at least few users already asked for this to work (and some been asking for years prior).

Before this whole conversation I was not fully aware of how RTL is stored internally normally, so I needed to test this out anyway.

In regards to the quick solution that I've been testing, so far it is this:
* expand the existing "Right to left mode", adding a new choice (Right-to-left reversed);
* if this option is on, then when doing text splitting scan the text from right to left.
* also set "alignment to right" as with normal RTL.
* everything else stays the same.

I spent 3-4 hours yesterday adding this option and testing things, but got stuck at the line splitting, because the algorithm is hardcoded for left-to-right scanning. So I wanted to look if I will be able to write a "generic" one instead.

Meanwhile, I also found and fixed 2 actual bugs in 3.6.0, so that was not a fully wasted time...


Quote from: Snarky on Fri 14/04/2023 09:29:05Is it possible that some of the inconveniences can even be solved in script without any engine changes, simply by reversing the strings, doing any manipulations on them (including, potentially, finding the linebreak points, reordering the lines and inserting explicit linebreaks), and re-reversing them? (Or not re-reversing them, in the case of some GUI Controls apparently.)

Afaik this is what Mehrdad is currently doing: he puts linebreaks himself, everywhere where necessary.

Another workaround, which I mentioned above, is to keep using this converter program, but turn off the option to "reverse" text during conversion. In this case it works in the game (with normal RTL setting), but it makes all the texts look reverse in script and translation file, obviously making it inconvenient to the user (I am unaware if human can normally read Arabic and Persian in reverse same way as e.g. English can be read in reverse).
#2313
Quote from: eri0o on Fri 14/04/2023 01:23:14Just to try to understand, all the context of what you are talking here is ags3?

Yes, because it needs enhancing unicode feature to let use these certain languages properly; and because other solutions require major changes to the font rendering, to which we do not have any estimate yet.

Quote from: eri0o on Fri 14/04/2023 01:23:14Because on ags4 (we are unicode only there, right?) we could use a bidi library

The case I'm trying to resolve is the "incorrect" unicode RTL text, which is stored in memory in reverse way compared to what it should normally be (it does not have these direction control characters). Because of that it has to be drawn as if it were LTR, but wrapped differently.

The reason why this kind of text data is used is because our font renderer does not have a correct Arabic, Persian etc ligature support. So user is relying on special converter that generates a text represented by a different kind of data, meant to be displayed with very particular fonts (I mentioned this  in a comment above).
If the font renderer was drawing these languages correctly, with proper ligatures, then this converter hack would likely not even be necessary. The proper solution would require at least a replacement of a font renderer to something modern (I guess that's "Harfbuzz" that you mentioned). But until then users of these languages have to resolve to this hack, whether that is ags3 or ags4.

Currently I'm looking for the minimalistic solution to the problem, with as little changes as possible.
#2314
The preliminary (unoptimized) version of "reverse RTL" solution code may be found here:
https://github.com/ivan-mogilko/ags-refactoring/tree/361--reversertl

It's unoptimized, because it does double text reverse when splitting (once before the split, and then second time after). I would have to somehow rewrite the splitting algorithm and make it capable of reading forwards and backwards...
#2315
Also, I found something weird again; apparently there are gui controls that never reverse the text in RTL mode: Buttons and ListBoxes.

Basically, only text that supports wrapping does this: Labels, speech, display boxes.

I'm not sure if dialog options or drawing text on drawing surface does this... DrawTextWrapped probably does, since it wraps.

This is like that at least since AGS 3.2.1.
#2316
Quote from: actaria on Thu 13/04/2023 22:34:37Perfectly working thank you didn"t know about the "always" thing

There's some information about these, and explains the difference:
https://adventuregamestudio.github.io/ags-manual/RepExec.html
#2317
"Enter room before fade-in" is a correct event for initializing anything inside the room.

If there's a similar initialization or action that has to be done for a number of rooms, then use "on_event" with eEventEnterRoomBeforeFadein in the GlobalScript:
https://adventuregamestudio.github.io/ags-manual/Globalfunctions_Event.html#on_event
#2318
If you are going to have a lot of items picked up like that, I may also suggest to write a global function that does most things. This way you will be able to reduce code duplication - that is write the basic algorithm once, and then just call it with some parameters.

For example:
in GlobalScript.asc
Code: ags
function PickupObject(Object *obj, InventoryItem *itemToAdd, eDirection dirToFace)
{
    player.FaceDirection(dirToFace);
    player.LockView(3);
    player.Animate(0, 0, eOnce, eNoBlock);
    while (player.Animating)
    {
        if (player.Frame == SOME_FRAME)
            obj.Visible=false;
        Wait(1); // Wait lets engine to update and redraw the scene
    }
    player.UnlockView();
    player.AddInventory(itemToAdd);
}

in GlobalScript.ash (header)
Code: ags
import function PickupObject(Object *obj, InventoryItem *itemToAdd, eDirection dirToFace);

Then in the room script you may do this:
Code: ags
function oCacerolas_PickUp()
{
    player.Walk(1037, 938, eBlock, eWalkableAreas);
    PickupObject(oCacerolas, iCacerolas, eDirectionUpRight);
    player.Say("I've the pans with me!!!.");
}

function oAnotherObject_PickUp()
{
    player.Walk(1037, 938, eBlock, eWalkableAreas);
    PickupObject(oAnotherObject, iAnotherObject, eDirectionDown);
    player.Say("I got AnotherObject!");
}

You may add more parameters to this, or reuse existing ones for other purposes. For example, you may use dirToFace parameter to select an animation loop: this will let you to play different animations depending on which direction the character is facing.
#2319
The solution is to run animation non-blocking and then wait until it finishes right in the same function.
If you will additionally check for the current frame, you will know when to do object changes.

Code: ags
cPerxeo.LockView(3);
cPerxeo.Animate(0, 0, eOnce, eNoBlock); // notice eNoBlock
while (cPerxeo.Animating)
{
    if (cPerxeo.Frame == SOME_FRAME)
        oCacerolas.Visible=false;
    Wait(1); // Wait lets engine to update and redraw the scene
}
cPerxeo.UnlockView();
player.AddInventory(iCacerolas);
cPerxeo.Say("I've the pans with me!!!.");

I also removed unnecessary LockView(1), and moved UnlockView right to where animation completes.

Note that if cPerxeo is a player character, you could just write player everywhere. This is recommended to do, in case something changes about the character, or you will have a game where player character may be switched for any reason.
#2320
From the engine's perspective it will of course be desired that there's a minimal amount of settings and "types" of data, and most conversions were done by compilers. For example, if compiler could inverse the "reversed" RTL text before packaging the game.

But in our case this will either be impossible or difficult to achieve, as the game script is allowed to have strings in multiple languages, and scripting language allows to switch Text Direction at runtime (see SetGameOption(OPT_RIGHTTOLEFT)). This assumes that script strings may contain text of all kinds.

Resolving this at game compilation time would likely require a new preprocessor feature, using some kind of annotations added to the string literals. And that would definitely bring more issues with texts loaded from custom files.

Considering the above, the easiest option at the moment is to add a new mode to OPT_RIGHTTOLEFT, and fix all places where OPT_RIGHTTOLEFT has a meaning, accounting for this new mode.

I.e, OPT_RIGHTTOLEFT will be:
0 - normal LTR;
1 - normal RTL (that is - characters stored in the order of natural reading);
2 - pre-reversed RTL (that is - characters stored against the order of natural reading, and in order of how they are displayed left-to-right).

How the engine handles the new mode 2 is purely internal to the engine. If new font libraries are added later, which may somehow affect the situation, this behavior may be adjusted accordingly.
SMF spam blocked by CleanTalk