Esempio n. 1
0
        protected sealed override void OnApply(HitObjectLifetimeEntry entry)
        {
            // LifetimeStart is already computed using HitObjectLifetimeEntry's InitialLifetimeOffset.
            // We override this with DHO's InitialLifetimeOffset for a non-pooled DHO.
            if (entry is SyntheticHitObjectEntry)
                LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;

            ensureEntryHasResult();

            foreach (var h in HitObject.NestedHitObjects)
            {
                var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h, this);
                var drawableNested = pooledDrawableNested
                                     ?? CreateNestedHitObject(h)
                                     ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");

                // Only invoke the event for non-pooled DHOs, otherwise the event will be fired by the playfield.
                if (pooledDrawableNested == null)
                    OnNestedDrawableCreated?.Invoke(drawableNested);

                drawableNested.OnNewResult += onNewResult;
                drawableNested.OnRevertResult += onRevertResult;
                drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState;

                // This is only necessary for non-pooled DHOs. For pooled DHOs, this is handled inside GetPooledDrawableRepresentation().
                // Must be done before the nested DHO is added to occur before the nested Apply()!
                drawableNested.ParentHitObject = this;

                nestedHitObjects.Add(drawableNested);
                AddNestedHitObject(drawableNested);
            }

            StartTimeBindable.BindTo(HitObject.StartTimeBindable);
            StartTimeBindable.BindValueChanged(onStartTimeChanged);

            if (HitObject is IHasComboInformation combo)
                comboIndexBindable.BindTo(combo.ComboIndexBindable);

            samplesBindable.BindTo(HitObject.SamplesBindable);
            samplesBindable.BindCollectionChanged(onSamplesChanged, true);

            HitObject.DefaultsApplied += onDefaultsApplied;

            OnApply();
            HitObjectApplied?.Invoke(this);

            // If not loaded, the state update happens in LoadComplete().
            if (IsLoaded)
            {
                if (Result.IsHit)
                    updateState(ArmedState.Hit, true);
                else if (Result.HasResult)
                    updateState(ArmedState.Miss, true);
                else
                    updateState(ArmedState.Idle, true);
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Applies a new <see cref="HitObject"/> to be represented by this <see cref="DrawableHitObject"/>.
        /// </summary>
        /// <param name="hitObject">The <see cref="HitObject"/> to apply.</param>
        /// <param name="lifetimeEntry">The <see cref="HitObjectLifetimeEntry"/> controlling the lifetime of <paramref name="hitObject"/>.</param>
        public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry)
        {
            free();

            HitObject = hitObject ?? throw new InvalidOperationException($"Cannot apply a null {nameof(HitObject)}.");

            this.lifetimeEntry = lifetimeEntry;

            if (lifetimeEntry != null)
            {
                // Transfer lifetime from the entry.
                LifetimeStart = lifetimeEntry.LifetimeStart;
                LifetimeEnd   = lifetimeEntry.LifetimeEnd;

                // Copy any existing result from the entry (required for rewind / judgement revert).
                Result = lifetimeEntry.Result;
            }
            else
            {
                LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
            }

            // Ensure this DHO has a result.
            Result ??= CreateResult(HitObject.CreateJudgement())
            ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");

            // Copy back the result to the entry for potential future retrieval.
            if (lifetimeEntry != null)
            {
                lifetimeEntry.Result = Result;
            }

            foreach (var h in HitObject.NestedHitObjects)
            {
                var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h, this);
                var drawableNested       = pooledDrawableNested
                                           ?? CreateNestedHitObject(h)
                                           ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");

                // Only invoke the event for non-pooled DHOs, otherwise the event will be fired by the playfield.
                if (pooledDrawableNested == null)
                {
                    OnNestedDrawableCreated?.Invoke(drawableNested);
                }

                drawableNested.OnNewResult            += onNewResult;
                drawableNested.OnRevertResult         += onRevertResult;
                drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState;

                // This is only necessary for non-pooled DHOs. For pooled DHOs, this is handled inside GetPooledDrawableRepresentation().
                // Must be done before the nested DHO is added to occur before the nested Apply()!
                drawableNested.ParentHitObject = this;

                nestedHitObjects.Value.Add(drawableNested);
                AddNestedHitObject(drawableNested);
            }

            StartTimeBindable.BindTo(HitObject.StartTimeBindable);
            StartTimeBindable.BindValueChanged(onStartTimeChanged);

            if (HitObject is IHasComboInformation combo)
            {
                comboIndexBindable.BindTo(combo.ComboIndexBindable);
            }

            samplesBindable.BindTo(HitObject.SamplesBindable);
            samplesBindable.BindCollectionChanged(onSamplesChanged, true);

            HitObject.DefaultsApplied += onDefaultsApplied;

            OnApply();
            HitObjectApplied?.Invoke(this);

            // If not loaded, the state update happens in LoadComplete().
            if (IsLoaded)
            {
                if (Result.IsHit)
                {
                    updateState(ArmedState.Hit, true);
                }
                else if (Result.HasResult)
                {
                    updateState(ArmedState.Miss, true);
                }
                else
                {
                    updateState(ArmedState.Idle, true);
                }
            }

            hasHitObjectApplied = true;
        }
