npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

com.xmobitea.changx.observertype

v1.5.0

Published

XmobiTea Unity Toolkit packages

Readme

XmobiTea ObserverType

Serializable observer wrappers for primitive and custom values, designed mainly for Unity inspector usage plus manual change notifications through UnityEvent<T>.

AI Quick Contract

If you only need the core usage rules, read this section first.

  • Each observer wraps one raw value plus one UnityEvent<T> named _onValueChanged.
  • SetValue(...) updates the raw value and invokes the event.
  • SetValue(...) does not check equality first. It invokes _onValueChanged even when the new value equals the current value.
  • SetValueWithoutNotify(...) updates the raw value without invoking the event.
  • GetValue() returns the raw value.
  • Most primitive observers support explicit cast from raw type to observer and implicit cast from observer to raw type.
  • ObserverFloat additionally accepts explicit cast from int and long.
  • ObserverDouble additionally accepts explicit cast from float, int, and long.
  • Numeric and char observers also overload ++ and --.
  • ObserverBool, ObserverString, and ObserverCustom<T> do not overload ++ or --.
  • ObserverBool and ObserverChar do not implement IFormattable; all other numeric wrappers do.
  • Important: ++ and -- mutate the raw value but do not invoke onValueChanged.
  • Inspector edits through the custom property drawers also update serialized _rawValue directly and do not invoke onValueChanged.
  • ObserverCustom<T> has no custom property drawer.
  • Most primitive implicit casts are not null-safe. Casting a null observer instance to the raw primitive type will throw.
  • ObserverString can hold a null raw string. Its implicit cast from a null wrapper is safe, but string helper members such as Length, Substring(...), ToString(), and GetHashCode() require _rawValue to be non-null.
  • Explicit cast from string to ObserverString returns null when the source string is null.
  • ObserverCustom<T> can hold a null raw value for reference types. ==, GetHashCode(), ToString(), and some direct Equals(...) overload paths require care when either raw value may be null.

AI Support Files

For AI agents and tooling-specific guidance, also read:

  • AGENTS.md: package-level rules for code generation and maintenance.
  • AI_USAGE.md: short machine-friendly usage rules for using this package without reading the implementation.

What This Package Provides

Primitive/specialized wrappers:

  • ObserverBool
  • ObserverByte
  • ObserverSByte
  • ObserverShort
  • ObserverUShort
  • ObserverInt
  • ObserverUInt
  • ObserverLong
  • ObserverULong
  • ObserverFloat
  • ObserverDouble
  • ObserverChar
  • ObserverString

Generic wrapper:

  • ObserverCustom<T>

Editor support:

  • custom property drawers for the non-generic observer types

AI Capability Matrix

Use this table when generating code quickly:

| Type group | Notify write | Silent write | Implicit read null-safe | ++ / -- | IFormattable | Custom drawer | | --- | --- | --- | --- | --- | --- | --- | | Numeric signed/unsigned except ObserverBool | SetValue(...) | SetValueWithoutNotify(...) | No | Yes | Yes | Yes | | ObserverFloat | SetValue(...) | SetValueWithoutNotify(...) | No | Yes | Yes | Yes | | ObserverDouble | SetValue(...) | SetValueWithoutNotify(...) | No | Yes | Yes | Yes | | ObserverChar | SetValue(...) | SetValueWithoutNotify(...) | No | Yes | No | Yes | | ObserverBool | SetValue(...) | SetValueWithoutNotify(...) | No | No | No | Yes | | ObserverString | SetValue(...) | SetValueWithoutNotify(...) | Yes, for null wrapper only | No | No | Yes | | ObserverCustom<T> | SetValue(...) | SetValueWithoutNotify(...) | No | No | No | No dedicated drawer |

Notes:

  • ObserverFloat has explicit casts from float, int, and long.
  • ObserverDouble has explicit casts from double, float, int, and long.
  • ObserverString explicit cast from null string returns null, not an observer wrapping null.
  • ObserverCustom<T> explicit cast from null creates an observer wrapping null.

