๐Ÿ“ฆ Zeenobit / moonshine_tag

Cheap, fast, mostly unique identifiers designed for Bevy.

โ˜… 12 stars โ‘‚ 0 forks ๐Ÿ‘ 12 watching โš–๏ธ MIT License
๐Ÿ“ฅ Clone https://github.com/Zeenobit/moonshine_tag.git
HTTPS git clone https://github.com/Zeenobit/moonshine_tag.git
SSH git clone git@github.com:Zeenobit/moonshine_tag.git
CLI gh repo clone Zeenobit/moonshine_tag
Zeenobit Zeenobit Update README.md 56cc8ad 3 months ago ๐Ÿ“ History
๐Ÿ“‚ main View all commits โ†’
๐Ÿ“ .github
๐Ÿ“ src
๐Ÿ“„ .gitattributes
๐Ÿ“„ .gitignore
๐Ÿ“„ Cargo.toml
๐Ÿ“„ LICENSE
๐Ÿ“„ README.md
๐Ÿ“„ README.md

๐Ÿท๏ธ Moonshine Tag

crates.io downloads docs.rs license stars

Cheap, fast, mostly unique identifiers designed for Bevy.

This crate is also included as part of ๐Ÿธ Moonshine Core.

Overview

A [Tag] represents a cheap, generic, somewhat unique identifier which may be used to associate "things" with each other or to dynamically flag entities.

use bevy::prelude::*;
use moonshine_tag::prelude::*;

tags! { APPLE, ORANGE, JUICY, CRUNCHY, POISONED }

let mut world = World::new();

// Define some fruits!
let fruits = [
    Tags::from([APPLE, CRUNCHY]),
    Tags::from([ORANGE, JUICY]),
    Tags::from([APPLE, CRUNCHY, POISONED])
];

// Only crunchy, edible apples, please! :)
let filter: TagFilter = tag_filter!([APPLE, CRUNCHY] & ![POISONED]);

for fruit in &fruits {
    if filter.allows(fruit) {
        world.spawn(fruit.clone());
    }
}

# assert!(filter.allows(&fruits[0]));

Features

  • Tags are cheap to create, cheap to copy, cheap to compare and "unique enough". It's just a u64.
  • Serialization support for both tags and tag filters
  • Ability to define complex tag filter expressions
  • Reverse lookup of tag names without any manual regitration
  • Simple implementation with no boilerplate and no procedural macros ๐Ÿง˜

Usage

Tags

You may create a [Tag] from any arbitrary string:

use moonshine_tag::prelude::*;

tags! { A }; // Convenient macro

const A1: Tag = Tag::new("A"); // Manual constant

let a2 = Tag::new("A"); // Runtime

assert_eq!(A, A1);
assert_eq!(A, a2);

Any two tags with the same name are considered equal.

[Tags] is a specialized collection for managing sets of tags:

use moonshine_tag::prelude::*;

tags! { A, B, C }

let a = Tags::from(A);
let c = Tags::from([C]);
let ab = Tags::from([A, B]);
let ac = a.union(c);

[Tags] may be used as a [Component] or on its own as a generic collection of tags.

Tag Filters

A tag [TagFilter] is used to test if a given [Tags] set matches a certain pattern:

use moonshine_tag::prelude::*;

tags! { A, B, C }

let a = Tags::from(A);
let c = Tags::from(C);

let a_or_b: TagFilter = TagFilter::any_of([A, B]);

assert!(a_or_b.allows(&a));
assert!(!a_or_b.allows(&c));

Tag filters may be combined which each other to create complex expressions:

use moonshine_tag::prelude::*;

tags! { A, B, C, D }

let ab = Tags::from([A, B]);
let cd = Tags::from([C, D]);
let c = Tags::from(C);

let filter = (TagFilter::all_of([A, B]) | TagFilter::any_of([C, B])) & TagFilter::any_of(D);

assert!(!filter.allows(&ab));
assert!(!filter.allows(&c));
assert!(filter.allows(&cd));

There is also a convenient filter! macro for constructing tag filters from tag expressions:

use moonshine_tag::prelude::*;

tags! { A, B, C, D }

let _: TagFilter = tag_filter!([A, B, ..]);       // Matches any tag set containing A or B
let _: TagFilter = tag_filter!([A, B, ..] | [C]); // Matches any tag set which contains A or B, or exactly C
let _: TagFilter = tag_filter!(![C]);             // Matches any tag set not containing C

โš ๏ธ This macro is still in development.

Tag Names

When debugging or implementing tools, it is often useful to have some human-friendly representation of tags.

There are two methods provided to for human-friendly identification of tags:

  • [pretty_hash]
  • This returns a base31-encoded string representation of the tag's hash value.
  • This is generally fast and suitable for most use cases.
  • [resolve_name]
  • This returns the actual name of the tag.
  • This is slow as it performs a linear search through all defined tags.
  • If performance is a concern, consider using [pretty_hash] or cache the names in memory.
  • The tag must be defined using the [tags!] macro for this to work, otherwise [pretty_hash] is returned as fallback.

Limitations and Guidelines

Internally, tags are just an FNV-1a (Why?) hash of their string representation. This makes them very cheap to use, but this means they are NOT guaranteed to be unique.

It is the assumption of this library that in most game application domains, this is a minor and unlikely problem.

In most applications, the chance of collision between two different tags within the same subsystem is very low, non-fatal, and easily correctable (just rename one of the tags!).

However, you should NOT use tags for any cryptographic purposes, or as globally unique identifiers.

Instead, prefer to use them for convenient, dynamic pattern matching or flagging "things" within your systems, especially entities.

Support

Please post an issue for any bugs, questions, or suggestions.

You may also contact me on the official Bevy Discord server as @Zeenobit.