Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AbstractGroups #15

Open
baedan opened this issue Aug 8, 2022 · 18 comments
Open

AbstractGroups #15

baedan opened this issue Aug 8, 2022 · 18 comments

Comments

@baedan
Copy link

baedan commented Aug 8, 2022

hi!

i've been enjoying Overseer.jl a lot for writing an auto-battler. thanks for the good work.

could you explain a little how to use AbstractGroup? i think i might have a use case that would be made easier with it, but having looked at the source code, i'm still not totally sure what's going on and what it's meant to do. thanks a bunch.

@louisponet
Copy link
Owner

Hi,

Awesome that you're enjoying the package!

So Groups should be considered experimental, I haven't worked on them in a long time, and honestly never quite got them to the point of really nailed down. The idea is that a Group of components simply means that the ordering of shared entities is identical in each of the components meaning that while iterating through the entities of said Group of components would not require any checks and the iterator would have a known length (right now the @entities_in iterator loops through the entities in the shortest component and runs a check whether it's in all of the components otherwise continues). It's mainly for increased performance, though it can also be extremely useful when working with the gpu since you can simply copy the memory of the components over to the gpu without having to rearrange everything.

What usecase are you thinking of?

@baedan
Copy link
Author

baedan commented Aug 10, 2022

i see, thanks for the explanation.

i'm considering making a trigger resolution system that process entities representing triggered abilities. still brainstorming idea, so this is pretty rough.

i've come up with two ways to go about it: first is to have a triggered ability by represented by an entity which has a parametric component type TriggerComponent{T<:AbstractTrigger}, where T would be a type of trigger (for example BeginningOfTurnTrigger; and TriggerComponent would have a field pointing to an effect (which should probably be an entity, i think) that is triggered. and i would have parametric TriggerResolutionSystem{T<:AbstractTrigger} managing each kind of triggered abilities.

the second way is to have two components, respectively containing the trigger type (e.g. like an empty TriggerTypeComponent{<:AbstractTrigger}) and the triggered effect; and i thought the two might well be contained in a Group (or a parametric subtype of AbstractGroup) which is processed by a trigger processing system. not sure about the performance characteristics of either option. idk, thinking about how to make a trigger system (or a 'stack` system in MTG parlance) in ECS without coupling systems together has fried my brain a little bit, haha.

@louisponet
Copy link
Owner

louisponet commented Aug 10, 2022

Interesting, is it similar to what I'm doing here to have some sort of "modal" system for tui's?

Is it a specific entity has a specific ability triggered, or more that an ability is "active" influence the whole board or world let's say.
From the beginning of turn trigger example it seems like the latter which to me feels similar to what I linked above.

EDIT: From the point of using groups for this, I don't think that's going to help you here, it really is just another "bookkeeping" tool

@baedan
Copy link
Author

baedan commented Aug 10, 2022

yeah, i think it's somewhat similar. this part is pretty cool; i came up with something kind of similar

struct TriggerAware{T<:System} <: System
    system::T
end

Overseer.requested_components(s::TriggerAware{T}) where {T} = (TriggerComponent, requested_components(s.system)...)

function Overseer.update(s::TriggerAware{T}, m::AbstractLedger) where {T}
    update(s.system, m)
    while !isempty(@entities_in(m[TriggerComponent]))
        update(TriggerSystem(), m)
    end
end

as someone trying out ECS for the first time, i was under the impression that having a system calling another system is a big "no no". good to see it's not that bad, ha.

in my case an ability is owned by an entity and is triggered by an event. for example, at the beginning of a turn, a cat deals 7 damage to a random enemy, or something to that effect.

@louisponet
Copy link
Owner

Well, technically it kind of is and you should have either one system for each possible event or one system with if-else for each possibility. If you want something more flexible I'm not sure you can do it any other way. The reason I was designing it like this is that it would potentially allow users to more easily specify tui modes etc. I'm not set on it yet.

In your case, I'm not sure there is much benefit to do this over just having one system for each event that loops over entities with the components linked to that event. I'd probably actually recommend doing that, what is the reason for not going that way? In a game usually you can update everything each turn, or each render loop.
If your turns don't come periodically, having something that triggers a given stage of systems to run (i.e. end turn button -> update game state) can be done during the external rendering loop perhaps.

@baedan
Copy link
Author

baedan commented Aug 10, 2022

i'm not sure, but my thinking is, a triggered ability (could be triggered by a variety of things, "a friend in front attacks" or "an enemy was hurt") can have arbitrary effects, which may trigger another ability. and those nested triggered abilities need to be resolved in the update loop they are generated in. so triggered abilities are like a global queue that's emptied before each system call. i think having my TriggerSystem effect the desired effects (ha) by calling an arbitrary system (which is not TriggerAware) might accomplish this. though, now systems are calling each other. but maybe limiting this strictly to the TriggerSystem might be ok?

@louisponet
Copy link
Owner

Oh I see what you mean. Yea it's kind of trying to solve the problem reactively, which is not at the moment exactly well-supported indeed. What you're suggesting and what I've kind of done in tuiseer is getting close to that. Now still, I'd suggest to also just try having a set of systems that would "trigger" an ability by putting the right component, which then would be seen during the next update loop by the next system that takes it and puts the components signalling triggered abilities that it takes care of there.
It's true that that means that things won't happen immediately, but the question is whether that's necessary or even desirable (think hmm what happened when and what triggered what when and now things are a mess, which is exactly why ECS was made not to have that).
If that really doesn't work in your case, I think what you're doing is valid

@baedan
Copy link
Author

baedan commented Aug 10, 2022

yeah, i thought about putting everything in messaging components and update them in future loops, but now in each loop (for example, AttackSystem, HurtSystem, FaintSystem etc) these systems cannot do anything before the triggers are resolved. additionally these systems now need a component to track whether they can do things. in reality it's probably not a big deal because nested ability triggers are rare and usually shallow, but aesthetically, knowing that update loops could just be spinning freely to no avail is a bit painful, lol

@louisponet
Copy link
Owner

Well, that is sort of the beauty of ECS to some degree, you have “no control” but that also means you don’t need to have control. I mean, a system will just be doing nothing unless it finds the right combo of components to then do something with. If these are one time things, using for example @safe_entities_in together with pop! Makes sure that next time it doesn’t iterate again over the same things. You can be even more explicit/fast by just having an early exit isempty return construct checking the component(s) the system acts on.
What you’d probably want to do is put the nested systems as the correct sequence in a Stage, so that first triggers are resolved, then first level of results happens, next loop second level of results propagate. I know it might feel weird, I’ve thought the same when I was working on Glimpse, but in reality often this is totally fine, and due to the innate performance of ECS shouldn’t be an issue.

@baedan
Copy link
Author

baedan commented Aug 10, 2022

interesting, that makes sense. will try to code for both versions and run some bench. thanks for your thoughts!

@louisponet
Copy link
Owner

Also, I don't know if you've looked at it before, but there's Gameoji by @c42f which is a potential source of inspiration!

@c42f
Copy link
Contributor

c42f commented Aug 11, 2022

first triggers are resolved, then first level of results happens, next loop second level of results propagate

This is basically what I do in Gamoji.

Sometimes it requires fiddling with the order of stages a bit. But generally I've found it to be fine and fairly transparent. I'm generally very concrete with my systems, I tend not to want a lot of abstraction other than what the ECS itself provides.

I wouldn't try to do standard reactive programming (ie, wiring causes and effects together) with an ECS. In fact I feel that doesn't work that well anyway for games because effects from one system can be fairly global and in general can propagate for multiple time ticks. I believe this is a feature of ECS though: it encourages you to think of systems as the "physics of the game world" - ie, generally the laws by which the game operates. Generally, thinking this way leads to

  • Emergent complexity which makes sense. But is due to the natural interaction of several systems and doesn't need to be programmed explicitly.
  • A lot less special cases in the code to fix weird and broken interactions between entities (because entities never interact directly)

@baedan
Copy link
Author

baedan commented Aug 11, 2022

I believe this is a feature of ECS though: it encourages you to think of systems as the "physics of the game world" - ie, generally the laws by which the game operates.

yeah, that's the dream for sure. along the same line i think ECS has difficulty when these laws of the game world cannot easily be captured cleanly in a regular system: system execution order, stage/mode transition (which is more for performance's sake than anything else, i think). system only interact through components, but the way they do is substantively determined by these meta-laws.

