/// <inheritdoc /> public async Task <IDisposable> AquireEntityLockAsync(NetworkEntityGuid guid) { //basically, root lock so nobody can change the entity state until we're done. //We lock read globally and then write for the entity IDisposable root = null; IDisposable child = null; try { //TODO: It's POSSIBLE, but unlikely that the entity was removed. Or that they didn't check? root = await InternalGlobalLock.ReaderLockAsync().ConfigureAwait(false); if (!EntityRefCountingMap.ContainsKey(guid.RawGuidValue)) { throw new InvalidOperationException($"Entity: {guid} does not exist in the locking service."); } //TODO: Should we do a write lock? child = await EntityLockingObjectMap[guid.RawGuidValue].WriterLockAsync().ConfigureAwait(false); } catch (Exception) { throw; } finally { child?.Dispose(); root?.Dispose(); } //MUST dispose this to dispose the locks. return(new AggregateDisposableLock(root, child)); }
private static void DequeueAddAllEntities([NotNull] IDequeable <NetworkEntityGuid> interestDequeueable, [NotNull] IEntityInterestSet interestSet, bool add = true) { if (interestDequeueable == null) { throw new ArgumentNullException(nameof(interestDequeueable)); } if (interestSet == null) { throw new ArgumentNullException(nameof(interestSet)); } while (!interestDequeueable.isEmpty) { NetworkEntityGuid entityGuid = interestDequeueable.Dequeue(); if (add) { interestSet.Add(entityGuid); } else { interestSet.Remove(entityGuid); } } }
private void InterestTriggerExit([NotNull] object sender, [NotNull] PhysicsTriggerEventArgs args) { if (sender == null) { throw new ArgumentNullException(nameof(sender)); } if (args == null) { throw new ArgumentNullException(nameof(args)); } GameObject rootObject = args.ColliderThatTriggered.GetRootGameObject(); NetworkEntityGuid me = ObjectToEntityMapper.ObjectToEntityMap[args.GameObjectTriggered.transform.GetRootGameObject()]; if (!ObjectToEntityMapper.ObjectToEntityMap.ContainsKey(rootObject)) { if (Logger.IsWarnEnabled) { Logger.Warn($"Tried to remove Entity: {rootObject.name} from Entity interest ID: {me} but does not exist. Is not owned."); } return; } OnEntityInterestChanged?.Invoke(this, new EntityInterestChangeEventArgs(me, ObjectToEntityMapper.ObjectToEntityMap[rootObject], EntityInterestChangeEventArgs.ChangeType.Exit)); }
private void InterestTriggerEnter([NotNull] object sender, [NotNull] PhysicsTriggerEventArgs args) { if (sender == null) { throw new ArgumentNullException(nameof(sender)); } if (args == null) { throw new ArgumentNullException(nameof(args)); } GameObject rootObject = args.ColliderThatTriggered.GetRootGameObject(); //TODO: This WON'T work if you parent the object. We NEED a better way to handle looking up the guid from collision //TODO: Should entites be interested in themselves? //Right now the way this is set up we don't check if this is the entity itself. //Which means an entity is always interested in itself, unless manually removed //because when it enters the world it will enter its own interest radius //This behavior MAY change. NetworkEntityGuid me = ObjectToEntityMapper.ObjectToEntityMap[args.GameObjectTriggered.transform.GetRootGameObject()]; if (!ObjectToEntityMapper.ObjectToEntityMap.ContainsKey(rootObject)) { if (Logger.IsWarnEnabled) { Logger.Warn($"Tried to enter Entity: {rootObject.name} to Entity interest ID: {me} but does not exist. Is not owned."); } return; } OnEntityInterestChanged?.Invoke(this, new EntityInterestChangeEventArgs(me, ObjectToEntityMapper.ObjectToEntityMap[rootObject], EntityInterestChangeEventArgs.ChangeType.Enter)); }
private void ThrowIfNoEntityInterestManaged(NetworkEntityGuid entryContext, NetworkEntityGuid entityGuid) { if (!ManagedInterestCollections.ContainsKey(entryContext)) { throw new InvalidOperationException($"Guid: {entityGuid} tried to enter Entity: {entryContext} interest. But Entity does not maintain interest. Does not exist in interest collection."); } }
/// <inheritdoc /> public bool Destroy([NotNull] PlayerSessionDeconstructionContext obj) { if (obj == null) { throw new ArgumentNullException(nameof(obj)); } //If the connection doesn't own an entity then we have nothing to do here. if (!OwnsEntityToDestruct(obj.ConnectionId)) { return(false); } NetworkEntityGuid entityGuid = ConnectionToEntityMap[obj.ConnectionId]; //An entity exists, so we just pass it along but we also must remove it from the map afterwards. bool result = PlayerEntityDestructor.Destroy(new PlayerEntityDestructionContext(entityGuid)); //We need to unregister BOTH of these collections, session collection orginally was cleanedup //immediately on disconnect. Now it is not and must be done here. ConnectionToEntityMap.Remove(obj.ConnectionId); SessionCollection.Unregister(obj.ConnectionId); return(result); }
/// <inheritdoc /> public void RegisterCallback <TCallbackValueCastType>(NetworkEntityGuid entity, EntityDataFieldType dataField, Action <NetworkEntityGuid, EntityDataChangedArgs <TCallbackValueCastType> > callback) where TCallbackValueCastType : struct { //TODO: Anyway we can avoid this for registering callbacks, wasted cycles kinda if (!CallbackMap.ContainsKey(entity)) { CallbackMap.Add(entity, new Dictionary <EntityDataFieldType, Action <int> >()); } //TODO: This isn't thread safe, this whole thinjg isn't. That could be problematic Action <int> dataChangeEvent = newValue => { //TODO: If we ever support original value we should change this //So, the callback needs to send the entity guid and the entity data change args which contain the original (not working yet) and new value. callback(entity, new EntityDataChangedArgs <TCallbackValueCastType>(default(TCallbackValueCastType), Unsafe.As <int, TCallbackValueCastType>(ref newValue))); }; //We need to add a null action here or it will throw when we try to add the action. But if one exists we need to Delegate.Combine if (!CallbackMap[entity].ContainsKey(dataField)) { CallbackMap[entity].Add(dataField, dataChangeEvent); } else { CallbackMap[entity][dataField] += dataChangeEvent; } }
/// <inheritdoc /> public void Tick() { if (Input.GetMouseButtonDown(0)) { Ray ray = CameraReference.ScreenPointToRay(Input.mousePosition); //TODO: We should enumerate the layers //5 is UI right now. int resultCount = Physics.RaycastNonAlloc(ray, CachedHitResults, 1000.0f, 1 << 5); //5th if (resultCount == 0) { Debug.Log($"No clicked on entities"); return; } //Otherwise, we have some hits. Let's check them. //TODO: This is kind of slow. GameObject rootGameObject = CachedHitResults[0].transform.GetRootGameObject(); if (GameObjectToEntityMappable.ObjectToEntityMap.ContainsKey(rootGameObject)) { NetworkEntityGuid entity = GameObjectToEntityMappable.ObjectToEntityMap[rootGameObject]; //If we actually have interacted with an entity that is on the UI layer //then we need to dispatch the event. Debug.Log($"Clicked on Entity: {entity.EntityType}:{entity.EntityId}"); } else { Debug.Log($"No entity. Clicked on: {rootGameObject.name}"); } } }
/// <inheritdoc /> public async Task <bool> ContainsLockingServiceForAsync(NetworkEntityGuid guid) { using (await InternalGlobalLock.ReaderLockAsync().ConfigureAwait(false)) { //We don't need to check value. return(EntityRefCountingMap.ContainsKey(guid.RawGuidValue)); } }
/// <inheritdoc /> protected override void HandleMovement(NetworkEntityGuid entityGuid, PathBasedMovementData data) { MovementDataMap[entityGuid] = data; MovementGenerator[entityGuid] = new PathMovementGenerator(data); //TODO: We may not be on the main thread, so we might not be able to do this. MovementGenerator[entityGuid].Update(GameObjectMap[entityGuid], TimeService.CurrentRemoteTime); }
/// <inheritdoc /> public bool Remove([NotNull] NetworkEntityGuid guid) { if (guid == null) { throw new ArgumentNullException(nameof(guid)); } return(_ContainedEntities.Remove(guid)); }
/// <inheritdoc /> public NetworkEntityGuid Retrieve(NetworkEntityGuid key) { if (!_ContainedEntities.Contains(key)) { throw new InvalidOperationException($"Provided Key: {key} does not exist in the tile."); } return(key); }
private async Task RegisterGuildOnExistingResponse(NetworkEntityGuid guid, IGroupManager groupManager, string connectionId) { if (GuildStatusMappable[guid].isSuccessful) { //TODO: don't hardcode await groupManager.AddToGroupAsync(connectionId, $"guild:{GuildStatusMappable[guid].GuildId}") .ConfigureAwait(false); } }
/// <inheritdoc /> public bool Contains([NotNull] NetworkEntityGuid key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } return(_ContainedEntities.Contains(key)); }
/// <inheritdoc /> public TextChatEventData([NotNull] string message, [NotNull] NetworkEntityGuid sender, ChatMessageType messageType) { if (!Enum.IsDefined(typeof(ChatMessageType), messageType)) { throw new InvalidEnumArgumentException(nameof(messageType), (int)messageType, typeof(ChatMessageType)); } Message = message ?? throw new ArgumentNullException(nameof(message)); Sender = sender ?? throw new ArgumentNullException(nameof(sender)); MessageType = messageType; }
//TODO: We need to filter in ONLY dirty movement data. Right now it resends movement data every packet but we only want to update if the data has changed. private AssociatedMovementData[] BuildMovementBlocks(NetworkEntityGuid guid) { return(GuidToInterestCollectionMappable[guid] .ContainedEntities //TODO: Temporarily we are not sending movement data about ourselves. //We also only send information about movement that is dirty from the last update we sent out. .Where(e => MovementDataMap.isEntryDirty(e)) .Select(e => new AssociatedMovementData(e, MovementDataMap[e])) .ToArray()); }
/// <inheritdoc /> public bool Unregister([NotNull] NetworkEntityGuid key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } _LeavingQueue.Enqueue(key); return(true); }
/// <inheritdoc /> public EntityInterestChangeEventArgs([NotNull] NetworkEntityGuid enterableEntity, [NotNull] NetworkEntityGuid enteringEntity, ChangeType changingType) { if (!Enum.IsDefined(typeof(ChangeType), changingType)) { throw new InvalidEnumArgumentException(nameof(changingType), (int)changingType, typeof(ChangeType)); } EnterableEntity = enterableEntity ?? throw new ArgumentNullException(nameof(enterableEntity)); EnteringEntity = enteringEntity ?? throw new ArgumentNullException(nameof(enteringEntity)); ChangingType = changingType; }
/// <inheritdoc /> public DefaultEntityCreationContext(NetworkEntityGuid entityGuid, IMovementData movementData, EntityPrefab prefabType, [NotNull] IEntityDataFieldContainer entityData) { if (!Enum.IsDefined(typeof(EntityPrefab), prefabType)) { throw new InvalidEnumArgumentException(nameof(prefabType), (int)prefabType, typeof(EntityPrefab)); } EntityGuid = entityGuid ?? throw new ArgumentNullException(nameof(entityGuid)); MovementData = movementData ?? throw new ArgumentNullException(nameof(movementData)); PrefabType = prefabType; EntityData = entityData ?? throw new ArgumentNullException(nameof(entityData)); }
/// <inheritdoc /> public bool TryHandleMovement(NetworkEntityGuid entityGuid, IMovementData data) { bool result = CanHandle(data); if (result) { HandleMovement(entityGuid, data as TSpecificMovementType); } return(result); }
//TODO: Create a converter type private static ZoneServerNpcEntryModel BuildDatabaseNPCEntryToTransportNPC(NPCEntryModel npc) { NetworkEntityGuidBuilder guidBuilder = new NetworkEntityGuidBuilder(); NetworkEntityGuid guid = guidBuilder.WithId(npc.EntryId) .WithType(EntityType.Npc) .Build(); //TODO: Create a Vector3 converter return(new ZoneServerNpcEntryModel(guid, npc.NpcTemplateId, new Vector3(npc.SpawnPosition.X, npc.SpawnPosition.Y, npc.SpawnPosition.Z), npc.MovementType, npc.MovementData)); }
private InterestCollection GetEntityInterestCollection(NetworkEntityGuid guid) { try { return(GuidToInterestCollectionMappable[guid]); } catch (Exception e) { throw new InvalidOperationException($"Attempted to load Entity: {guid}'s interst collection From: {GuidToInterestCollectionMappable.GetType().Name} but failed. No interest collection matched the key. Exception: {e.Message}", e); } }
private bool ChangeTrackerHasChangesForEntity(NetworkEntityGuid interestingEntityGuid) { try { return(ChangeTrackingCollections[interestingEntityGuid].HasPendingChanges); } catch (Exception e) { throw new InvalidOperationException($"Attempted to load Entity: {interestingEntityGuid}'s interst collection From: {ChangeTrackingCollections.GetType().Name} but failed. No entry matched the key. Exception: {e.Message}", e); } }
private void SendUpdate(NetworkEntityGuid guid, List <EntityAssociatedData <FieldValueUpdate> > updates) { try { SessionMappable[guid].SendMessageImmediately(new FieldValueUpdateEvent(updates.ToArray())); } catch (Exception e) { throw new InvalidOperationException($"Failed to send update to session with Guid: {guid}. Exception: {e.Message}", e); } }
private void LogNoSessionError([NotNull] NetworkEntityGuid guid) { if (guid == null) { throw new ArgumentNullException(nameof(guid)); } if (Logger.IsErrorEnabled) { Logger.Error($"Cannot send message to: {guid}. No session associated with it."); } }
/// <inheritdoc /> public bool TryHandleMovement(NetworkEntityGuid entityGuid, IMovementData data) { foreach (var handler in MovementHandlers) { if (handler.CanHandle(data)) { return(handler.TryHandleMovement(entityGuid, data)); } } return(false); }
/// <inheritdoc /> public ZoneServerNpcEntryModel([NotNull] NetworkEntityGuid guid, int templateId, Vector3 initialPosition, NpcMovementType movement, int movementData) { if (templateId <= 0) { throw new ArgumentOutOfRangeException(nameof(templateId)); } Guid = guid ?? throw new ArgumentNullException(nameof(guid)); TemplateId = templateId; InitialPosition = initialPosition; Movement = movement; MovementData = movementData; }
/// <inheritdoc /> public void Register([NotNull] NetworkEntityGuid key, [NotNull] NetworkEntityGuid value) { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } //Both key and value are the same _EnteringQueue.Enqueue(value); }
private async Task <HubOnConnectionState> TryRegisterGuildStatus(NetworkEntityGuid guid, IGroupManager groups, string connectionId) { //It's possible we already maintain guild information for this entity //This can happen if multiple connections share an entity. if (GuildStatusMappable.ContainsKey(guid)) { await RegisterGuildOnExistingResponse(guid, groups, connectionId) .ConfigureAwait(false); return(HubOnConnectionState.Success); } return(HubOnConnectionState.Error); }
/// <inheritdoc /> public void InvokeChangeEvents(NetworkEntityGuid entity, EntityDataFieldType field, int newValueAsInt) { //We aren't watching ANY data changes for this particular entity. if (!CallbackMap.ContainsKey(entity)) { return; } //If we have any registered callbacks for this entity's data change we should dispatch it (they will all be called) if (CallbackMap[entity].ContainsKey(field)) { CallbackMap[entity][field]?.Invoke(newValueAsInt); } }