Runtime Files

  • Runtime/IObserverType.cs
  • Runtime/ObserverBool.cs
  • Runtime/ObserverByte.cs
  • Runtime/ObserverSByte.cs
  • Runtime/ObserverShort.cs
  • Runtime/ObserverUShort.cs
  • Runtime/ObserverInt.cs
  • Runtime/ObserverUInt.cs
  • Runtime/ObserverLong.cs
  • Runtime/ObserverULong.cs
  • Runtime/ObserverFloat.cs
  • Runtime/ObserverDouble.cs
  • Runtime/ObserverChar.cs
  • Runtime/ObserverString.cs
  • Runtime/ObserverCustom.cs

Exact Runtime Behavior

Shared observer pattern

Almost every observer class follows the same structure:

  1. serialize _rawValue
  2. serialize _onValueChanged
  3. initialize _onValueChanged in the constructor
  4. expose:
    • SetValue(...)
    • SetValueWithoutNotify(...)
    • GetValue()
    • conversion operators
    • equality/comparison helpers

This package is not a full reactive system.

It is a lightweight value-wrapper with manual notification.

SetValue(...)

SetValue(...):

  1. updates _rawValue
  2. invokes onValueChanged

This is the main API to use when you want listeners to react.

It does not skip duplicate values. Calling SetValue(currentValue) still invokes onValueChanged.

SetValueWithoutNotify(...)

Updates _rawValue only.

No event is invoked.

Use it only when silent mutation is intentional.

GetValue()

Returns the current raw value.

onValueChanged

Each wrapper exposes:

public UnityEvent<T> onValueChanged => _onValueChanged;

This is UnityEvent-based, not C# event-based.

Listeners can be:

  • wired in inspector
  • added by code

++ / -- behavior

Numeric and char wrappers implement ++ and --.

Important behavior:

  • they mutate _rawValue directly
  • they do not call SetValue(...)
  • they do not invoke onValueChanged

So this:

observerInt++;

changes the value silently.

If you need notification, use:

observerInt.SetValue(observerInt.GetValue() + 1);

