Trying out MonoAGS (AGS 5 candidate) : journal

Started by Monsieur OUXX, Wed 02/05/2018 14:56:42

Previous topic - Next topic

Monsieur OUXX

#60
I'm seeing interesting things in AGSSplashscreen.cs :

About Tweens :
Question 1 : If I understand the code correctly, you can have tweens that do not update automatically, but instead you let them give you a dummy function (_visitTween) when you create them, and then it's your responsibility to call that function whenever you want?
Question 2 : Is there a way to let the Tweens do their thing without having to call that function? (in a normal context where the game is not busy loading resources, like it's the case in the splash screen). I suppose yes, by calling "Run" instead of "RunWithExternalVisit".

About onRepeatedLyExecute :
I see that you actually call it yourself (insteads of letting the game engine do it automatically), by making the function call itself recursively every Task.Delay(5).
Question 3: Isn't there a risk that the recursive calls stack explodes after some time? Or am I being a dinosaur for even thinking that it's still (ever was) a thing?
 

tzachs

Quote from: Monsieur OUXX on Wed 09/05/2018 11:41:30
Question 1 : If I understand the code correctly, you can have tweens that do not update automatically, but instead you let them give you a dummy function (_visitTween) when you create them, and then it's your responsibility to call that function whenever you want?
You understood correctly.

Quote from: Monsieur OUXX on Wed 09/05/2018 11:41:30
Question 2 : Is there a way to let the Tweens do their thing without having to call that function? (in a normal context where the game is not busy loading resources, like it's the case in the splash screen). I suppose yes, by calling "Run" instead of "RunWithExternalVisit".
Right again.
In case you missed it, there's a few dozens of built in tweens, and they are implemented as a one-liner using Tween.Run: https://github.com/tzachshabtay/MonoAGS/blob/master/Source/Engine/AGS.Engine/Misc/Tweening/Tweens.cs

Quote from: Monsieur OUXX on Wed 09/05/2018 11:41:30
About onRepeatedLyExecute :
I see that you actually call it yourself (insteads of letting the game engine do it automatically), by making the function call itself recursively every Task.Delay(5).
Question 3: Isn't there a risk that the recursive calls stack explodes after some time? Or am I being a dinosaur for even thinking that it's still (ever was) a thing?
You're not a dinosaur, but there's no risk (I think), because it's using async which means it's not a direct continuation of the call stack (every time the logic continues after Task.Delay it's a new stack).

Monsieur OUXX

#62
My roadmap is as follows :
(IMPORTANT: I'm not trying to write a AGS-->MonoAGS converter, I'm just trying to have something that produces a basic game as fast as possible even without an actual Editor in MonoAGS)

1) I was going to write a small program that splits game.agf into xml fragments (definitions files) carefully organized into file-system subfolders, but then I got lazy when I realized that I have no idea how to read the acsripte file. So I got lazy and instead, for now, I'll export a game with the XAGE exporter plugin and see what files structure is produces. Maybe a combination of both methods. Anyway, for now, the quicker the better.
2) Write a few loader classes (factories, really) that load these XML fragments into configuration classes and then construct the corresponding classes (Character, Room, whatever).
3) Write a set of wrappers implementing ICharacter, IRoom, etc. that will perform the most basic initialization tasks (from the configuration files mentionned above), and, more interestingly, add a few overridable methods (the most obvious being : "RepeatedlyExecute") so that all a complete MonoAGS newbie has to do is to make their classes (Rooms, Characters, etc.) inherit from those classes. Then they will know instantly where to add custom code. I know it's not ideal (or possible) to make a MonoAGS game work exactly like an AGS game, but as I said the goal here is to have a prototype game as quickly as possible with as little hard-coded script as possible. From my perspective, the more code hidden under the hood (for a start), the better.

==========

Question 4: what core feature does not work in MonoAGS? Does save/load work? That's super duper important and that's the most difficult task. I would be pretty pissed off if I spent several months developing in MonoAGS only to realize that the difficulty of Save/load implementation was underestimated and the project stalled.