spitballing here: to abstract away system interaction through execution order, ideally a game's behavior should be execution order agnostic; perhaps every system in a loop can operate on the same old state, produce $\Delta$ components, which are aggregated (how? no obvious way to "add" for all conceptual types) and effected only at the end of a loop? that would be an attractive conceptual model, but i'm not sure about practicality

@louisponet
Copy link
Owner

louisponet commented Aug 11, 2022

I have found that indeed mostly this is actually the case (the execution order agnosticity). The reason for a given execution order is just because then certain things happen without delay, waiting for the next loop. In the real world the loop is continuous and every “system” and result happens instantaneously and so the problem of execution order of systems doesn’t at all matter. I’d say, at least in most of the things I’ve coded up, it doesn’t matter when which system runs, just that then certain effects take one frame more to appear.

EDIT: “results are instantaneous in physics” well this is a lie, but you understand what I mean 😅

@c42f
Copy link
Contributor

c42f commented Aug 11, 2022

produce Δ components, which are aggregated

Yes that could work in the context of a particular game and systems.

I don't think any mechanism is really general because game rules are some weird mixture of

  • high level "game logic" - ad hoc, discrete time, arbitrary logic
  • "actual physics" - continuous systems described by differential equations (generally requiring ODE integrators of some kind)

Systems with arbitrary high level logic do sometimes interact with each other a little weirdly. I feel like... that's a fact of life? And some case by case fixes just need to be made in those situations. But also that ECS allows such code to be relatively well factored regardless of the occasional difficulty.

For the "actual physics", the right and fully consistent thing to do is to treat all coupled systems as one big set of differential equations. But IIUC people tend to separate such systems into tightly coupled updates (which go into individual systems and are integrated together with a purpose built solver), and loosely coupled updates (which go into separate systems and only communicate at the game's tick rate)

@louisponet
Copy link
Owner

@c42f Do you often run into the situation where the exact order of the system execution matters for the actual behavior, or mostly of how fast results take effects (i.e. this tick or next tick)?

Systems with arbitrary high level logic do sometimes interact with each other a little weirdly

Do you have an example of this in Gameoji?

@c42f
Copy link
Contributor

c42f commented Aug 12, 2022

I have had trouble with ordering in the past but I can't remember the exact detail. The game previously triggered updates only when keyboard events occurred so in that case it does matter more. I've since moved to time-based updates though so it might not be such a problem now. My memory is a bit hazy to be honest! I haven't looked at the code for quite some time now.

@louisponet
Copy link
Owner

louisponet commented Aug 12, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants