#Generic IBufferElement and use it in a Generic System

1 messages · Page 1 of 1 (latest)

river idol
#

2 types of Entity in my game Both have same IBufferElement B, Except one Enum field in B. I tried and successfully added that Generic IBufferElement into both said types.
Ok then the problem came, I made my first generic ISystem, red popup appeared when I tried to put that DynamicBuffer<B<TEnum>> into the query:

  • The type 'B<TEnum>' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as parameter 'T' in the generic type or method 'DynamicBuffer<T>'
  • The type 'T' in DynamicBuffer<T>, UnityEngineComponent<T>, RefRO<T>, RefRW<T>, EnabledRefRO<T> and EnabledRefRW<T> must not contain a generic type parameter

After that, I searched over the internet to see how people Handle the generic component in the System (or Generic System), all I saw that they was using Generic Job, no one used foreach to query the Generic Component/Buffer.
Then why DynamicBuffer<T>, UnityEngineComponent<T>, RefRO<T>, ... in Query if generic component do exist?

More details of my case, Tool and Unit have B attached to them (only thing different is an Enum field in B), they both can be spawned through Structure, then their spawning process will be controlled by a set of systems (my case: 4 systems), the only system that need information of TEnum is IncSpawnCountSystem, which needs TEnum to look for the spawnCost.

Sorry for not posting the code here cause it is quite complicated
And Thanks for your supports

sturdy stag
# river idol 2 types of Entity in my game Both have same IBufferElement B, Except one Enum fi...

Usually we achieve generics through using literally same type of component, just different values across different entities.

Sometimes that means that data may represent guid or entity reference.
In the end - no C# generics at all in such systems.

One example: different units have different abilities. But what abilities they have is defined by Ability : IBufferElementData { public Entity value; }. So we don't need different components for different units, we just reuse exact same one.

river idol
sturdy stag
#

not really
Another example:
You don't have PlantTransform and CharacterTransform. You only have generic Transform.

restive spade
#

the only system that need information of TEnum is IncSpawnCountSystem, which needs TEnum to look for the spawnCost
In my case I would use a generic ID for this lookup (as generic as the Transfrom component, as Issue said). This ID can identify each kind of entity you want and the systems don't care about the underlying values. Generally I would use this kind of ID for various things, not just spawn cost lookup.

river idol
#

Thank you both of you, previously this morning I just tried a new way, no generic components or systems, I removed the conflict enum, find another way to find spawn cost as Green Leaf said.

It not the way I want cause it is inconvenient:

  • Use 2 dynamic buffers (just as the way we split one ICD to more part to make it more modular), 1st one is the main buffer, 2nd one is the cost buffer, both don't have same size cause 2nd buffer would turned into a 2-dimensional array if i do so.
  • When Integrating the first buffer, i use 1st buffer index to look up for the cost in the 2nd buffer (of course the indexes used in both is different but always 1-1).

And Yeah, it worked, no generic components or systems but 2 buffers

restive spade
#

In your previous issue, I did suggest making a giant hash map for the cost lookup and put it on a singleton so you can save 1 dynamic buffer. Does that approach work for you?

river idol
#

`
[StructLayout(LayoutKind.Explicit)]
public readonly struct UnitCostId : IEquatable<UnitCostId>
{

[FieldOffset(0)] private readonly int _raw; // 4 bytes

[FieldOffset(0)] public readonly UnitType Unit; // 1 byte
[FieldOffset(1)] public readonly ResourceType Cost; // 1 byte
[FieldOffset(2)] public readonly ushort LocalIndex; // 2 bytes

public UnitCostId(UnitType unit, ResourceType cost) : this()
{
    this.Unit = unit;
    this.Cost = cost;
}

public UnitCostId(UnitType unit, ResourceType cost, ushort localIndex) : this()
{
    this.Unit = unit;
    this.Cost = cost;
    this.LocalIndex = localIndex;
}


public bool Equals(UnitCostId other)
    => this._raw == other._raw;

public override bool Equals(object obj)
    => obj is UnitCostId other && this._raw == other._raw;

public override int GetHashCode()
    => this._raw.GetHashCode();

public static bool operator ==(UnitCostId left, UnitCostId right)
    => left._raw == right._raw;

public static bool operator !=(UnitCostId left, UnitCostId right)
    => left._raw != right._raw;

public override string ToString()
{
    return $"{nameof(Unit)}: {Unit}, {nameof(Cost)}: {Cost}, {nameof(LocalIndex)}: {LocalIndex}";
}

}
`

My modified version you gave me

#

LocalIndex for different versions of UnitProfileSO I used (both are Villager unit but with different stats, only for testing)

restive spade
#

I don't understand how this relates to my question above?