Question 5 : is there a more natural way of implementing "RepeatedlyExecute" than the one relying on recursive call (which is targetting specifially the splash screen, that is a room with very unstable framerate)? I've seen in one of the source files that you issue a warning about Repeatedly Execute, that might cause an infinite loop of calls/dependencies if not used properly. Can you produce a simple example of : a) such infinite loop issue, b) a proper way of doing things?
The naive, dirty approach that comes to mind would be for the scripter to define an arbitrarily stack storing the order in which to call the repeatedlyExecute method of each object, just like scripts in AGS can be moved up and down and that defines the order of execution.

=================

I would suggest to make the help articles (those ones : https://github.com/tzachshabtay/MonoAGS/tree/4f0189c1bebe15b4d36a0e8c34dea6c5f0120399/Docs/articles ) easier to find.
When you're here, you have no idea that they exist : https://github.com/tzachshabtay/MonoAGS/tree/4f0189c1bebe15b4d36a0e8c34dea6c5f0120399
When you're here, either : https://github.com/tzachshabtay/MonoAGS/blob/4f0189c1bebe15b4d36a0e8c34dea6c5f0120399/Docs/index.md
When you're here, you still don't know that they exist : https://tzachshabtay.github.io/MonoAGS/
And here either : https://github.com/tzachshabtay/MonoAGS/blob/4f0189c1bebe15b4d36a0e8c34dea6c5f0120399/Docs/articles/getting-started.md

I know I've stumbled upon a page where they were listed on the left, but I cannot find how to go back to that page.
 

tzachs

Quote from: Monsieur OUXX on Wed 09/05/2018 15:18:04
Question 4: what core feature does not work in MonoAGS? Does save/load work? That's super duper important and that's the most difficult task. I would be pretty pissed off if I spent several months developing in MonoAGS only to realize that the difficulty of Save/load implementation was underestimated and the project stalled.
Save/Load game does not work now. I don't think I'm underestimating the task, as I've never given an estimation: I have no idea how long it's going to take to get it right. Same goes for having a complete editor.
Those are both very complicated things to get done right and I expect they will come through several iterations until they will perform reliably and be stable.
Note though, that the fact that built in save/load doesn't work does not mean you can't roll out your own. Depending on your game, specific save/load systems might be much easier to create than a generic works-for-everyone system: especially if you do saved checkpoints, which would be very easy (relatively) to implement.

Other core features (that I know of) from AGS which are not implemented yet: Object.Solid (characters will walk through each other), playing videos, text parser, translations, lip syncing, non 32-bit games (and color palettes).
Also you can see missing APIs in the cheat-sheet: https://tzachshabtay.github.io/MonoAGS/articles/ags-cheat-sheet.html#api-comparisons

Quote from: Monsieur OUXX on Wed 09/05/2018 15:18:04
Question 5 : is there a more natural way of implementing "RepeatedlyExecute" than the one relying on recursive call (which is targetting specifially the splash screen, that is a room with very unstable framerate)? I've seen in one of the source files that you issue a warning about Repeatedly Execute, that might cause an infinite loop of calls/dependencies if not used properly. Can you produce a simple example of : a) such infinite loop issue, b) a proper way of doing things?
The naive, dirty approach that comes to mind would be for the scripter to define an arbitrarily stack storing the order in which to call the repeatedlyExecute method of each object, just like scripts in AGS can be moved up and down and that defines the order of execution.

You can subscribe to repeatedlyExecute from the game events: "game.Events.OnRepeatedlyExecute.Subscribe(onRepeatedlyExecute);".
I don't remember any warning about an infinite loop, the docs for this event have warnings about not to do slow operations or intensive memory allocations from repeatedly execute (same warnings apply to classic AGS as well) so maybe that's what you were referring to? Those same docs also give good/bad examples for those issues: https://tzachshabtay.github.io/MonoAGS/api/AGS.API.IGameEvents.html#AGS_API_IGameEvents_OnRepeatedlyExecute

Quote from: Monsieur OUXX on Wed 09/05/2018 15:18:04
I would suggest to make the help articles (those ones : https://github.com/tzachshabtay/MonoAGS/tree/4f0189c1bebe15b4d36a0e8c34dea6c5f0120399/Docs/articles ) easier to find.
When you're here, you have no idea that they exist : https://github.com/tzachshabtay/MonoAGS/tree/4f0189c1bebe15b4d36a0e8c34dea6c5f0120399
When you're here, either : https://github.com/tzachshabtay/MonoAGS/blob/4f0189c1bebe15b4d36a0e8c34dea6c5f0120399/Docs/index.md
When you're here, you still don't know that they exist : https://tzachshabtay.github.io/MonoAGS/
And here either : https://github.com/tzachshabtay/MonoAGS/blob/4f0189c1bebe15b4d36a0e8c34dea6c5f0120399/Docs/articles/getting-started.md

I know I've stumbled upon a page where they were listed on the left, but I cannot find how to go back to that page.
So you were looking at the documents on github instead of in the documentation website. The documents in github is the source which triggers the generation of the website.
In the main readme on github you linked to there's a link to the documentation website, it's both in the top of the page and in the bottom of the page.
Here's the link again: https://tzachshabtay.github.io/MonoAGS/index.html
From the documentation website you have a link to the articles and api on the top, and the getting started link (or any article link) have the list of articles in the left.

Monsieur OUXX

#64
I'm very confused because none of the pages you mentionned have any links on the left, nor at the top, nor at the bottom. I only see the article. Maybe it's because they're iframes blocked by my current firewall at work (they have a very restrictive policy).

So anyways, don't spend any more time on that, it's not on you.

Spoiler


[close]

================

About repeatedlyExecute : the "warning" I was talking about is in AGSGame.cs :
Quote
177                 //Invoking repeatedly execute asynchronously, as if one subscriber is waiting on another subscriber the event will 
178                 //never get to it (for example: calling ChangeRoom from within RepeatedlyExecute calls StopWalking which 
179                 //waits for the walk to stop, only the walk also happens on RepeatedlyExecute and we'll hang.
180                 //Since we're running asynchronously, the next UpdateFrame will call RepeatedlyExecute for the walk cycle to stop itself and we're good.
181                 ///The downside of this approach is that we need to look out for re-entrancy issues.
182                 await Events.OnRepeatedlyExecute.InvokeAsync(_repeatArgs);

 

tzachs

Quote from: Monsieur OUXX on Wed 09/05/2018 16:26:36
I'm very confused because none of the pages you mentionned have any links on the left, nor at the top, nor at the bottom. I only see the article. Maybe it's because they're iframes blocked by my current firewall at work (they have a very restrictive policy).

So anyways, don't spend any more time on that, it's not on you.
8-0

Hmm, just to make sure: which browser and version are you using?
Assuming you're running on chrome, if you right-click the page and click inspect, and then switch to the console tab, do you see any errors?

Quote
About repeatedlyExecute : the "warning" I was talking about is in AGSGame.cs :
Ah, that's a comment meant to explain why the engine invokes the function asynchronously (to any future engine developer or a future version of myself), it's not relevant to how a game developer would use the event.

Monsieur OUXX

#66
Quote from: tzachs on Wed 09/05/2018 17:24:42
which browser and version are you using? any errors in the inspector?
IE11, in "non-Edge" mode. Pah, just ignore it, it's either my damn workplace firewall or that shit browser. That might explain how I knew that the articles exist while not being able to remember how I saw them earlier : probably because everything works as expected when I'm at home on Firefox.
 

Monsieur OUXX

#67
I'm facing issues because the injection of dependencies makes some bits of code too abstract for me.
Let me explain what I'm failing to do :
- I want to load some XML files using a custom class of my making.
- For that, I need the Resource Loader (either the FileSystem one, or the other one)
- to get the actual instanciated object, I need the device instance
- All the device instances I see are int he factories, which are constructed automatically by the injection of dependencies (I guess! since I see their contructor get called nowhere in the code).
- But I don't want my class to be one of the built-in factories. I want it to be part of my custom library built on top of MonoAGS.

============

Also, pretty cool Facebook page : https://www.facebook.com/mono.ags
 

Crimson Wizard

#68
Quote from: Monsieur OUXX on Wed 09/05/2018 23:21:14
- But I don't want my class to be one of the built-in factories. I want it to be part of my custom library built on top of MonoAGS.

I may not understand something from your intentions, but I believe you could simply require passing reference to factories to your loader constructor, create empty object using factory, then load your XML and fill the object's fields, then return object.

As a very primitive conceptual example:
https://github.com/ivan-mogilko/MonoAGSGames/blob/master/Games/LastAndFurious/Rooms/RoomScript.cs#L48
Code: csharp

protected async Task<IObject> addObject(string name, string gfile, int x = 0, int y = 0)
{
    IObject o = _game.Factory.Object.GetObject(name);
    o.Image = await _game.Factory.Graphics.LoadImageAsync(_roomAssetFolder + gfile);
    o.X = x;
    o.Y = y;
    return o;
}


As you may see, my "object creator" function uses one factory to create an empty object, another factory to load an image, then combines them and sets some parameters, then returns resulting object.

You may expand it to a class that does the same by reading parameters from XML.

EDIT: imho since now these are code-related questions, maybe it would be more convenient if you wrote some prototype, even non -working with dummy functions, as something we could observe and refer to.

Monsieur OUXX

You're using factories that already exist : "Object" and "Graphics". Both already have their resource-loading function : LoadImageAsync. That function, behind the scene, uses the IResourceLoader that has already been automatically provided to that factory.
I want to write my own class that loads resources (XML files) from the disk, and I don't know how to get the IResourceLoader instance.

Are you saying that I should just use "Object"? Isn't that only meant for room objects?

 

Crimson Wizard

#70
Quote from: Monsieur OUXX on Thu 10/05/2018 00:36:03
You're using factories that already exist : "Object" and "Graphics". Both already have their resource-loading function : LoadImageAsync. That function, behind the scene, uses the IResourceLoader that has already been automatically provided to that factory.
I want to write my own class that loads resources (XML files) from the disk, and I don't know how to get the IResourceLoader instance.

Oh.

game.Factory.Resources <-- this is IResourceLoader.

But you could make your loader class have a constructor that requires user to pass IResourceLoader to you, and not worry about that yourself. When they create your loading-from-XML class, they will assign IResourceLoader from the game instance.*

*by "user" I mean the developer who uses your library, ofc. And that may be also you :).

I apologize, if it's still not what you need. I believe it would be better if you draw some chart, or write dummy code to elaborate on your idea. Maybe start with how your loader itself is going to be created in the game, - that may define available options.


Quote from: Monsieur OUXX on Thu 10/05/2018 00:36:03
Are you saying that I should just use "Object"? Isn't that only meant for room objects?

Object class may be used for most things visible on screen, it is used as a parent class for Character, and also for GUI, if I remember correctly. In most cases you may use it and add more Components to expand it further for advanced behavior.

For more bare entities, you may create IEntity, although I do not remember how.

Snarky

I realize you're sticking with "Object" and "Character" from AGS, but is it too late to change that? It makes it much more annoying to discuss scripting when you have the ambiguity with the standard data types.

I like the SCUMM convention where Rooms are Scenes, Objects are Props and Characters are Actors, and would suggest that as an alternative.

Crimson Wizard

#72
Quote from: Snarky on Thu 10/05/2018 08:41:19
I like the SCUMM convention where Rooms are Scenes, Objects are Props and Characters are Actors, and would suggest that as an alternative.

To clarify more, in MonoAGS Object is not a "prop", it is a default class for anything that has position and image. It is not necessarily belongs to the room too.
It's actually more basic than a "Room Object" from AGS, beause it does not have any interactions by default.

Monsieur OUXX

#73
Quote from: Crimson Wizard on Thu 10/05/2018 00:43:48
game.Factory.Resources <-- this is IResourceLoader.
Oh OK. Duh. I'll see how I can work with it.

Quote from: Crimson Wizard on Thu 10/05/2018 00:43:48
But you could make your loader class have a constructor that requires user to pass IResourceLoader to you
Well I'd still be stuck with the same issue, as I'm ultimately the user (I'm making the demo game too) and I don't know where to get the (implemented and constructed) IResourceLoader. See my commant about game.Factory.* below


Quote from: Crimson Wizard on Thu 10/05/2018 00:43:48
I believe it would be better if you draw some chart, or write dummy code
Well all I want to do really is to write a "XML.Load(path)" function, but to make sure that it uses the abstract resources system of MonoAGS instead of just hard-coding the reading of the file from the hard drive.


EDIT: dammit, this is just too abstract for me.
Let's take the simple example of how AudioClips are loaded, and now imagine that I want to do the same with an XML file, the only difference being that my classes are in my own library instead of MonoAGS project.

Here is how it's done in MonoAGS :

game.Factory.Sound.LoadAudioClipAsync(path)
  |
   -----> IAudioFactory implementation
            |
             ----->  LoadAudioClip(path)
                       |
                        ---->  loadSoundData(path)
                                |
                                 ----> IResourceLoader (or is it IResourcePack ? I don't even understand) implementation : _loader.LoadResource(path)
                                         |
                                          ---> Finally, the basic file-opening code.

I'm just not good enough to be able to mimic that cleanly (some of the interfaces being in the engine, the other being in the API project), without just writing a function that opens a file from the hard drive and reads it.

I don't even understand how game.Factory.* are populated, when and by who.
 

Crimson Wizard

#74
Quote from: Monsieur OUXX on Thu 10/05/2018 11:17:08
I'm just not good enough to be able to mimic that cleanly (some of the interfaces being in the engine, the other being in the API project), without just writing a function that opens a file from the hard drive and reads it.

Ok, I am a bit confused right now, not quite sure what exactly you want to mimic and why.

But assuming you want to go simple for starters, all you need is a pointer to IResourceLoader, or rather pointer to IGameFactory (that includes a set of all default factories). You library won't, and perhaps should not know where the actual factories are created, they only need to require a pointer to one.

For instance, this is a mockup example of a simple class that creates stuff from XML. User needs to pass IGameFactory instance into constructor.

Code: csharp

public class LoaderThatCreatesAGSObjectsFromXML
{
    IGameFactory _factory;

    public LoaderThatCreatesAGSObjectsFromXML(IGameFactory factory)
    {
        _factory = factory;
    }

    public List<IObject> LoadRoomObjects(string resourceName)
    {
        // get named resource
        IResource r = _factory.Resources.LoadResource(resourceName);
        // parse the stream into XML document
        XMLDoc doc = LoadXMLFromStream(r.Stream);

        // create a list of objects, reading their properties from the doc
        List<IObject> objs = new List<IObject>();
        int count = doc.GetValue("numobjs");
        for (int i = 0; i < numobjs; ++i)
        {
            // create blank object
            IObject newObj = _factory.Object.GetObject("bla");
            // set parameters
            newObj.SomeParam = doc.GetValue("blabla");
            objs.Add(newObj);
        }
        return objs;
    }
}


This class will read named resources using provided abstract IResourceLoader, which was configured by the engine, or game specifically, without knowing (or bothering) where those resources come from (file on disk, zip archive, net connection etc).


And here is a dummy example of using this class in a game:
Code: ags

protected void setupRoom(IGame game, string roomFileName)
{
    LoaderThatCreatesAGSObjectsFromXML loader = new LoaderThatCreatesAGSObjectsFromXML(game.Factory);
    List<IObject> room1objs = loader.LoadObjectsFromXML(roomFileName);
    // do something
}

As you may notice, here the game developer simply gives your class the factories created by the engine.

tzachs

Quote from: Snarky on Thu 10/05/2018 08:41:19
I realize you're sticking with "Object" and "Character" from AGS, but is it too late to change that? It makes it much more annoying to discuss scripting when you have the ambiguity with the standard data types.

I like the SCUMM convention where Rooms are Scenes, Objects are Props and Characters are Actors, and would suggest that as an alternative.
It's not too late to change.
It annoys me too, but I opted for keeping familiarity to AGSers to be higher priority than ambiguity when I think that ambiguity is tolerable (i.e I don't think I actually use the dotnet object anywhere in the code, and the keyword for characters is char). But I agree that it can be confusing when discussing or reading c# general tutorials.
I'm open to change names if there's a majority agreement, although slightly concerned as I think the early adopters represent the more advanced AGSer who might cares less about familiarity than mainstream AGSers.
About the names themselves:
- Room vs Scene: I find scene to be a better name than room too, but there's also no ambiguity here, so Room doesn't actually bother me that much. I'm ok either way.
- Object vs Prop: Like CW said, I'm not sure prop covers all scenarios for object. An object in MonoAGS is something that has a transform (move, scale, rotate), an image/animation, a collider, and can be placed in a room or as a GUI. So all characters and guis are also objects. Some alternative names that come up: "Drawable", "Moveable", "Thing"?
- Character vs Actor: I have a problem with Actor because it has ambiguity with the actor model which is a model used to utilize safe concurrency, and we might choose to use it in the future. Alternative names that I can think of all suck: "Creature", "Person", "Individual"?

@Monsieur OUXX: I'm going to answer your questions to try helping you better understand the inner workings of the engine, though I agree with CW, you don't actually need that, the example he gave is what you should probably do, unless I misunderstood what you want to do.

First of all, any API that you can't find a direct reference to and want to use, you can try resolving it. So:
Code: csharp

using AutoFac;

var resourceLoader = AGSGame.Resolver.Container.Resolve<IResourceLoader>();
var device = AGSGame.Resolver.Container.Resolver<IDevice>();


Although like CW said, resource loader can be found in "game.Factory.Resources" (and device can be found in AGSGame.Device).
The resolver is how the engine gets the concrete implementations injected to it (and also how you can give it different implementations and customize almost everything imaginable), and how the factory is populated.

Quote
IResourceLoader (or is it IResourcePack ? I don't even understand) implementation : _loader.LoadResource(path)
It's resource loader. The resource loader has multiple resource packs to allow loading resources from multiple sources. Currently there's a "file system" resource pack and an "embedded resource" resource pack, but in the future we can have a "get from web" resource pack, "get from ftp" resource pack, etc.

Regarding the actual example CW gave, here's a minor improvement you can make (you might argue that it's not an improvement but at least know you have the option). You can access the game statically with "AGSGame.Game" as well.
So you can provide a default in your class and allow the user not to think about it:
Code: csharp

public class LoaderThatCreatesAGSObjectsFromXML
{
    IGameFactory _factory;
 
    public LoaderThatCreatesAGSObjectsFromXML(IGameFactory factory = null)
    {
        _factory = factory ?? AGSGame.Game.Factory;
    }

    ...


And then you can call it like CW showed, or without giving the factory: "var loader = new LoaderThatCreatesAGSObjectsFromXML();"

Crimson Wizard

#76
EDIT2: Aw, scrap that, there are types that always resolve to a single instance apparently, there is what SingleInstance() is for. :sealed:
This kind of makes things simplier, but only if you know for sure that they are registered as single instance.


EDIT: Oh, and the actual custom implementations are registered like this:
Code: csharp

Builder.RegisterType<MyOwnResourceLoaderClass>().SingleInstance().As<IResourceLoader>();

If you do that, every next IResourceLoader created with the use of Resolver will be actually your class.
TBH I do not know at which point you need to call this command, probably before IGame is created?

Personally, I'd prefer to stay away from this Resolver stuff if possible, because it will affect everything in the engine.

tzachs

Quote from: Crimson Wizard on Thu 10/05/2018 16:00:52
I would like to add, that although indeed you can create a ResourceLoader from resolver, that will be a separate new object, not the one from game.Factory.
That's not true, it will be the same object that the game factory gets, because it's registered by the engine as a single instance.

Quote from: Crimson Wizard on Thu 10/05/2018 16:00:52
TBH I do not know at which point you need to call this command, probably before IGame is created?
If you want to override engine behavior, then yes, you need to do it before IGame is created.

Quote from: Crimson Wizard on Thu 10/05/2018 16:00:52
Personally, I'd prefer to stay away from this Resolver stuff if possible, because it will affect everything in the engine.
Using the resolver for changing behaviors can affect things in the engine, and you need to know what you're doing.
But as for resolving things from the resolver, that wouldn't have an effect (worst case scenario is that you won't be able to resolve something because it's missing a parameter and you'll get an exception immediately).

Crimson Wizard

Quote from: tzachs on Thu 10/05/2018 16:19:01
Quote from: Crimson Wizard on Thu 10/05/2018 16:00:52
I would like to add, that although indeed you can create a ResourceLoader from resolver, that will be a separate new object, not the one from game.Factory.
That's not true, it will be the same object that the game factory gets, because it's registered by the engine as a single instance.

I've got a question, is this single instanceness enforced for an interface somehow? For example, if someone will re-register their own implementation, can they do change it to multiinstance? Because if they may, that would mean that the common/library code cannot rely on interface being single-instanced.

tzachs

It's not enforced (and I don't think it can be enforced).

SMF spam blocked by CleanTalk