Skip to content
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 comp tick #780

Draft
wants to merge 9 commits into
base: dev
Choose a base branch
from
69 changes: 69 additions & 0 deletions Source/Pawnmorphs/Esoteria/Hediffs/Base/HediffCompProps_PM.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#nullable enable

using System.Collections.Generic;
using Verse;

namespace Pawnmorph.Hediffs
{
/// <summary>
/// Properties that define a <see cref="HediffComp_PM{TComp,TProps}"/>. Identical to <see cref="HediffCompProperties"/>
/// except only meant to be used Comps that subclass <see cref="HediffComp_PM"/>.
/// <br/>
/// Note that the <see cref="HediffCompProps_PM{TComp,TProps}"/> subclass knows its own Comp type and is able to perform
/// compile-time checking to ensure it is only used with the correct Comp. You should prefer subclassing that class instead
/// of this one directly. This class exists primarily to serve as a non-generic base class for all Pawnmorpher hediff comp
/// properties.
/// </summary>
/// <seealso cref="HediffComp_PM{TComp,TProps}"/>
public abstract class HediffCompProps_PM : HediffCompProperties
{
}

/// <summary>
/// Properties for <see cref="HediffComp_PM{TComp,TProps}"/>. This is the same as HediffCompProperties but additionally
/// is able to perform verification to ensure that it's only used with the correct HediffComp_PM.
/// <br/>
/// <see cref="HediffCompProperties.compClass"/> is automatically initialized to the correct type as well.
/// </summary>
/// <example>
/// To use, define your classes like so:
/// <code>
/// class MyComp : HediffComp_PM{MyComp, MyCompProps}
/// class MyCompProps : HediffCompProps_PM{MyComp, MyCompProps}
/// </code>
/// </example>
/// <seealso cref="HediffCompProps_PM"/>
/// <seealso cref="HediffComp_PM{TComp,TProps}"/>
/// <typeparam name="TComp">The concrete type of the HediffComp_PM</typeparam>
/// <typeparam name="TProps">The concrete type of the HediffCompProps_PM</typeparam>
public abstract class HediffCompProps_PM<TComp, TProps> : HediffCompProps_PM
where TComp : HediffComp_PM<TComp, TProps>
where TProps : HediffCompProps_PM<TComp, TProps>
{
/// <summary>
/// Called once during loading, while Defs are being finalized.
/// Any special initialization behavior for a Def should be done here.
/// </summary>
public override void ResolveReferences(HediffDef parentDef)
{
base.ResolveReferences(parentDef);
compClass ??= typeof(TComp); // Only assign comp class here if the def didn't manually specify one
}

/// <summary>
/// Called once during loading, to check Defs for any configuration errors.
/// Any error checking of Defs should be done here.
/// </summary>
/// <param name="parentDef">The parent hediff def this comp is attached to</param>
/// <returns>An enumerable of any configuration errors detected during loading</returns>
public override IEnumerable<string> ConfigErrors(HediffDef parentDef)
{
foreach (string error in base.ConfigErrors(parentDef)!)
yield return error;

if (compClass != null && !compClass.IsAssignableFrom(typeof(TComp)))
yield return $"PMComp {GetType().Name} attached to {parentDef.defName} had an invalid compClass."
+ $"Was {compClass.Name} but should have been a {typeof(TComp).Name} or subclass.";
}
}
}
127 changes: 127 additions & 0 deletions Source/Pawnmorphs/Esoteria/Hediffs/Base/HediffComp_PM.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#nullable enable

using System;
using Verse;

