-
Notifications
You must be signed in to change notification settings - Fork 1
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
Custom Entity Migration #232
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super exciting feature! Generally just things to discuss and TODOs to create.
Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs
Outdated
Show resolved
Hide resolved
private sealed class SharedTypeOffsetInfo | ||
{ | ||
public static readonly SharedStatic<UnsafeParallelHashMap<long, TypeOffsetInfo>> REF = SharedStatic<UnsafeParallelHashMap<long, TypeOffsetInfo>>.GetOrCreate<MigrationUtilContext, SharedTypeOffsetInfo>(); | ||
} | ||
|
||
private sealed class SharedEntityOffsetInfo | ||
{ | ||
public static readonly SharedStatic<IntPtr> REF = SharedStatic<IntPtr>.GetOrCreate<MigrationUtilContext, SharedEntityOffsetInfo>(); | ||
} | ||
|
||
private sealed class SharedBlobAssetRefInfo | ||
{ | ||
public static readonly SharedStatic<IntPtr> REF = SharedStatic<IntPtr>.GetOrCreate<MigrationUtilContext, SharedBlobAssetRefInfo>(); | ||
} | ||
|
||
private sealed class SharedWeakAssetRefInfo | ||
{ | ||
public static readonly SharedStatic<IntPtr> REF = SharedStatic<IntPtr>.GetOrCreate<MigrationUtilContext, SharedWeakAssetRefInfo>(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these wrapped in individual types vs just being statics in the MigrationUtil
class?
If they have to be wrapped to they need to each be wrapped int heir own type or could they all be put into on private class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just how SharedStatic
's work. Unity itself has to do a similar scan for IComponentTypes
and we need a way to differentiate between their IntPtr
and my IntPtr
. By wrapping in private sealed classes, I ensure my uniqueness and they ensure theirs.
It's a bunch of boiler plate but that's why I relegated it to the bottom of this class hidden away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool, weird pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SharedStatic and FunctionPointers for Burst in general are weird patterns I agree, but I guess I see how they need to ensure the uniqueness of a memory address without using any managed code and that's really only possible by using a bunch of compile time generated generics with unique class types.
Annoying though.
Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs
Show resolved
Hide resolved
{ | ||
//Can't modify while iterating so we collapse down to a single array and clean the underlying stream. | ||
//We'll build this stream back up if anything should still remain | ||
NativeArray<EntityProxyInstanceID> currentInstanceArray = m_CurrentStream.ToNativeArray(Allocator.Temp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
future optimization but would it be possible to build up the new stream (if needed) at the same time? The same way we do on each iteration of a task driver?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not 100% sure what you mean, but I think we already are?
Entities that weren't moved get rewritten back to this world's stream. Entities that were moved get remapped and written to the new world's stream.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now you're copying your existing stream, clearing and then re-adding to it if the entity isn't remapped.
If you instead created a new stream you could avoid the full temp array copy.
The same way when the task driver system job updates it processes the current stream and writes any instances that need to continue to a new stream for the next frame. Similar to a double buffer approach.
Alternatively, if stream could support some sort of removeSwapBack type operation then maybe we could leave the current stream intact and just cherry pick out the entities that do get remapped to the new world?...unless we think order matters in a stream.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohhhh I see what you mean. Interesting. I've added a TODO and referred to this.
I don't think order matters in the stream... removeSwapBack is really neat though, I think that could be possible and is probably the way to do this instead of adding a second stream and double buffering them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea just want to be explicit in our docs that DataStreams are unordered.
...ntime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs
Show resolved
Hide resolved
.../Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs
Show resolved
Hide resolved
|
||
AddToMigrationLookup( | ||
parentPath, | ||
typeof(CancelCompleteDataStream).GetReadableName(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mild concern about using readable name for a uniqueID type situation.
Readable name is used for human readable formatting and if we ever changed that formatting it could break the path. Should we just use C#'s built in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmn I'm not sure. If we changed the formatting it would break, but only if we changed the formatting mid-run. It's a runtime only id.
We could use the built in, it's just harder to parse when debugging. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not so concerned about changing the formatting mid run as I am that our formatting doesn't provide proper uniqueness at some point.
In fact, today, this doesn't guarantee uniqueness between types. namespace1.MyType
and namespace2.MyType
will both resolve to the same readable name.
Maybe the type hash is a better solution? Then for debug-ability our ToString() methods can parse out and map the hash back to the type and generate the readable name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmn ok, fair point. I changed it to FullName
for now to keep the uniqueness.
I added a TODO to hash the FullName
to get shorter paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
teeechhnically you want the Assembly qualified one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switched to using BurstRuntime to get the hash of the type which uses the Assembly qualified name under the hood
# Conflicts: # Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs # Scripts/Runtime/Entities/TaskDriver/System/AbstractTaskDriverSystem.cs # Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/EntityProxyDataStream.cs # Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs
@mbaker3 Ready for re-review. Also contains changing the name of the Task Driver System to the outside to be |
All addressed. Some discussions to sort out but this is looking good! |
Unity will automatically remap
Entity
references on Components and in DynamicBuffers when Entities move from one World to another.Entities that exist in
TaskDrivers
orEntityPersistentData
however will not.This PR gives functionality to provide a framework for allowing anything to be remapped and implements it for
TaskDrivers
andEntityPersistentData
What is the current behaviour?
No way to provide custom remapping.
What is the new behaviour?
Framework
Anything can implement the
IMigrationObserver
and register with theWorldEntityMigrationSystem
to receive a notification when Entities have migrated to a new World.The callback will provide the necessary information to perform the custom logic to remap Entity references.
MigrationUtil
provides the functionality to actually do the remapping viaRegisterTypeForEntityPatching
and then the call toPatchEntityReferences
.All changes to the world should happen through
WorldEntityMigrationSystem.MigrateTo
. If you use theEntityManager.MoveEntitiesFrom
vanilla function, we won't know and custom migration won't happen.TaskDrivers and EntityPersistentData
I've handled both of these systems to automatically register their types and patch references.
Everything is jobified so that we can use Burst and in practice ends up being pretty quick.
Tag-Alongs
ENABLE_UNITY_COLLECTIONS_CHECKS
toANVIL_DEBUG_SAFETY
where I found them.struct
tounmanaged
GetKeyValueArrays
toEntityPersistentDataWriter
What's Next
Deep Migration #233 There are still some ways you can miss Entities. If you have an EPD then you have a relationship of Key: Entity to Value: Something.
Migration Integrity #234 Integrity.
What issues does this resolve?
What PRs does this depend on?
Does this introduce a breaking change?
WorldEntityMigrationSystem
now instead.