Remove Bytes, FromBytes, Labels, EntityLabels. Document rest of bevy_core and enable warning on missing docs. (#3521)

This PR is part of the issue #3492.

# Objective

 - Clean up dead code in `bevy_core`.
 - Add and update the `bevy_core` documentation to achieve a 100% documentation coverage.
 - Add the #![warn(missing_docs)] lint to keep the documentation coverage for the future.

# Solution

 - Remove unused `Bytes`, `FromBytes`, `Labels`, and `EntityLabels` types and associated systems.
 - Made several types private that really only have use as internal types, mostly pertaining to fixed timestep execution.
 - Add and update the bevy_core documentation.
 - Add the #![warn(missing_docs)] lint.

# Open Questions

Should more of the internal states of `FixedTimestep` be public? Seems mostly to be an implementation detail unless someone really needs that fixed timestep state.
This commit is contained in:
James Liu 2022-01-02 20:36:40 +00:00
parent 363bdf78dc
commit dc8fefe27f
12 changed files with 80 additions and 484 deletions

View file

@ -1,103 +0,0 @@
pub use bevy_derive::Bytes;
// NOTE: we can reexport common traits and methods from bytemuck to avoid requiring dependency most of
// the time, but unfortunately we can't use derive macros that way due to hardcoded path in generated code.
pub use bytemuck::{bytes_of, cast_slice, Pod, Zeroable};
// FIXME: `Bytes` trait doesn't specify the expected encoding format,
// which means types that implement it have to know what format is expected
// and can only implement one encoding at a time.
// TODO: Remove `Bytes` and `FromBytes` in favour of `crevice` crate.
/// Converts the implementing type to bytes by writing them to a given buffer
pub trait Bytes {
/// Converts the implementing type to bytes by writing them to a given buffer
fn write_bytes(&self, buffer: &mut [u8]);
/// The number of bytes that will be written when calling `write_bytes`
fn byte_len(&self) -> usize;
}
impl<T> Bytes for T
where
T: Pod,
{
fn write_bytes(&self, buffer: &mut [u8]) {
buffer[0..self.byte_len()].copy_from_slice(bytes_of(self))
}
fn byte_len(&self) -> usize {
std::mem::size_of::<Self>()
}
}
/// Converts a byte array to `Self`
pub trait FromBytes {
/// Converts a byte array to `Self`
fn from_bytes(bytes: &[u8]) -> Self;
}
impl<T> FromBytes for T
where
T: Pod,
{
fn from_bytes(bytes: &[u8]) -> Self {
assert_eq!(
bytes.len(),
std::mem::size_of::<T>(),
"Cannot convert byte slice `&[u8]` to type `{}`. They are not the same size.",
std::any::type_name::<T>()
);
unsafe { bytes.as_ptr().cast::<T>().read_unaligned() }
}
}
#[cfg(test)]
mod tests {
use super::{Bytes, FromBytes};
use bevy_math::{Mat4, Vec2, Vec3, Vec4};
fn test_round_trip<T: Bytes + FromBytes + std::fmt::Debug + PartialEq>(value: T) {
let mut bytes = vec![0; value.byte_len()];
value.write_bytes(&mut bytes);
let result = T::from_bytes(&bytes);
assert_eq!(value, result);
}
#[test]
fn test_u32_bytes_round_trip() {
test_round_trip(123u32);
}
#[test]
fn test_f64_bytes_round_trip() {
test_round_trip(123f64);
}
#[test]
fn test_vec2_round_trip() {
test_round_trip(Vec2::new(1.0, 2.0));
}
#[test]
fn test_vec3_round_trip() {
test_round_trip(Vec3::new(1.0, 2.0, 3.0));
}
#[test]
fn test_vec4_round_trip() {
test_round_trip(Vec4::new(1.0, 2.0, 3.0, 4.0));
}
#[test]
fn test_mat4_round_trip() {
test_round_trip(Mat4::IDENTITY);
}
#[test]
fn test_array_round_trip() {
test_round_trip([-10i32; 1024]);
test_round_trip([Vec2::ZERO, Vec2::ONE, Vec2::Y, Vec2::X]);
}
}

View file

@ -1,4 +1,3 @@
use crate::bytes_of;
use std::{
cmp::Ordering,
hash::{Hash, Hasher},
@ -42,12 +41,12 @@ impl Hash for FloatOrd {
fn hash<H: Hasher>(&self, state: &mut H) {
if self.0.is_nan() {
// Ensure all NaN representations hash to the same value
state.write(bytes_of(&f32::NAN))
state.write(bytemuck::bytes_of(&f32::NAN))
} else if self.0 == 0.0 {
// Ensure both zeroes hash to the same value
state.write(bytes_of(&0.0f32))
state.write(bytemuck::bytes_of(&0.0f32))
} else {
state.write(bytes_of(&self.0));
state.write(bytemuck::bytes_of(&self.0));
}
}
}

View file

@ -1,256 +0,0 @@
use bevy_ecs::{
component::Component,
entity::Entity,
query::Changed,
reflect::ReflectComponent,
system::{Query, RemovedComponents, ResMut},
};
use bevy_reflect::Reflect;
use bevy_utils::{HashMap, HashSet};
use std::{
borrow::Cow,
fmt::Debug,
ops::{Deref, DerefMut},
};
/// A collection of labels
#[derive(Component, Default, Reflect)]
#[reflect(Component)]
pub struct Labels {
labels: HashSet<Cow<'static, str>>,
}
impl Debug for Labels {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut list = f.debug_list();
for label in self.iter() {
list.entry(&label);
}
list.finish()
}
}
impl<'a, T, L: Into<Cow<'static, str>>> From<T> for Labels
where
T: IntoIterator<Item = L>,
{
fn from(value: T) -> Self {
let mut labels = HashSet::default();
for label in value {
labels.insert(label.into());
}
Self { labels }
}
}
impl Labels {
pub fn contains<T: Into<Cow<'static, str>>>(&self, label: T) -> bool {
self.labels.contains(&label.into())
}
pub fn insert<T: Into<Cow<'static, str>>>(&mut self, label: T) {
self.labels.insert(label.into());
}
pub fn remove<T: Into<Cow<'static, str>>>(&mut self, label: T) {
self.labels.remove(&label.into());
}
pub fn iter(&self) -> impl Iterator<Item = &str> {
self.labels.iter().map(|label| label.deref())
}
}
/// Maintains a mapping from [Entity](bevy_ecs::prelude::Entity) ids to entity labels and entity
/// labels to [Entities](bevy_ecs::prelude::Entity).
#[derive(Debug, Default)]
pub struct EntityLabels {
label_entities: HashMap<Cow<'static, str>, Vec<Entity>>,
entity_labels: HashMap<Entity, HashSet<Cow<'static, str>>>,
}
impl EntityLabels {
pub fn get(&self, label: &str) -> &[Entity] {
self.label_entities
.get(label)
.map(|entities| entities.as_slice())
.unwrap_or(&[])
}
}
pub(crate) fn entity_labels_system(
mut entity_labels: ResMut<EntityLabels>,
removed_labels: RemovedComponents<Labels>,
query: Query<(Entity, &Labels), Changed<Labels>>,
) {
let entity_labels = entity_labels.deref_mut();
for entity in removed_labels.iter() {
if let Some(labels) = entity_labels.entity_labels.get(&entity) {
for label in labels.iter() {
if let Some(entities) = entity_labels.label_entities.get_mut(label) {
entities.retain(|e| *e != entity);
}
}
}
}
for (entity, labels) in query.iter() {
let current_labels = entity_labels
.entity_labels
.entry(entity)
.or_insert_with(HashSet::default);
for removed_label in current_labels.difference(&labels.labels) {
if let Some(entities) = entity_labels.label_entities.get_mut(removed_label) {
entities.retain(|e| *e != entity);
}
}
for added_label in labels.labels.difference(current_labels) {
entity_labels
.label_entities
.entry(added_label.clone())
.or_insert_with(Vec::new)
.push(entity);
}
*current_labels = labels.labels.clone();
}
}
#[cfg(test)]
mod tests {
use bevy_ecs::{
schedule::{Schedule, Stage, SystemStage},
world::World,
};
use super::*;
fn setup() -> (World, Schedule) {
let mut world = World::new();
world.insert_resource(EntityLabels::default());
let mut schedule = Schedule::default();
schedule.add_stage("test", SystemStage::single_threaded());
schedule.add_system_to_stage("test", entity_labels_system);
(world, schedule)
}
fn holy_cow() -> Labels {
Labels::from(["holy", "cow"].iter().cloned())
}
fn holy_shamoni() -> Labels {
Labels::from(["holy", "shamoni"].iter().cloned())
}
#[test]
fn adds_spawned_entity() {
let (mut world, mut schedule) = setup();
let e1 = world.spawn().insert(holy_cow()).id();
schedule.run(&mut world);
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
assert_eq!(entity_labels.get("holy"), &[e1], "holy");
assert_eq!(entity_labels.get("cow"), &[e1], "cow");
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
}
#[test]
fn add_labels() {
let (mut world, mut schedule) = setup();
let e1 = world.spawn().insert(holy_cow()).id();
schedule.run(&mut world);
world.get_mut::<Labels>(e1).unwrap().insert("shalau");
schedule.run(&mut world);
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
assert_eq!(entity_labels.get("holy"), &[e1], "holy");
assert_eq!(entity_labels.get("cow"), &[e1], "cow");
assert_eq!(entity_labels.get("shalau"), &[e1], "shalau");
}
#[test]
fn remove_labels() {
let (mut world, mut schedule) = setup();
let e1 = world.spawn().insert(holy_cow()).id();
schedule.run(&mut world);
world.get_mut::<Labels>(e1).unwrap().remove("holy");
schedule.run(&mut world);
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
assert_eq!(entity_labels.get("holy"), &[], "holy");
assert_eq!(entity_labels.get("cow"), &[e1], "cow");
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
}
#[test]
fn removes_despawned_entity() {
let (mut world, mut schedule) = setup();
let e1 = world.spawn().insert(holy_cow()).id();
schedule.run(&mut world);
assert!(world.despawn(e1));
schedule.run(&mut world);
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
assert_eq!(entity_labels.get("holy"), &[], "holy");
assert_eq!(entity_labels.get("cow"), &[], "cow");
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
}
#[test]
fn removes_labels_when_component_removed() {
let (mut world, mut schedule) = setup();
let e1 = world.spawn().insert(holy_cow()).id();
schedule.run(&mut world);
world.entity_mut(e1).remove::<Labels>().unwrap();
schedule.run(&mut world);
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
assert_eq!(entity_labels.get("holy"), &[], "holy");
assert_eq!(entity_labels.get("cow"), &[], "cow");
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
}
#[test]
fn adds_another_spawned_entity() {
let (mut world, mut schedule) = setup();
let e1 = world.spawn().insert(holy_cow()).id();
schedule.run(&mut world);
let e2 = world.spawn().insert(holy_shamoni()).id();
schedule.run(&mut world);
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
assert_eq!(entity_labels.get("holy"), &[e1, e2], "holy");
assert_eq!(entity_labels.get("cow"), &[e1], "cow");
assert_eq!(entity_labels.get("shamoni"), &[e2], "shamoni");
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
}
#[test]
fn removes_despawned_entity_but_leaves_other() {
let (mut world, mut schedule) = setup();
let e1 = world.spawn().insert(holy_cow()).id();
schedule.run(&mut world);
let e2 = world.spawn().insert(holy_shamoni()).id();
schedule.run(&mut world);
assert!(world.despawn(e1));
schedule.run(&mut world);
let entity_labels = world.get_resource::<EntityLabels>().unwrap();
assert_eq!(entity_labels.get("holy"), &[e2], "holy");
assert_eq!(entity_labels.get("cow"), &[], "cow");
assert_eq!(entity_labels.get("shamoni"), &[e2], "shamoni");
assert_eq!(entity_labels.get("shalau"), &[], "shalau");
}
}