namespace Pawnmorph.Hediffs
{
/// <summary>
/// A base class for Pawnmorpher hediff comps. This class is very similar to <see cref="Verse.HediffComp"/> but notably
/// does not have an unsealed Tick() method. Instead, a number of TickX() methods are supplied to run logic once every 60,
/// 2500, or 60000 ticks.
/// <br/>
/// This is a performance optimization. Pawnmorpher adds a large number of hediffs, and to keep performance high the amount
/// of code executed every tick should be minimized. Most hediff comps should be implementable with a tick that runs at most
/// once a second, but if you need to implement logic that runs every game tick anyway you should use a regular HediffComp
/// instead.
/// <br/>
/// Note that the <see cref="HediffComp_PM{TComp,TProps}"/> subclass knows its own CompProperties type and is able to perform
/// compile-time checking to ensure the correct CompProperties is used. You should prefer subclassing that class instead of
/// this one directly. This class exists primarily to serve as a non-generic base class for all Pawnmorpher hediff comps.
/// </summary>
/// <seealso cref="HediffComp_PM{TComp,TProps}"/>
public abstract class HediffComp_PM : HediffComp
{
[Unsaved] private Hediff_PM? _parent;

/// <summary>
/// The hediff this comp belongs to.
/// This version is cast to Hediff_PM automatically.
/// </summary>
/// <exception cref="InvalidCastException">If the parent hediff is not a subclass of Hediff_PM</exception>
public Hediff_PM Parent => _parent ??= parent as Hediff_PM
?? throw new
InvalidCastException($"{Def?.defName} was not attached to a Pawnmorpher hediff!"
+ $" Hediff {parent?.def?.defName} should have been a subclass of"
+ $" Hediff_PM but was instead a {parent?.GetType().Name}");

/// <summary>
/// Called once a game tick, but only if this PMComp is attached as a regular comp. All this method does is replicate the
/// behavior <see cref="Hediff_PM{THediff,TDef}.PostTick()"/> when this is used as a regular comp.
/// <br />
/// This method is sealed because it does not normally get called at all. It exists only for backwards compatibility if a
/// PMComp is used as a regular comp instead of a PM comp. If you need to do something every tick, you should create a
/// normal <see cref="Verse.HediffComp"/> instead.
/// </summary>
/// <param name="severityAdjustment"></param>
public sealed override void CompPostTick(ref float severityAdjustment)
{
Log.WarningOnce($"[Pawnmorpher] Comp {GetType().Name} is a PMComp but is still being used as a regular Comp on"
+ $" Hediff {parent?.def?.defName}. It should be set as a PMComp for improved performance.",
Gen.HashCombineInt(4444, parent?.loadID ?? 0));

int hashOffsetTick = Find.TickManager!.TicksGame + Pawn?.HashOffset() ?? 0;

if (hashOffsetTick % 60 == 0) // Every real-life second
TickSecond(ref severityAdjustment);

if (hashOffsetTick % 2500 == 0) // Once an in-game hour
TickHour(ref severityAdjustment);

if (hashOffsetTick % 60000 == 0) // Once an in-game day
TickDay(ref severityAdjustment);
}

/// <summary>
/// Called once every real-time second (every 60 game ticks). This happens 1000 times per Rimworld day.
/// Each call is exactly 1 second apart, but the exact moment it is called is offset by the pawn hash and so is different
/// for every pawn.
/// </summary>
/// <param name="severityAdjustment">The severity will be adjusted by this amount after all comps have ticked</param>
public virtual void TickSecond(ref float severityAdjustment)
{
}

/// <summary>
/// Called once every Rimworld hour (every 2500 game ticks). This happens 24 times per Rimworld day.
/// Each call is exactly 1 hour apart, but the exact moment it is called is offset by the pawn hash and so is different
/// for every pawn.
/// </summary>
/// <param name="severityAdjustment">The severity will be adjusted by this amount after all comps have ticked</param>
public virtual void TickHour(ref float severityAdjustment)
{
}

/// <summary>
/// Called once every Rimworld day (every 60000 game ticks).
/// Each call is exactly 1 day apart, but the exact moment it is called is offset by the pawn hash and so is different
/// for every pawn.
/// </summary>
/// <param name="severityAdjustment">The severity will be adjusted by this amount after all comps have ticked</param>
public virtual void TickDay(ref float severityAdjustment)
{
}
}

/// <summary>
/// A subclass of <see cref="HediffComp_PM"/> that knows its own type and its expected properties type and can
/// perform compile-time verification to ensure the correct property object is being used.
/// <br/>
/// See https://en.wikipedia.org/wiki/Bounded_quantification#F-bounded_quantification if your want to understand what the type
/// bounds are doing doing.
/// </summary>
/// <example>
/// Define your classes like so:
/// <code>
/// class MyComp : HediffComp_PM{MyComp, MyCompProps}
/// class MyCompProps : HediffCompProps_PM{MyComp, MyCompProps}
/// </code>
/// </example>
/// <typeparam name="TComp">The concrete type of the HediffComp_PM</typeparam>
/// <typeparam name="TProps">The concrete type of the HediffCompProps_PM</typeparam>
public abstract class HediffComp_PM<TComp, TProps> : HediffComp_PM
where TComp : HediffComp_PM<TComp, TProps>
where TProps : HediffCompProps_PM<TComp, TProps>
{
[Unsaved] private TProps? _props;

/// <summary>
/// The <see cref="HediffCompProps_PM{TComp, TProps}"/> of this hediff comp, cast to the correct type
/// </summary>
/// <exception cref="InvalidCastException">If the comp properties object were not of the correct type</exception>
public TProps Props => _props ??= props as TProps
?? throw new InvalidCastException($"HediffCompProps for {GetType().Name} had"
+ $" incorrect type! Should have been {typeof(TProps).Name}"
+ $" but was instead {props?.GetType().Name}");
}
}
127 changes: 127 additions & 0 deletions Source/Pawnmorphs/Esoteria/Hediffs/Base/HediffDef_PM.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#nullable enable

