3个不稳定版本

0.3.0 2023年12月12日
0.0.2 2023年10月5日
0.0.1 2023年10月5日

#476 in 数据结构

每月25次下载

MIT/Apache

30KB
447

介绍

constructivism 是一个 Rust 样本库,旨在通过定义和操作Constructs序列来简化结构化数据的构建。本README文件提供了使用 constructivism 的概述以及如何使用 constructivist 库将其内联到您的项目中。

安装

要在您的Rust项目中使用Constructivism,请将其作为依赖项添加到您的 Cargo.toml 文件中

[dependencies]
constructivism = "0.0.2"

或者让cargo来完成这些工作

cargo add constructivism

Constructivism可以作为例如 your_library_constructivism 的内联到您的库中,位于 constructivist 包中。请参阅 说明

指南

另请参阅 examples/tutorial.rs

入门

通常,您从以下内容开始

use constructivism::*;

Constructs和序列

1.1. Constructs:建构主义围绕Constructs的概念展开。您可以通过这种方式派生construct

#[derive(Construct)]
pub struct Node {
    hidden: bool,
    position: (f32, f32),
}

1.2 construct!:您可以使用 construct! 宏来创建Constructs的实例。请注意每个参数开头的点,这是必需的,您会发现这种语法非常有用。

fn create_node() {
    let node = construct!(Node {
        .position: (10., 10.),
        .hidden: true
    });
    assert_eq!(node.position.0, 10.);
    assert_eq!(node.hidden, true);
}

1.3 序列:可以在另一个构造符之前声明一个构造符。`constructivism` 只包含空值,`()` 构造符。`Self -> Base` 是 `constructivism` 中的序列关系。可以省略序列声明,在这种情况下使用 `Self -> Nothing`。如果你想在另一个有意义的构造符之上派生构造符,必须直接使用 `#[construct(/* 序列 */)]` 属性来指定序列。

#[derive(Construct)]
#[construct(Rect -> Node)]
pub struct Rect {
    size: (f32, f32),
}

1.4 构建序列:上述例子中的矩形序列变为 `Rect -> Node -> Nothing`。可以在单个调用中构建整个序列。

fn create_sequence() {
    let (rect, node /* nothing */) = construct!(Rect {
        .hidden,                        // You can write just `.hidden` instead of `.hidden: true`
        .position: (10., 10.),
        .size: (10., 10.),
    });
    assert_eq!(rect.size.0, 10.);
    assert_eq!(node.position.1, 10.);
    assert_eq!(node.hidden, false);
}

1.5 参数:有不同的参数类型(传递给 `construct!(..)` 的东西)

  • 常见:如果没有传递给 `construct!(..)`,则使用 `Default::default()`
  • 默认:如果没有传递给 `construct!(..)`,则使用提供的值
  • 必需:必须传递给 `construct!(..)`
  • 跳过:不能传递给 `construct!(..)`,使用 `Default::default()` 或提供的值。在派生时使用 `#[param]` 属性来配置行为。
#[derive(Construct)]
#[construct(Follow -> Node)]
pub struct Follow {
    offset: (f32, f32),                 // Common, no #[param]

    #[param(required)]                  // Required
    target: Entity,
    
    #[param(default = Anchor::Center)]  // Default
    anchor: Anchor,

    #[param(skip)]                      // Skip with Default::default()
    last_computed_distance: f32,

    #[param(skip = FollowState::None)]  // Skip with provided value
    state: FollowState,
}

#[derive(PartialEq, Debug, Copy, Clone)]
pub struct Entity;

pub enum Anchor {
    Left,
    Center,
    Right,
}

pub enum FollowState {
    None,
    Initialized(f32)
}

1.6 传递参数:当传递参数给 `construct!(..)` 时,必须传递序列所需的所有必需参数,否则将得到编译错误。可以省略非必需参数。

fn create_elements() {
    // omit everything, default param values will be used
    let (rect, node, /* nothing */) = construct!(Rect);
    assert_eq!(node.hidden, false);
    assert_eq!(rect.size.0, 0.);

    // you have to pass target to Follow, the rest can be omitted..
    let (follow, node) = construct!(Follow {
        .target: Entity
    });
    assert_eq!(follow.offset.0, 0.);
    assert_eq!(node.hidden, false);

    // ..or specified:
    let (follow, node) = construct!(Follow {
        .hidden,
        .target: Entity,
        .offset: (10., 10.),

        // last_computed_distance param is skipped, uncommenting
        // the next line will result in compilation error
        // error: no field `last_computed_distance` on type `&follow_construct::Params`
        
        // .last_computed_distance: 10.
    });
    assert_eq!(follow.offset.0, 10.);
    assert_eq!(node.hidden, true);
}