Esempio n. 3
0
        /// <summary>
        /// Applies a new <see cref="HitObject"/> to be represented by this <see cref="DrawableHitObject"/>.
        /// </summary>
        /// <param name="hitObject">The <see cref="HitObject"/> to apply.</param>
        /// <param name="lifetimeEntry">The <see cref="HitObjectLifetimeEntry"/> controlling the lifetime of <paramref name="hitObject"/>.</param>
        public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry)
        {
            free();

            HitObject = hitObject ?? throw new InvalidOperationException($"Cannot apply a null {nameof(HitObject)}.");

            this.lifetimeEntry = lifetimeEntry;

            if (lifetimeEntry != null)
            {
                // Transfer lifetime from the entry.
                LifetimeStart = lifetimeEntry.LifetimeStart;
                LifetimeEnd   = lifetimeEntry.LifetimeEnd;

                // Copy any existing result from the entry (required for rewind / judgement revert).
                Result = lifetimeEntry.Result;
            }
            else
            {
                LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
            }

            // Ensure this DHO has a result.
            Result ??= CreateResult(HitObject.CreateJudgement())
            ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");

            // Copy back the result to the entry for potential future retrieval.
            if (lifetimeEntry != null)
            {
                lifetimeEntry.Result = Result;
            }

            foreach (var h in HitObject.NestedHitObjects)
            {
                var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h);
                var drawableNested       = pooledDrawableNested
                                           ?? CreateNestedHitObject(h)
                                           ?? throw new InvalidOperationException($"{nameof(CreateNestedHitObject)} returned null for {h.GetType().ReadableName()}.");

                // Invoke the event only if this nested object is just created by `CreateNestedHitObject`.
                if (pooledDrawableNested == null)
                {
                    OnNestedDrawableCreated?.Invoke(drawableNested);
                }

                drawableNested.OnNewResult            += onNewResult;
                drawableNested.OnRevertResult         += onRevertResult;
                drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState;

                nestedHitObjects.Value.Add(drawableNested);
                AddNestedHitObject(drawableNested);

                drawableNested.OnParentReceived(this);
            }

            StartTimeBindable.BindTo(HitObject.StartTimeBindable);
            StartTimeBindable.BindValueChanged(onStartTimeChanged);

            if (HitObject is IHasComboInformation combo)
            {
                comboIndexBindable.BindTo(combo.ComboIndexBindable);
            }

            samplesBindable.BindTo(HitObject.SamplesBindable);
            samplesBindable.BindCollectionChanged(onSamplesChanged, true);

            HitObject.DefaultsApplied += onDefaultsApplied;

            OnApply(hitObject);
            HitObjectApplied?.Invoke(this);

            // If not loaded, the state update happens in LoadComplete(). Otherwise, the update is scheduled to allow for lifetime updates.
            if (IsLoaded)
            {
                Schedule(() => updateState(ArmedState.Idle, true));
            }

            hasHitObjectApplied = true;
        }