using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Verse;

namespace Pawnmorph.Hediffs
{
/// <summary>
/// The base Def class for <see cref="Hediff_PM"/> hediffs. This is mostly identical to <see cref="Verse.HediffDef"/> but
/// features an additional field for <see cref="HediffComp_PM"/> comps. These custom comps are not ticked every tick but
/// rather support ticks every 60, 2500, or 60000 ticks for performance reasons.
/// <br/>
/// Note that the <see cref="HediffDef_PM{THediff,TDef}"/> subclass knows its own Hediff type and automatically ensures it is
/// instantiated as the correct type. You should prefer subclassing that class instead of this one directly. This class
/// exists primarily to serve as a non-generic base class for all Pawnmorpher hediff defs.
/// </summary>
/// <seealso cref="HediffDef_PM{THediff,TDef}"/>
public abstract class HediffDef_PM : HediffDef
{
/// <summary>
/// The Pawnmorpher comps attached to this hediff. <see cref="HediffComp_PM{TComp,TProps}"/> comps are only ticked once a
/// second for performance reasons.
/// </summary>
[UsedImplicitly(ImplicitUseKindFlags.Assign)]
public List<HediffCompProps_PM>? pmComps;

/// <summary>
/// Checks if this Def has a Comp or PMComp of the given type
/// </summary>
/// <param name="compClass">The class of the comp</param>
/// <returns><c>true</c> if the given PMComp is attached to this hediff, or false otherwise</returns>
public bool HasPMComp(Type compClass)
{
return HasComp(compClass) || (pmComps?.Any(comp => comp.compClass == compClass) ?? false);
}

/// <summary>
/// Gets the <see cref="HediffCompProps_PM" /> the given type, if one is attached to this Hediff as a Comp or PMComp
/// </summary>
/// <typeparam name="T">The type of the PMComp properties</typeparam>
/// <returns>
/// The requested PMComp properties, or <c>null</c> if this def does not have the requested properties
/// </returns>
public T? PMCompProps<T>() where T : HediffCompProps_PM
{
var props = CompProps<T>();
if (props != null)
return props;

if (pmComps != null)
foreach (HediffCompProps_PM pmComp in pmComps)
if (pmComp is T comp)
return comp;
return default;
}

/// <summary>
/// Called once during loading, while Defs are being finalized.
/// Any special initialization behavior for a Def should be done here.
/// </summary>
public override void ResolveReferences()
{
base.ResolveReferences();
if (pmComps == null)
return;
foreach (HediffCompProps_PM compProps in pmComps)
compProps.ResolveReferences(this);
}

/// <summary>
/// Called once during loading, to check Defs for any configuration errors.
/// Any error checking of Defs should be done here.
/// </summary>
/// <returns>An enumerable of any configuration errors detected during loading</returns>
public override IEnumerable<string> ConfigErrors()
{
foreach (string str in base.ConfigErrors()!)
yield return str;

if (pmComps != null)
foreach (HediffCompProps_PM t in pmComps)
foreach (string configError in t.ConfigErrors(this)!)
yield return t + ": " + configError;
}
}

/// <summary>
/// A subclass of <see cref="HediffDef_PM"/> that knows its own type and its hediff's type and can automatically initialize
/// its hediff to the correct type.
/// <br/>
/// See https://en.wikipedia.org/wiki/Bounded_quantification#F-bounded_quantification if your want to understand what the type
/// bounds are doing doing.
/// </summary>
/// <example>
/// Define your classes like so:
/// <code>
/// class MyHediff : Hediff_PM{MyHediff, MyHediffDef}
/// class MyHediffDef : HediffDef_PM{MyHediff, MyHediffDef}
/// </code>
/// </example>
/// <seealso cref="HediffDef_PM"/>
/// <typeparam name="THediff">The concrete type of the Hediff_PM</typeparam>
/// <typeparam name="TDef">The concrete type of the HediffDef_PM</typeparam>
public abstract class HediffDef_PM<THediff, TDef> : HediffDef_PM
where THediff : Hediff_PM<THediff, TDef>
where TDef : HediffDef_PM<THediff, TDef>
{
/// <inheritdoc />
public override void ResolveReferences()
{
base.ResolveReferences();
hediffClass ??= typeof(THediff); // Only assign hediff class here if the def didn't manually specify one
}

/// <inheritdoc />
public override IEnumerable<string> ConfigErrors()
{
foreach (string error in base.ConfigErrors())
yield return error;
if (hediffClass != null && !hediffClass.IsAssignableFrom(typeof(THediff)))
yield return $"HediffDef {defName} had an invalid hediffClass. Was {hediffClass.Name} but should have been a"
+ $"{typeof(THediff).Name} or subclass.";
}
}
}
Loading