Additional overflow note for unsigned observers:

  • ObserverULong-- when _rawValue is 0 overflows to ulong.MaxValue (standard unchecked C# wrap-around).
  • ObserverByte++ when _rawValue is 255 overflows to 0.
  • All unsigned observers follow the same unchecked wrap-around behavior on overflow.

Avoid using -- on an ObserverULong without first verifying the value is greater than zero.

Equality and comparison

Primitive wrappers implement:

  • ==
  • !=
  • Equals(...)
  • CompareTo(...)
  • GetHashCode()
  • ToString()

ObserverCustom<T> implements only ==, !=, Equals(...), GetHashCode(), and ToString(). It does not implement CompareTo(...).

ObserverString implements Equals(ObserverString) as a method but does not implement IEquatable<ObserverString>.

Comparison/equality is based on _rawValue, not on listener state.

The overloaded == / != operators null-check wrapper references. Most typed Equals(...) and CompareTo(...) overloads do not null-check the wrapper argument before reading other._rawValue, so null-check first when the compared observer may be null.

Null-safety

Important behavior:

  • most primitive wrappers do not null-check in implicit cast to raw type
  • so this can throw:
ObserverInt value = null;
int raw = value;

ObserverString is different:

  • implicit cast to string returns null if the observer is null
  • explicit cast from string returns null if the source string is null
  • if the observer exists but its raw string is null, string-like helpers can throw

ObserverString

ObserverString provides extra string-like helpers:

  • Length
  • indexer this[int index]
  • Substring(...)
  • StartsWith(...)
  • EndsWith(...)

Important behavior:

  • it is still just wrapping a plain string
  • there is no actual encryption or obfuscation despite helper naming like InternalDecryptToString()
  • notification semantics remain the same as other wrappers
  • does not implement IEquatable<ObserverString> (unlike numeric wrappers); Equals(ObserverString) is available as a method overload only
  • implicit cast to string is null-safe: returns null when the observer reference is null
  • explicit cast from string returns null when the source string is null
  • Length, indexer, Substring(...), StartsWith(...), EndsWith(...), ToString(), and GetHashCode() assume _rawValue is non-null

ObserverCustom<T>

ObserverCustom<T> is the generic version.

Important behavior:

  • same SetValue(...) / SetValueWithoutNotify(...) pattern
  • same UnityEvent<T> event storage
  • no custom property drawer
  • equality depends on T.Equals(...)
  • explicit cast from T always creates an observer wrapper, even when T is null
  • implicit cast to T is not null-safe
  • does not implement IComparable or IComparable<T> - do not call CompareTo(...) on it
  • does not implement IFormattable
  • ==, GetHashCode(), ToString(), and Equals(ObserverCustom<T>) are not safe for every null raw-value combination
  • when T can be null, prefer comparing GetValue() results with EqualityComparer<T>.Default

This type is usable in code and serializable scenarios where Unity supports the generic payload, but editor UX is much weaker than the primitive wrappers.

Safe nullable custom comparison pattern:

using System.Collections.Generic;

bool AreEqual<T>(ObserverCustom<T> left, ObserverCustom<T> right)
{
	if (ReferenceEquals(left, right)) return true;
	if (left == null || right == null) return false;

	return EqualityComparer<T>.Default.Equals(left.GetValue(), right.GetValue());
}

ObserverULong notes

ObserverULong has two differences from all other observers:

1. _rawValue is internal instead of private:

internal ulong _rawValue;

Code within the same runtime assembly (com.xmobitea.changx.observer-type.runtime) can read and mutate _rawValue directly without going through SetValue(...), bypassing onValueChanged. From external user assemblies (normal game code) this field is not accessible.

2. Editor drawer uses LongField (signed):

The inspector drawer renders _rawValue using EditorGUI.LongField. For ulong values above long.MaxValue (9,223,372,036,854,775,807), the inspector will display a negative number. No clamping is applied. The underlying serialized value is not corrupted, but inspector UX is unreliable for large ulong values.

Editor Behavior

Custom property drawers exist for the non-generic observer types.

They show:

  1. one field for _rawValue
  2. one foldout area for _onValueChanged

Important behavior:

  • editor drawers write _rawValue directly through SerializedProperty
  • they do not invoke onValueChanged
  • inspector edits are therefore silent state changes

This is consistent with serialized editor editing, not runtime reactive notification.

Per-type editor specifics:

  • ObserverByte: renders as IntSlider clamped to [0, 255].
  • ObserverSByte: renders as IntField clamped to [-128, 127].
  • ObserverShort: renders as IntField clamped to [-32768, 32767].
  • ObserverUShort: renders as IntField clamped to [0, 65535].
  • ObserverUInt: renders as LongField clamped to [0, 4294967295].
  • ObserverChar: renders as TextField. Only the first character is stored; empty string stores '\0'.
  • ObserverULong: renders as LongField (signed). Values above long.MaxValue display incorrectly. No clamping.

Basic Usage

Runtime notification

using UnityEngine;
using XmobiTea.ObserverType;

public sealed class ExampleObserverUsage : MonoBehaviour
{
	[SerializeField] private ObserverInt coins = new ObserverInt();

	private void Awake()
	{
		coins ??= new ObserverInt();
		coins.onValueChanged.AddListener(OnCoinsChanged);
	}

	private void OnDestroy()
	{
		if (coins != null)
			coins.onValueChanged.RemoveListener(OnCoinsChanged);
	}

	private void Start()
	{
		coins.SetValue(10);
	}

	private void OnCoinsChanged(int value)
	{
		Debug.Log("Coins changed: " + value);
	}
}

Silent update

coins.SetValueWithoutNotify(100);

Primitive conversion

ObserverInt hp = (ObserverInt)10;
int rawHp = hp;

Correct increment with notification

coins.SetValue(coins.GetValue() + 1);

Do / Don't

Do

  • Do use SetValue(...) when listeners must react.
  • Do use SetValueWithoutNotify(...) only when silent mutation is intentional.
  • Do assume inspector edits are silent.
  • Do treat ++ / -- as silent mutation helpers.
  • Do null-check observer references before implicit conversion in code where the wrapper may be null.
  • Do initialize serialized observer fields inline or guard them before adding listeners when components may be created from code.
  • Do remove runtime listeners you add with AddListener(...) when the owning object is destroyed or disabled.

Don't

  • Don't assume ++ / -- triggers onValueChanged.
  • Don't assume inspector edits trigger onValueChanged.
  • Don't treat this package as a full reactive binding framework.
  • Don't assume ObserverCustom<T> has the same editor support as primitive wrappers.
  • Don't rely on implicit cast from a null primitive observer wrapper.
  • Don't add runtime listeners repeatedly without a matching cleanup path.

Common Mistakes

Mistake 1: Using ++ and expecting notification

Wrong:

coins++;

This changes _rawValue silently.

Correct:

coins.SetValue(coins.GetValue() + 1);

Mistake 2: Assuming inspector edits invoke listeners

They do not.

The drawers only assign serialized _rawValue.

Mistake 3: Implicit cast from null observer

For most primitive wrappers, this throws.

Mistake 3.1: Adding listeners to a null wrapper

Serialized observer fields are class references. If a component is created from code or data is incomplete, the wrapper reference can be null.

Prefer inline initialization plus a defensive guard:

[SerializeField] private ObserverInt score = new ObserverInt();

private void Awake()
{
	score ??= new ObserverInt();
	score.onValueChanged.AddListener(OnScoreChanged);
}

Remove code-added listeners when the owner is destroyed or disabled:

private void OnDestroy()
{
	if (score != null)
		score.onValueChanged.RemoveListener(OnScoreChanged);
}

Mistake 4: Treating ObserverString as encrypted storage

It is not encrypted.

It is just a wrapper around string.

Mistake 5: Calling string helpers while ObserverString stores null

The implicit cast from a null ObserverString wrapper to string is null-safe, but methods on an existing wrapper assume _rawValue is non-null.

Prefer this when a null string is possible:

string rawName = playerName;
if (!string.IsNullOrEmpty(rawName))
{
	Debug.Log(rawName.Substring(0, 1));
}

Mistake 6: Expecting SetValue(...) to skip unchanged values

SetValue(...) always invokes onValueChanged, even when the new raw value equals the current raw value.

Add your own equality guard if duplicate notifications matter:

if (coins.GetValue() != newCoins)
{
	coins.SetValue(newCoins);
}

Mistake 7: Comparing nullable ObserverCustom<T> values with ==

When T can be null, avoid left == right because the operator can call Equals(...) on a null raw value in some one-null/one-non-null cases. Compare raw values through EqualityComparer<T>.Default instead.

Repository Usage Pattern

Current repository usage shows these observer wrappers are intended as:

  • serialized fields in MonoBehaviours
  • inspector-friendly event sources
  • small reactive-style wrappers around primitive values

They are not used as a replacement for a full observable state system.

Decision Table

Use this package when:

  • you want serialized value wrappers with UnityEvent callbacks,
  • you want inspector-exposed primitive observer fields,
  • you accept manual notification semantics.

Do not use this package when:

  • you need full reactive streams,
  • you need automatic event dispatch for every mutation path,
  • you need robust generic inspector support for arbitrary T,
  • you need thread-safe observable state.

Expected AI Usage Pattern

When an AI agent generates code using this package, the correct default pattern is:

  1. Store the observer as a serialized field.
  2. Subscribe to onValueChanged.
  3. Use SetValue(...) for notifying mutations.
  4. Use GetValue() or implicit cast for reads.
  5. Avoid ++ / -- unless silent mutation is intended.

An AI agent should not:

  • assume every mutation path notifies listeners,
  • assume editor edits notify listeners,
  • assume generic observer fields have polished custom inspector support,
  • assume primitive implicit casts are null-safe.

Namespace

using XmobiTea.ObserverType;

Assembly Definitions

Runtime assembly:

com.xmobitea.changx.observer-type.runtime

Runtime assembly details:

  • root namespace: XmobiTea.ObserverType
  • autoReferenced: true
  • no explicit asmdef references
  • available on all platforms

Editor assembly:

com.xmobitea.changx.observer-type.editor

Editor assembly details:

  • root namespace: XmobiTea.ObserverType.Editor
  • references com.xmobitea.changx.observer-type.runtime
  • included only on the Editor platform
  • autoReferenced: true

Package Metadata

  • Package name: com.xmobitea.changx.observertype
  • Version: 1.5.0
  • Unity version: 2022.3+
  • License: Apache-2.0
  • Required dependency: com.xmobitea.changx.app: 1.5.0

The runtime assembly (com.xmobitea.changx.observer-type.runtime) has autoReferenced: true, so normal user assemblies do not need to add an explicit reference to use this package. If a project assembly disables auto references or uses strict asmdef references, add a reference to com.xmobitea.changx.observer-type.runtime.