设计和方法

2.1 设计和方法:每个构造符都有自己的设计。可以为构造符的设计实现方法

impl NodeDesign {
    pub fn move_to(&self, entity: Entity, position: (f32, f32)) { }
}

impl RectDesign {
    pub fn expand_to(&self, entity: Entity, size: (f32, f32)) { }
}

2.2 调用方法:可以在构造符的设计上调用方法。方法解析遵循序列顺序

fn use_design() {
    let rect_entity = Entity;
    design!(Rect).expand_to(rect_entity, (10., 10.));
    design!(Rect).move_to(rect_entity, (10., 10.)); // move_to implemented for NodeDesign
}

3.1 :段允许你在构造符的序列中定义和插入段

#[derive(Segment)]
pub struct Input {
    disabled: bool,
}

#[derive(Construct)]
#[construct(Button -> Input -> Rect)]
pub struct Button {
    pressed: bool
}

3.2 带有段的序列:按钮的序列变为 `Button -> Input -> Rect -> Node -> Nothing`。可以在单个 `construct!` 调用中实例化包含段的构造符的整个序列

fn create_button() {
    let (button, input, rect, node) = construct!(Button {
        .disabled: true
    });
    assert_eq!(button.pressed, false);
    assert_eq!(input.disabled, true);
    assert_eq!(rect.size.0, 100.);
    assert_eq!(node.position.0, 0.);
}

3.3 段设计:段也有自己的设计。方法调用同样在序列顺序中解析。段的设计有一个泛型参数 - 下一个段/构造符,因此实现段的设计时必须遵守它。

impl<T> InputDesign<T> {
    fn focus(&self, entity: Entity) {
        /* do the focus stuff */
    }
}

fn focus_button() {
    let btn = Entity;
    design!(Button).focus(btn);
}

属性

4.1 属性:通过推导构建或段,您还可以设置和获取与序列相关的项的属性

fn button_props() {
    let (mut button, mut input, mut rect, mut node) = construct!(Button);
    
    // You can access to props knowing only the top-level Construct
    let pos         /* Prop<Node, (f32, f32)> */    = prop!(Button.position);
    let size        /* Prop<Rect, (f32, f32)> */    = prop!(Button.size);
    let disabled    /* Prop<Input, bool> */         = prop!(Button.disabled);
    let pressed     /* Prop<Button, bool */         = prop!(Button.pressed);

    // You can read props. You have to pass exact item to the get()
    let x = pos.get(&node).as_ref().0;
    let w = size.get(&rect).as_ref().0;
    let is_disabled = *disabled.get(&input).as_ref();
    let is_pressed = *pressed.get(&button).as_ref();
    assert_eq!(0., x);
    assert_eq!(100., w);
    assert_eq!(false, is_disabled);
    assert_eq!(false, is_pressed);

    // You can set props. You have to pass exact item to set()
    pos.set(&mut node, (1., 1.));
    size.set(&mut rect, (10., 10.));
    disabled.set(&mut input, true);
    pressed.set(&mut button, true);
    assert_eq!(node.position.0, 1.);
    assert_eq!(rect.size.0, 10.);
    assert_eq!(input.disabled, true);
    assert_eq!(button.pressed, true);

}

4.2 扩展属性:如果您有构建类型的字段,您也可以访问这些字段的属性

#[derive(Construct, Default)]
#[construct(Vec2 -> Nothing)]
pub struct Vec2 {
    x: f32,
    y: f32,
}

#[derive(Construct)]
#[construct(Node2d -> Nothing)] 
pub struct Node2d {
    #[prop(construct)]      // You have to mark expandable props with #[prop(construct)]
    position: Vec2,
}

fn modify_position_x() {
    let mut node = construct!(Node2d);
    assert_eq!(node.position.x, 0.);
    assert_eq!(node.position.y, 0.);

    let x = prop!(Node2d.position.x);

    x.set(&mut node, 100.);
    assert_eq!(node.position.x, 100.);
    assert_eq!(node.position.y, 0.);
}

自定义构建器

5.1 自定义构建器:有时您可能需要为外部类型实现构建或提供自定义构建器。您可以使用 derive_construct! 来实现此目的

pub struct ProgressBar {
    min: f32,
    val: f32,
    max: f32,
}


