 As we've said, our component types can't include normal C sharp arrays, but what we can include are what C sharp calls fixed arrays, such as here, this struct has a fixed array of bytes of size five. And this is not a reference pointing to some bytes elsewhere in memory. This struct directly stores five bytes for this fixed byte array. And there are two restrictions with these fixed arrays. First off, you can only have fixed arrays of the basic types like byte and float, double, etc. And secondly, a struct which has a fixed array must be marked as unsafe. And then you can only access this field of the struct, this fixed array of the struct, in an unsafe context. So down here, when I create an instance of the struct, I can't get at the fixed array unless I use an unsafe block. And just be very clear, this is not a normal C sharp array. So here, if I were to make a C sharp array reference just called bytes, I couldn't then assign to bytes comp dot bytes. This just doesn't make sense because this is not a reference to an array. It's not a normal normal C sharp array at all. It's just five byte slots embedded directly in the struct. So if you go look at unity, it's telling us cannot implicitly convert type byte pointer to byte array because it considers comp dot bytes here to be a byte pointer, not a byte array. Keep in mind that our components still should be really quite small. We generally shouldn't have components that are larger than say a couple hundred bytes or so. And keep in mind that our components are stored in these chunks, which are 16 kilobytes in size. So you'll actually get an error at runtime if you exceed the capacity of a chunk because it won't have space to store this and we make this too big. Like say if we made this 30,000, well, that's larger than 16,000 bytes. So we couldn't store even a single one of these components in a chunk. It's just too big. So we would get an error. So C sharp fixed arrays are one way to get arrays into our components. Another option we have is we can create components which are themselves considered by ECS to be fixed arrays as a component type called fixed array. And so here when we're setting up this archetype, we have this normal component, but here we're creating a fixed array component, which is an array of ints size three. And we're also giving this archetype another component type, which is a fixed array of my structs size 10. So these fixed arrays are not elements of components, they themselves are the components. And we can give a single entity any number of fixed array components as long as those fixed arrays have different base types. So say you couldn't have a single entity with two fixed arrays of type int, you couldn't have that. There could only be one of type int. You get a multiple fixed arrays, but they have to be fixed arrays of different types like we have here. And so here we're creating five entities with that archetype. And then in our update, we're going to iterate over component group, which is matching for that same archetype. And so the update method here should run because we do have entities which match this component group. And so if I come over to Unity, hit play, we'll see that the update method is running every frame like we should expect. And within the update from our group, we're getting the entity array to access the entity IDs. And we're getting a normal component data array for the my components, just like we've seen before. But then to get these fixed arrays, there's this method get fixed array array, very conveniently named. And that gets us these iterators to iterate over the fixed arrays of the various entities. And so here we're looping through all of the entity IDs. And in each iteration, we're accessing the fixed array of ints associated with the entity for that index. And when we do so, what we're getting is a native array of int. And now here we're using that array, we're looping through all the ints in that native array of int. And for this example, we're just doing an arbitrary bit of business, we're just taking the current value of that int and incrementing it. And be clear, you can't use the postfix operator on a native array and have it do what you expect. Unity as of yet does not support C sharp seven and C sharp seven has a feature called rough returns that would allow the overloads of the operators for the native array and the other native container types to implement these operators like postfix here to do what you expect. But without rough return, if we had this expression here, then this operation would just be returning the value from the array at index J. This would then increment that value, but it wouldn't store it back in that slot in the array because this is just returning an int. It's not recognized as a stored slot by the postfix operator here. You won't have to worry about this little gotcha once they bring in C sharp seven supports and they'll have rough returns and they can fix this problem. But that's why we have to write out this incrementing of the index of the array. Now, an interesting question here is, when you specify for component group, a fixed array component, does the size you specify really matter? And the answer as far as I can tell is no, it really has no effect whatsoever. So this component group actually matches all entities that have a my component and fixed array of ints and fixed array of structs, regardless of the sizes of those ints and structs, right? Because you can have separate entities that both have these fixed array types, but they're not necessarily fixed arrays of the same size. So far as I can tell for these component groups, it doesn't matter what the size is, it just matches entities that have fixed arrays of those types, regardless of size. So if I change this to five here, that's not actually going to change anything if I hit play. The component group still matches. And so, yes, our updates are still running for the system. So because there are entities which, which match the component group, even though I changed the size. And this number also doesn't affect what happens when we actually access these interays, because if I print out here the, the length of the interay, it's still three, it's still the length of the actual array. And so, coming here now, we should see three, three, three. Yeah. So the size of the rays three, which is good because it means we can have a system which iterates over entities with different length fixed arrays. And as long as we don't assume the length and actually query what the length is when we iterate over the elements as we do here, then we're not going to have an issue because if the length is five for one, but three for another or 10 for another, that it'll just do a different number of iterations for those different arrays. Now you may be wondering, well, if this is a native array, am I responsible for disposing of it? Should I put here dispose? And the answer is actually no, we will get an exception. And the exception is telling us the native array cannot be disposed because it was not allocated with a valid allocator. And that message is a little cryptic. But what it's saying is that these, these native arrays aren't allocated in the normal way. They're, they're part of components stored in the entity manager chunks. And these native array values we're getting here. They're just representing a pointer to that location and memory. So you wouldn't want to dispose of because the way we get rid of components in our entities is by remove component or by destroying the entity. That's how we're supposed to get rid of these things. So you should not dispose these native arrays. One more thing to mention here about these fixed arrays, you can have fixed arrays of struct types, but those struct types have to be blittable. And for whatever reason, they can't be implementers of I component data, I don't know what the rationale behind that choice was, but that seems to be a restriction. So this my struct here is okay, but we couldn't have my component, we couldn't have a fixed array of my components. Strangely, though, we're not allowed to have a fixed array of entities, even though entities are blittable structs, and they're not implementers of I component data. I think this is really just an oversight because what we can do is we can create a struct, which is what's just called entity wrap, which has a single field of type entity. And then we can use that wrapper. And this is okay, this is acceptable. And effectively, we just have a struct, which is really just a wrapper for an entity. So you can have entities in the structs, which make up your fixed arrays. You just can't use entity itself. Again, I think that's really just an oversight in the code. I delved a bit into the code and it seemed like they just didn't account for that use case. Maybe there's some good reason not to allow it, but then why allow entities in these structs then it doesn't seem consistent. Another kind of component type we have is what's called a shared component. And these are defined by using the I shared component data interface rather than just I component data. And these are called shared components, because a chunk will only store one value of a shared component type for all entities in that chunk. Each entity doesn't have its own individual value of that component type, they just all share the one within that chunk. So you can have multiple entities that all have different values of this shared component type, but within a chunk they all have to have the same one, which implies that if you set the value of a shared component on an entity, then that entity has to be moved to a different chunk. The other special thing about these shared components is that they can actually store reference types. They don't have to be blittable. And so here, for example, this my shared component has a string field, which would not be allowed in an I component data, but it is allowed in an I shared component data. And so in fact, we can have any kind of class instance reference in here or C sharp array or just anything we want. But keep in mind that ECS relies upon the hash value of these shared components to determine when they're equal. Because when you set a shared component value on an entity, you want to know if it's equal to a shared component value of other existing entities of the same archetype. And if so, then you want to store it in that same chunk, if possible, you don't want to fragment all the entities so that they're stored in their own individual chunks, that'd be very wasteful. So ECS, when it looks at your shared component values, it competes a hash code by going through all the bytes and computing the hash. Anyway, so in practice, let's see what we're doing here. We're creating an archetype that uses my shared component type component group also is looking for that shared component type. And then in our update, we get an iterator using get shared component data array. And this is an indexer just like our normal component data arrays. And here when we loop through the entities, we can get the shared component values, which in this case, we didn't initialize any value. So they just have the default values here, which should be zero and null for the string. So we should see here printed num zero and stir is null should be true. So let's just see that play out. And yes, there we go. Num zero stir is null is true. Now you might assume that we should be able to update the values of these shared components when we iterate through them like we do with the regular components with a normal component, this would be fine. But the problem is that, as we said, shared component values are stored on a per chunk basis, not per entity basis. All the entities will then chunk share the same shared component value. So for an entity, if you set its shared component value, then that basically always means that it has to move to a new chunk. So implies a structural change to our chunks rather than just mutating one of the component values in the chunk. And when you make structural changes to the chunks that invalidates the component group iterators. And so if this were allowed bad things would happen. But what will actually happen here is that we simply get a compilation error, because shared component data arrays, you are read only, you can't assign to their indexes, just not allowed, because it's just always a dangerous thing to do. So what we need to do instead is use our entity command buffer, post update commands, whichever component system has, and call this set shared component method, specify the shared component type, specify which entity we want to set the shared component value on. And now this will work because it's just queuing up the change which is then affected in this case immediately after this on update returns all these queued up commands, setting these shared components and all the entities will be performed after the update. So come over to hit play. And I'm going to pause, scroll up, and we have four entities all with these shared components. And first time in the update, we see num zero stir is null true four times. But then in the next update, we have changed the shared component values to be number four, and stir is not null, it's something what we said to high. So it's not null. And so stir is null is false. And then with the entity manager, we can make the exact same call that we used on the entity command buffer. And so now if we run this code, the first time through the values will be number four and string of high. So that's how shared component values work. Now, when should you use them? Well, by their nature, for reasons we hinted at the way the ECS has to compare all your shared component values with each other by their hash codes. It's a little expensive. There's some overhead involved when you use add and remove these component types and set their component values in a way that you don't have with regular component types. And so I would say the shared components are appropriate in cases where you have many entities that there's going to be a lot of overlap in their sharing of values. If you have a component type where it might be the case that most entities have different values, then you generally wouldn't want to use a shared component because you end up fragmenting your entities into many different chunks because, you know, each chunk can have only one value of a particular shared component type. I should be clear, you can have multiple shared components within a single chunk, but only one value of us of a type. Anyway, if you had many entities with a shared component type, but most of them had different values, then you'd really be losing all the benefits of ECS and the linear memory layout because everything would just be in different chunks. And the chunks are scattered all throughout memory the same way like conventional objects are scattered everywhere throughout memory. So you start storing things in a bunch of different tiny little chunks that barely have anything in them, then you're really defeating all the performance gains of ECS. So only use shared components in those cases where, you know, there's going to be a relatively restrained set of possible values, such as those large amount of overlap between entities sharing the same values. There are a couple more things to talk about in regards to shared components, but first I need to note that I showed you some invalid code earlier. The method on entity manager is called set shared component data, whereas on entity command buffer, it's just called set shared component without the data at the end, even though it does the same thing basically. So it's a strange little inconsistency there. Anyway, what we're doing here now is I'm creating five entities with one shared component value and another five entities with a different shared component value. And so again, these five entities can't be stored in the same chunk as these five entities, they'll likely I assume we'll have just two chunks here one for these five and one for the other five. And now down here in an update, we're calling set filter on the component group passing in a shared component value. And now with this filter, the iterators we get from the component group are only going to iterate through the chunks whose my shared component value matches this value here. And so down here in this loop, what we'll see printed out should just be num1 and stir is high. We won't see num2 and stir by because such entities exist, but they're being filtered out. We're selecting here only entities which have this shared component value. So here I hit play. And as you can see num1 stir is high and you don't see it printing num2 stir is by. Another useful method is the entity managers get all unique shared component data where we pass in a list of a shared component type, and it adds to this list, all the unique values of that type. So here we're looping through all the unique values of this type my shared component, and we're using the value to set the filter on the component group and getting an entity array and a component data array like we did before. Except now we don't need a shared component data array because we already have the shared component value of this type. We already have it here as this variable my shared. And then here in this inner loop we're looping through all the entities of this component group and printed out the my shared value like we did before. And we could of course get at the entity IDs and the my components for these individual entities if we'd like, but we're not doing so here. So effectively then we should see printed out. We're going to see num1 stir high printed out five times and we'll see num2 stir by printed out five times. And there's no guarantee of what order you get the unique values in in this list. But let's see what we get in this case. It prints out first five times num1 stir high and then num2 stir by and it does this for each frame. In any case sometimes this is useful because when you go through all the entities you want to group the entities with the same component values together because you want to do something en masse with all those together. We're not doing anything like that here. But you'll see an example in the next video when we start using the rendering system where that is useful. Now understand that the set filter method is actually a bit costly because what it has to do is it has to compute a hash code for the shared component value and then go find the set of chunks that all match that shared component value. And so particularly if we had a lot of different unique values of this shared component type then we'd have a lot of calls to set filter and be nice if we could make that more efficient. So instead of making multiple calls to set filter we can on the component group create a for each filter as it's called passing in the uniques and we get back this thing which is a for each component group filter. And this is doing all at once finding for each shared component value finding the set of chunks which match that shared component value and doing it all in one go in a way that's more efficient than doing it separately. And so having set up this filter we then when we get the iterators the entity arrays and the component data arrays for these methods we pass in the filter and an index into the filter. So now this inner loop is iterating up to length of the filter which is the same as the length of uniques they're actually parallel arrays. And so we're getting here first the shared component value out of uniques. And then when we get the entity array and the component data array we pass in the filter and the index I. So this all gets us the same result in the end just avoiding multiple calls to set filter all that work is done in this one more efficient method create for each filter. And lastly note down here with these filters you have to dispose of them so after this inner loop we're going to dispose of the filter. So that's how we work with shared component values. Last thing to say is a point I was confused about for a while is how exactly are these equality comparisons between the shared component values performed because anytime we set filter or add a shared component or set a shared component. There has to be equality tests between the shared component values. And the question is well how exactly is that done well for a while I got the impression that it did a full traversal of the full shared component value and recursively followed all the references into the pointers and computed a hash code from all of those bytes. Which of course would be horribly expensive because your shared components can have reference types like say meshes and textures and so forth and reading all those bytes for things that are potentially very large of course would be very expensive. So that definitely seemed wrong because you'd be getting this potentially very significant hit every time you call set filter or set shared component value and that obviously would be no good. So of course that's not what actually happens when two shared component values of the same type are evaluated for quality is just doing a shallow comparison of the structs themselves and any structs they contain is just looking at those bytes. The equality test isn't following any pointers or references and looking at those bytes that would be potentially very very expensive. So what this means then is if you have two values of the same shared component type and let's say the type contains say a mesh field. And while it's possible to have two meshes that are not the same object but they happen to have matching data and so in that sense could be equal for the purpose of this quality test. All your references have to point to the very same object that it's actually an identity test because the way it's actually performed is just looking at the bytes within the shared component structs. So it just matters what the references are. So keep that in mind the quality tests between shared components is actually an identity test for the references within. And while in some circumstances you'd prefer to have a proper deep quality test over just simple identity tests. In this case it's really not an issue because I can't really think of scenarios where you would want to have shared component values treated as equal if they're not referencing the very same objects. I really can't think of a scenario where that be useful. So again these quality tests are just shallow tests are not deep traversals into all the references.