Open liach opened 5 years ago
I initially didn't foresee me writing so much. Guess I can ping @Zidane for a look then :sweat_smile:
How will this support custom AITaskOccupiedFlag
s?
we can expose a factory method in game registry (most likely) for hard registration with mutex
IMO the flags should be a CatalogType, that will work similar to an enumset to resolve the contents
@st-ddt i did not make it one since block change flag is not a catalog type. might ask @gabizou on that design
The difference is that it's not possible to create custom BlockChangeFlag
s
The number of Mutex bits/AI flags is also limited. Some might not have a name by default, but they exists nevertheless. Unless we throw the bits in the ditch and invent something new that is a superset of MCs way, but might cause issues for mods that also want to use a different superset.
We can assign the actual bits dynamically based on calls to the normal goal. when the normal goal is called, we usually have an idea which bits are actually used and can substitute their flag objects with preset or generated entries
Changing existing bits or dynamically assigning them will cause conflicts with non Sponge plugins? How is a forge mod supposed to check whether the Sponge plugin uses the CastMagicFlag if the bit is always different.
@ST-DDT forge mods do not check flags. They only set flags they need. Sponge plugins will enable flags with entity update event listeners.
So the revamped flag class is:
public interface AITaskOccupiedFlag {
}
the flag set:
public interface AITaskOccupiedFlagSet {
boolean has(AITaskOccupiedFlag flag);
AITaskOccupiedFlagSet set(AITaskOccupiedFlag flag);
AITaskOccupiedFlagSet inverse();
AITaskOccupiedFlagSet union(AITaskOccupiedFlagSet flag);
AITaskOccupiedFlagSet distinct(AITaskOccupiedFlagSet flag);
AITaskOccupiedFlagSet intersect(AITaskOccupiedFlagSet flag);
}
goals and tasks use the flag set instead.
Mutex bits set by mods may be different from vanilla ones if they create completely custom ai. In that case it won't possible to assume the flag. It will always be a issue to recognise these bits in combination with mods, unless something gets changed in forge.
we can recognize those bits with hooks in the method where goals set their available bits and where ai tasks set their mutex bits.
IMO we should this part of the API as simple as possible (and close to vanilla) for the following reasons:
O(1)
vs O(n * (n/2))
VanillaAITask
interface that can be used for these behavior/flag based canRunConcurrentlyWith
checks or just some fake methods in AbstractAITask from the API, that will be implemented at runtime.TLDR: Have a getter/setter with a Set<VanillaAITask...Flag extends CatalogType>
and a method to call to check.
(If my explanation is not clear, I can write a diff with my proposed changes regarding the flags)
@ST-DDT
union
for bitwise or, distinct
for "and not", inverse
for inversing bits, and intersect
for and operation. They are definitely needed. Don't try to outdumb me.@ST-DDT I believe you have trouble understanding some of my ideas:
We can detect with injections in EntityAIBase#setMutexBits
,EntityAITasks#enableControlFlag
, and EntityAITasks#disableControlFlag
We will also fetch the agent type in context to determine for which types of agents these flags exist.
By the way, the new system I propose may supersede #1014
By the way, the new system I propose may supersede #1014
I opened that issue to track (one of) the issues of the current AI API. I will happily close that issue once we have a working variant inside the API.
We can detect with injections in EntityAIBase#setMutexBits,EntityAITasks#enableControlFlag, and EntityAITasks#disableControlFlag We will also fetch the agent type in context to determine for which types of agents these flags exist.
Wouldn't this LAZY initialization have the drawback that it might never be fully populated if certain entity types aren't spawned. What would be the difference between BIT_4 for Zombies and BIT_4 for Skeletons? Why can't I use BIT_4 for Slimes, if vanilla MC does not use it?
The primitive value thing is not that important. The flags can be sorted to make comparison much faster. It is not freaking slow.
Performance isn't my only concern there. Well AFAICT that int is the one value every custom AI mod out there is using (if they use the vanilla classes at all).
All those methods are bitwise operations. union for bitwise or, distinct for "and not", inverse for inversing bits, and intersect for and operation. They are definitely needed. Don't try to outdumb me.
Sorry, that wasn't my intention. This class can easily replaced by a Set (addAll, removeAll, retainAll), maybe the impl is backed by an int, but that doesn't mean we should avoid the java standard API for well known concepts. As for Inverse
I'm not 100% sure about its definition: Is it !MOVE = NOT_MOVE or !MOVE = LOOK + SWIM + ... ( I assume you refer to the second variant). But still what would be the usecase?
IMO Our concepts might be pretty similar, that why I try to demonstrate my thoughts using pseudo code (limited to the behavior flags).
Changes to the API:
AITask:
Set<AIFlag> getAIFlags(); // optional
void setAIFlags(Set<AIFlag> flags); // even more optional
AIFlag implements CatalogType (no additional methods)
AIFlags:
public final AIFlags {
public static final AIFlag MOVE = ...;
public static final AIFlag LOOK= ...;
...
public static final AIFlag BIT_31= ...;
}
AbstractAITask:
protected final Set<AIFlag> getAIFlags() {
Set<AIFlag> flags = new ...;
return flags ;
}
protected final void setAIFlags(Set<AIFlag>flags) {
}
protected final boolean flagBasedCanRunConcurrentWith(AITask task) {
return false;
}
API changes would be 100% backward compatible.
Changes to SpongeCommon:
MixinAbstractAITask:
@InsteadOf(Set::new) // Optional
// This might use an int internally and might have extra setters/getters to set/get the mutex bits directly
private Set<AiTask> newSet() {
return new MutexSet();
}
@After(Set::new)
protected void getAIFlags(Set<AIFlag> flags) {
for (int i in 0...31) {
if (getMutexBits() & 1 << i != 0) {
flags.add(VANILLA_FLAGS[i]);
}
}
}
@Head
protected void setAIFlags(Set<AIFlag>flags) {
int mutex = 0;
for (AIFlag flag in flags) {
if (flag instanceof SpongeAITask) { // VanillaAITask
mutex |= flag.getMutexBit();
}
}
setMutexBit(mutex);
}
@Replace
protected boolean flagBasedCanRunConcurrentWith(AITask task) {
return getMutexBits() & (EntityAIBase) task.getMutexBits() == 0;
}
Usage in SpongePlugins
MyAITask:
public class MyAITask extends AbstractAITask {
// BIT_15 = Cast Spell in this plugin and MCorcery
public MyAITask () {
setAIFlags(Set.of(AIFlags.MOVE, AIFlags.LOOK, AIFlags.BIT_15);
addFlagIfExists(registry.get(AiTask.class, "spellforce:magic_invoke"));
}
public boolean canRunConcurrentlyWith(AITask task) {
return flagBasedCanRunConcurrentWith(task) && ! criticalSection;
}
}
Extension by Third-Party mods: (if some mod ever wants to do this)
MixinEntityAIBase:
public MixinEntityAIBase implements MyAIInterface {
private long myMutexBits;
private boolean noConcurrentFlagSet;
// getter + setter (as defined by the interface)
}
MyAIFlags:
public final MyAIFlags {
public static final AIFlag NO_CONCURRENT = new MyAIFlag(...);
public static final AIFlag MY_MUTEX_0 = new MyMutexAIFlag(...); // Might be spellforce:magic_invoke
...
public static final AIFlag[] FLAGS_ARRAY = { MY_MUTEX_0, ..., MY_MUTEX_63};
}
MixinAbstractAITask:
@After(Set::new)
protected final void getAIFlags(Set<AIFlag> flags) {
if (isNoConcurrentFlagSet()) {
flags.add(MyAIFlags.NO_CONCURRENT);
}
for (int i in 0...63) {
if (getMyMutexBits() & 1 << i != 0) {
flags.add(MyAIFlags.FLAGS_ARRAY[i]);
}
}
}
@Head
protected void setAIFlags(Set<AIFlag>flags) {
setNoConcurrentFlag(false);
setMyMutexBits(0);
for (AIFlag flag in flags) {
if (flag instanceOf MyAIFlag) {
(MyAIFlag) flag.apply(this);
}
}
}
@Return
protected boolean flagBasedCanRunConcurrentWith(AITask task, boolean returnValue) {
return returnValue && (MyAIInterface) this.getMyMutexBits() & (MyAIInterface) task.getMyMutexBits() == 0 && !(MyAIInterface ) this.isNoConcurrentFlagSet();
}
We can use another tracking system for sponge-added flags besides the existing mutex. Inverse should be all other flags set to true.
For your performance concerns, we can require all plugin-based ai flags to be added before a loading stage to allow putting flags in something backed by a bitset, etc.
If you fear sponge cannot spawn mobs, in fact, sponge can (by iterating over the entity registry and spawning in a dummy world)
I'd keep my ai flag set class so that people are not silly enough to feed an array-based set or something similar to the method.
We can use another tracking system for sponge-added flags besides the existing mutex.
You are not referring to a different API here, are you?
Inverse should be all other flags set to true.
What would be the usecase? When would you actually do this? This does not make any sense on an AI functional level (although it might for boolean logic).
plugin-based ai flags to be added in something backed by a bitset, etc.
It should be possible to refer to an exact bit, without knowing the using/depending on a plugin (to allow plugins to be compatible to non-Sponge plugins). Like in my example BIT_15.
If you fear sponge cannot spawn mobs, in fact, sponge can (by iterating over the entity registry and spawning in a dummy world)
This does not guarantee that plugin/mod/vanilla provided AI tasks get created/run at all. Some goals are (might be) created conditionally (killerRabbit) or externally.
I'd keep my ai flag set class so that people are not silly enough to feed an array-based set or something similar to the method.
How would you implement that in the API without exposing implementation details/internals? Well it might be possible, somehow. Thats probably something to verify during the actual PR.
@liach Are we going to address all AI API issues at once, or one at a time (to keep possibly breaking changes separate)?
PS: A few days ago I got contacted for implementation help related to AI, so the current API is definitely in use.
I am referring to a totally different, new ai system for bleeding.
the whole AI system (agent goals/tasks) will be revamped.
I am aiming to get the flags in for the new version.
To refer to exact bit, we can convert flags to catalog types and look up with arbitrary id.
The inversion is for goals. Goals need to reverse the bits.
My AITaskOccupiedFlagSet proposed already covered your last question "How would you implement that in the API without exposing implementation details/internals?"
As the bleeding is getting worked on now, should I send a pr to modify soon?
If we could get an initial draft, then we could at least have a base for discussion. I could play with it and check whether this is easily usable by plugins and maybe also start an accompanying docs PR.
Last time, Zidane mentioned when ST-DDT contributed AI docs on SpongeDocs that the whole AI system (agent goals/tasks) will be revamped. I have a few notes for these changes:
Goal class:
The AITask now holds the exact owner type instead of the superclass owner type which is the lowest bound of the task. Making it easier for plugins. Moreover, the task entry object allows retrieval of priority and confirm removal.
AITask class:
Replaced the
canRunConcurrentWith
with the occupied flag. The old method is way too ambiguous. Some of the occupied flags are changed with entity states (e.g. riding a boat disables some flags)AITaskBuilder class: (Or AbstractAITaskBuilder)
O
: ownerR
: result built, aware of generic ownerS
: sources to initialize the builder, wildcard ownerB
: builder Unfortunately, I couldn't find a way to reduce the generic parameters.Sample builtin ai task class: (Take avoid entity for example)
Note that every builtin ai class now carries a generic for the owner type; the owner type does not matter that much except its lower bound. This design guarantees mod compatibility in case mod ai tasks extend vanilla ones. As a result, every builtin ai class has an abstract builder and a concrete builder.
For
AITaskOccupiedFlag
, we can write it in a fashion similar to block change flags. Known info from mc code snippet:So flag 1, 2, 4 are not ridden flags and flag 4 is not riding boat (or can move?) flag
Examples: avoid entity: 1 leap at entity: 4 | 1 eat grass: 4 | 2 | 1 villager mate: 2 | 1 sit: 4 | 1 wither do nothing: 4 | 2 | 1 panic: 1 drowned go to water: 1 tempt: 2 | 1 swimming: 4 ghast look around: 2 watch closest: 2 watch closest without moving: 2 | 1 trade player: 4 | 1 ranged attack: 2 | 1 melee/zombie attack: 2 | 1 defend village: 1 move to block: 4 | 1
Appears flag 1 is for movement (in phantoms, all regular tasks have a flag of 1) flag 2 is for the action of looking (watch/ghast look)
For flag 4, it is a bit more complex mutex bit of 4: swimming mutex bit of 5: slime float in liquid trade with player jump leap at target move to block sit mutex bit of 6: (none) mutex bit of 7: eat grass wither do nothing
In conclusion, flag 4 is for liquid movement.
So, I propose an AI Task Occupied flag class as below:
And an AITaskOccupiedFlags, too. (You need to run sortfields)
With BlockChangedFlags as a precedent, this should not be too hard to implement.