impl ProgressBar {
    pub fn min(&self) -> f32 {
        self.min
    }
    pub fn set_min(&mut self, min: f32) {
        self.min = min;
        if self.max < min {
            self.max = min;
        }
        if self.val < min {
            self.val = min;
        }
    }
    pub fn max(&self) -> f32 {
        self.max
    }
    pub fn set_max(&mut self, max: f32) {
        self.max = max;
        if self.min > max {
            self.min = max;
        }
        if self.val > max {
            self.val = max;
        }
    }
    pub fn val(&self) -> f32 {
        self.val
    }
    pub fn set_val(&mut self, val: f32) {
        self.val = val.max(self.min).min(self.max)
    }
}

derive_construct! {
    // Sequence
    seq => ProgressBar -> Rect;

    // Constructor, all params with default values
    construct => (min: f32 = 0., max: f32 = 1., val: f32 = 0.) -> {
        if max < min {
            max = min;
        }
        val = val.min(max).max(min);
        Self { min, val, max }
    };

    // Props using getters and setters
    props => {
        min: f32 = [min, set_min];
        max: f32 = [max, set_max];
        val: f32 = [val, set_val];
    };
}

5.2 使用自定义构建器:在创建实例时将调用提供的构建器

fn create_progress_bar() {
    let (pb, _, _) = construct!(ProgressBar { .val: 100. });
    assert_eq!(pb.min, 0.);
    assert_eq!(pb.max, 1.);
    assert_eq!(pb.val, 1.);
}

5.3 自定义构建属性:在上面的示例中,derive_construct! 使用获取器和设置器声明属性。当您使用 Prop::getProp::set

fn modify_progress_bar() {
    let (mut pb, _, _) = construct!(ProgressBar {});
    let min = prop!(ProgressBar.min);
    let val = prop!(ProgressBar.val);
    let max = prop!(ProgressBar.max);

    assert_eq!(pb.val, 0.);

    val.set(&mut pb, 2.);
    assert_eq!(pb.val, 1.0); //because default for max = 1.0

    min.set(&mut pb, 5.);
    max.set(&mut pb, 10.);
    assert_eq!(pb.min, 5.);
    assert_eq!(pb.val, 5.);
    assert_eq!(pb.max, 10.);
}

5.4 推导段:您可以以类似的方式推导段

pub struct Range {
    min: f32,
    max: f32,
    val: f32,
}
derive_segment! {
    // use `seg` to provide type you want to derive Segment
    seg => Range;
    construct => (min: f32 = 0., max: f32 = 1., val: f32 = 0.) -> {
        if max < min {
            max = min;
    }
        val = val.min(max).max(min);
        Self { min, val, max }
    };
    // Props using fields directly
    props => {
        min: f32 = value;
        max: f32 = value;
        val: f32 = value;
    };
}

#[derive(Construct)]
#[construct(Slider -> Range -> Rect)]
pub struct Slider;

fn create_slider() {
    let (slider, range, _, _) = construct!(Slider {
        .val: 10.
    });
    assert_eq!(range.min, 0.0);
    assert_eq!(range.max, 1.0);
    assert_eq!(range.val, 1.0);
}

限制

  • 仅公共结构体(或具有 constructable! 的枚举)
  • 尚不支持泛型(看起来非常可能)
  • 整个继承树参数数量有限(默认版本编译为16,测试为64)
  • 仅静态结构体/枚举(无生命周期)

成本

我没有进行任何压力测试。它应该运行得相当快:没有堆分配,只有一些对每个 construct! 定义的属性在每个深度级别进行的解引用调用。冷编译时间随着参数数量限制的增加而增长(64个参数为1.5分钟),但二进制文件的大小没有变化。

路线图

  • #![forbid(missing_docs)] 添加到每个存储库的根目录
  • docstring 绕过
  • 泛型
  • 联合参数,因此您只能从组中传递一个参数。例如,范围可以具有 minmaxabsrel 构建器参数,并且您不能同时传递 absrel
  • 嵌套构建推理(看起来是可能的)
#[derive(Construct, Default)]
pub struct Vec2 {
    x: f32,
    y: f32
}
#[derive(Construct)]
pub struct Div {
    position: Vec2,
    size: Vec2,
}
fn step_inference() {
    let div = construct!(Div {
        position: {{ x: 23., y: 20. }},
        size: {{ x: 23., y: 20. }}
    })
}

贡献

我欢迎对Constructivism的贡献!如果您想贡献或有任何问题,请随时打开一个问题或提交一个拉取请求。

许可

constructivism 根据以下任一许可进行双许可

这意味着您可以选择您喜欢的许可!这种双许可方法是Rust生态系统中的事实标准,并且有很好的理由包括两者。

依赖项

~0.6–1.2MB
~28K SLoC