View file

@ -1,20 +1,21 @@
mod bytes;
#![warn(missing_docs)]
//! This crate provides core functionality for Bevy Engine.
mod float_ord;
mod label;
mod name;
mod task_pool_options;
mod time;
pub use bytes::*;
pub use bytemuck::{bytes_of, cast_slice, Pod, Zeroable};
pub use float_ord::*;
pub use label::*;
pub use name::*;
pub use task_pool_options::DefaultTaskPoolOptions;
pub use time::*;
pub mod prelude {
//! The Bevy Core Prelude.
#[doc(hidden)]
pub use crate::{DefaultTaskPoolOptions, EntityLabels, Labels, Name, Time, Timer};
pub use crate::{DefaultTaskPoolOptions, Name, Time, Timer};
}
use bevy_app::prelude::*;
@ -30,6 +31,7 @@ use std::ops::Range;
#[derive(Default)]
pub struct CorePlugin;
/// A `SystemLabel` enum for ordering systems relative to core Bevy systems.
#[derive(Debug, PartialEq, Eq, Clone, Hash, SystemLabel)]
pub enum CoreSystem {
/// Updates the elapsed time. Any system that interacts with [Time] component should run after
@ -47,13 +49,11 @@ impl Plugin for CorePlugin {
.create_default_pools(&mut app.world);
app.init_resource::<Time>()
.init_resource::<EntityLabels>()
.init_resource::<FixedTimesteps>()
.register_type::<HashSet<String>>()
.register_type::<Option<String>>()
.register_type::<Entity>()
.register_type::<Name>()
.register_type::<Labels>()
.register_type::<Range<f32>>()
.register_type::<Timer>()
// time system is added as an "exclusive system" to ensure it runs before other systems
@ -61,9 +61,7 @@ impl Plugin for CorePlugin {
.add_system_to_stage(
CoreStage::First,
time_system.exclusive_system().label(CoreSystem::Time),
)
.add_startup_system_to_stage(StartupStage::PostStartup, entity_labels_system)
.add_system_to_stage(CoreStage::PostUpdate, entity_labels_system);
);
register_rust_types(app);
register_math_types(app);

View file

@ -8,6 +8,11 @@ use std::{
};
/// Component used to identify an entity. Stores a hash for faster comparisons
/// The hash is eagerly re-computed upon each update to the name.
///
/// [`Name`] should not be treated as a globally unique identifier for entities,
/// as multiple entities can have the same name. [`bevy_ecs::entity::Entity`] should be
/// used instead as the default unique identifier.
#[derive(Component, Debug, Clone, Reflect)]
#[reflect(Component)]
pub struct Name {
@ -22,6 +27,9 @@ impl Default for Name {
}
impl Name {
/// Creates a new [`Name`] from any string-like type.
///
/// The internal hash will be computed immediately.
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
let name = name.into();
let mut name = Name { name, hash: 0 };
@ -29,17 +37,25 @@ impl Name {
name
}
/// Sets the entity's name.
///
/// The internal hash will be re-computed.
#[inline(always)]
pub fn set(&mut self, name: impl Into<Cow<'static, str>>) {
*self = Name::new(name);
}
/// Updates the name of the entity in place.
///
/// This will allocate a new string if the name was previously
/// created from a borrow.
#[inline(always)]
pub fn mutate<F: FnOnce(&mut String)>(&mut self, f: F) {
f(self.name.to_mut());
self.update_hash();
}
/// Gets the name of the entity as a `&str`.
#[inline(always)]
pub fn as_str(&self) -> &str {
&self.name

View file

@ -10,45 +10,66 @@ use bevy_ecs::{
use bevy_utils::HashMap;
use std::borrow::Cow;
/// The internal state of each [`FixedTimestep`].
#[derive(Debug)]
pub struct FixedTimestepState {
pub step: f64,
pub accumulator: f64,
step: f64,
accumulator: f64,
}
impl FixedTimestepState {
/// The amount of time each step takes
/// The amount of time each step takes.
pub fn step(&self) -> f64 {
self.step
}
/// The number of steps made in a second
/// The number of steps made in a second.
pub fn steps_per_second(&self) -> f64 {
1.0 / self.step
}
/// The amount of time (in seconds) left over from the last step
/// The amount of time (in seconds) left over from the last step.
pub fn accumulator(&self) -> f64 {
self.accumulator
}
/// The percentage of "step" stored inside the accumulator. Calculated as accumulator / step
/// The percentage of "step" stored inside the accumulator. Calculated as accumulator / step.
pub fn overstep_percentage(&self) -> f64 {
self.accumulator / self.step
}
}
/// A global resource that tracks the individual [`FixedTimestepState`]s
/// for every labeled [`FixedTimestep`].
#[derive(Default)]
pub struct FixedTimesteps {
fixed_timesteps: HashMap<String, FixedTimestepState>,
}
impl FixedTimesteps {
/// Gets the [`FixedTimestepState`] for a given label.
pub fn get(&self, name: &str) -> Option<&FixedTimestepState> {
self.fixed_timesteps.get(name)
}
}
/// A system run criteria that enables systems or stages to run at a fixed timestep between executions.
///
/// This does not guarentee that the time elapsed between executions is exactly the provided
/// fixed timestep, but will guarentee that the execution will run multiple times per game tick
/// until the number of repetitions is as expected.
///
/// For example, a system with a fixed timestep run criteria of 120 times per second will run
/// two times during a ~16.667ms frame, once during a ~8.333ms frame, and once every two frames
/// with ~4.167ms frames. However, the same criteria may not result in exactly 8.333ms passing
/// between each execution.
///
/// When using this run criteria, it is advised not to rely on [`Time::delta`] or any of it's
/// variants for game simulation, but rather use the constant time delta used to initialize the
/// [`FixedTimestep`] instead.
///
/// For more fine tuned information about the execution status of a given fixed timestep,
/// use the [`FixedTimesteps`] resource.
pub struct FixedTimestep {
state: LocalFixedTimestepState,
internal_system: Box<dyn System<In = (), Out = ShouldRun>>,
@ -64,6 +85,7 @@ impl Default for FixedTimestep {
}
impl FixedTimestep {
/// Creates a [`FixedTimestep`] that ticks once every `step` seconds.
pub fn step(step: f64) -> Self {
Self {
state: LocalFixedTimestepState {
@ -74,6 +96,7 @@ impl FixedTimestep {
}
}
/// Creates a [`FixedTimestep`] that ticks once every `rate` times per second.
pub fn steps_per_second(rate: f64) -> Self {
Self {
state: LocalFixedTimestepState {
@ -84,6 +107,8 @@ impl FixedTimestep {
}
}
/// Sets the label for the timestep. Setting a label allows a timestep
/// to be observed by the global [`FixedTimesteps`] resource.
pub fn with_label(mut self, label: &str) -> Self {
self.state.label = Some(label.to_string());
self
@ -106,7 +131,7 @@ impl FixedTimestep {
}
#[derive(Clone)]
pub struct LocalFixedTimestepState {
struct LocalFixedTimestepState {
label: Option<String>, // TODO: consider making this a TypedLabel
step: f64,
accumulator: f64,

View file

@ -55,11 +55,30 @@ impl Stopwatch {
/// stopwatch.tick(Duration::from_secs(1));
/// assert_eq!(stopwatch.elapsed(), Duration::from_secs(1));
/// ```
///
/// # See Also
///
/// [`elapsed_secs`](Stopwatch::elapsed) - if a `f32` value is desirable instead.
#[inline]
pub fn elapsed(&self) -> Duration {
self.elapsed
}
/// Returns the elapsed time since the last [`reset`](Stopwatch::reset)
/// of the stopwatch, in seconds.
///
/// # Examples
/// ```
/// # use bevy_core::*;
/// use std::time::Duration;
/// let mut stopwatch = Stopwatch::new();
/// stopwatch.tick(Duration::from_secs(1));
/// assert_eq!(stopwatch.elapsed_secs(), 1.0);
/// ```
///
/// # See Also
///
/// [`elapsed`](Stopwatch::elapsed) - if a `Duration` is desirable instead.
#[inline]
pub fn elapsed_secs(&self) -> f32 {
self.elapsed().as_secs_f32()

View file

@ -28,9 +28,9 @@ impl Default for Time {
}
impl Time {
/// Updates the internal time measurements.
pub fn update(&mut self) {
let now = Instant::now();
self.update_with_instant(now);
self.update_with_instant(Instant::now());
}
pub(crate) fn update_with_instant(&mut self, instant: Instant) {

View file

@ -1,41 +0,0 @@
use bevy_macro_utils::BevyManifest;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields};
pub fn derive_bytes(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let fields = match &ast.data {
Data::Struct(DataStruct {
fields: Fields::Named(fields),
..
}) => &fields.named,
_ => panic!("Expected a struct with named fields."),
};
let bevy_core_path = BevyManifest::default().get_path(crate::modules::BEVY_CORE);
let fields = fields
.iter()
.map(|field| field.ident.as_ref().unwrap())
.collect::<Vec<_>>();
let struct_name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #bevy_core_path::Bytes for #struct_name #ty_generics #where_clause {
fn write_bytes(&self, buffer: &mut [u8]) {
let mut offset: usize = 0;
#(let byte_len = self.#fields.byte_len();
self.#fields.write_bytes(&mut buffer[offset..(offset + byte_len)]);
offset += byte_len;)*
}
fn byte_len(&self) -> usize {
let mut byte_len: usize = 0;
#(byte_len += self.#fields.byte_len();)*
byte_len
}
}
})
}

View file

@ -2,7 +2,6 @@ extern crate proc_macro;
mod app_plugin;
mod bevy_main;
mod bytes;
mod enum_variant_meta;
mod modules;
@ -10,12 +9,6 @@ use bevy_macro_utils::{derive_label, BevyManifest};
use proc_macro::TokenStream;
use quote::format_ident;
/// Derives the Bytes trait. Each field must also implements Bytes or this will fail.
#[proc_macro_derive(Bytes)]
pub fn derive_bytes(input: TokenStream) -> TokenStream {
bytes::derive_bytes(input)
}
/// Generates a dynamic plugin entry point function for the given `Plugin` type.
#[proc_macro_derive(DynamicPlugin)]
pub fn derive_dynamic_plugin(input: TokenStream) -> TokenStream {

View file

@ -1,2 +1 @@
pub const BEVY_CORE: &str = "bevy_core";
pub const BEVY_UTILS: &str = "bevy_utils";

View file

@ -3,7 +3,6 @@ mod colorspace;
pub use colorspace::*;
use crate::color::{HslRepresentation, SrgbColorSpace};
use bevy_core::Bytes;
use bevy_math::{Vec3, Vec4};
use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize};
use serde::{Deserialize, Serialize};
@ -1035,58 +1034,6 @@ impl MulAssign<[f32; 3]> for Color {
}
}
impl Bytes for Color {
fn write_bytes(&self, buffer: &mut [u8]) {
match *self {
Color::Rgba {
red,
green,
blue,
alpha,
} => {
red.nonlinear_to_linear_srgb().write_bytes(buffer);
green
.nonlinear_to_linear_srgb()
.write_bytes(&mut buffer[std::mem::size_of::<f32>()..]);
blue.nonlinear_to_linear_srgb()
.write_bytes(&mut buffer[2 * std::mem::size_of::<f32>()..]);
alpha.write_bytes(&mut buffer[3 * std::mem::size_of::<f32>()..]);
}
Color::RgbaLinear {
red,
green,
blue,
alpha,
} => {
red.write_bytes(buffer);
green.write_bytes(&mut buffer[std::mem::size_of::<f32>()..]);
blue.write_bytes(&mut buffer[2 * std::mem::size_of::<f32>()..]);
alpha.write_bytes(&mut buffer[3 * std::mem::size_of::<f32>()..]);
}
Color::Hsla {
hue,
saturation,
lightness,
alpha,
} => {
let [red, green, blue] =
HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness);
red.nonlinear_to_linear_srgb().write_bytes(buffer);
green
.nonlinear_to_linear_srgb()
.write_bytes(&mut buffer[std::mem::size_of::<f32>()..]);
blue.nonlinear_to_linear_srgb()
.write_bytes(&mut buffer[std::mem::size_of::<f32>() * 2..]);
alpha.write_bytes(&mut buffer[std::mem::size_of::<f32>() * 3..]);
}
}
}
fn byte_len(&self) -> usize {
std::mem::size_of::<f32>() * 4
}
}
#[derive(Debug)]
pub enum HexColorError {
Length,