数据类型¶
可观察对象通过属性向页面提供数据。每个属性都有确定的类型,类型决定它可以参与哪些表达式运算、是否能作为集合的键,以及如何用于文本或其他绑定目标。
本章介绍 SDK 支持的数据类型。Unreal Engine、Unity 等平台的原生类型,由对应平台插件提供绑定支持;具体类型映射和使用限制请阅读各平台文档。
类型支持与目标支持
数据可以进入绑定表达式,不代表任意 HTML 属性或 CSS 样式都能接收它。例如,支持矩阵计算不会自动增加 CSS transform 的矩阵输入能力。绑定结果仍须符合目标要求。
类型一览¶
| 分类 | 类型 |
|---|---|
| 布尔值 | Bool |
| 整数 | Int8、UInt8、Int16、UInt16、Int32、UInt32、Int64、UInt64 |
| 浮点数 | Float、Double |
| 文本 | String |
| 颜色 | Color、ColorF |
| 向量 | Vector2、Vector3 |
| 矩阵 | Matrix4x4 |
| 对象 | Object |
| 集合 | Array、Map、Set |
| 键值对 | KeyValue |
| 空值 | Null |
对象和集合通过引用提供给绑定。强引用保持目标对象的生命周期;弱引用不延长目标对象的生命周期,目标仍有效时,绑定可以访问其属性。
使用 std::shared_ptr<T> 表示强引用,使用 std::weak_ptr<T> 表示弱引用。T 为可观察对象或相应的集合类型。对象通常通过 std::make_shared<T>() 创建,并由应用或其他拥有方保留强引用。
使用对象实例的引用,也可以通过 object 类型的属性持有对象;使用 System.WeakReference<T> 表示弱引用。object 是承载对象引用的语言类型,实际对象仍须符合 SDK 对可观察对象或集合的要求。
C# GC 可以回收不再可达的循环引用对象,不要求为打破循环引用而使用弱引用。弱引用适用于不希望属性延长目标对象生命周期的场景。
强引用和弱引用是同一目标的不同引用方式,不会改变目标对应的 Object、Array、Map 或 Set 分类。弱引用目标被回收后,绑定无法再通过该引用读取目标;弱引用失效本身不会发出属性变更通知。
布尔值¶
Bool 表示 true 或 false,适用于状态、条件判断和复选框等输入。
| C++ | C# | 表达式字面量 |
|---|---|---|
bool |
bool |
true、false |
布尔值支持逻辑运算、相等比较及字符串拼接,不隐式转换为数字。true + 1 无法进行数值加法,true == 1 为 false。
<p>{{ 'Ready: ' + IsReady }}</p>
<p @if="{IsReady}">Ready</p>
布尔值用于文本时输出小写的 true 或 false。
整数¶
SDK 支持四种位宽的有符号与无符号整数:
| SDK 类型 | 范围 | C++ | C# |
|---|---|---|---|
Int8 |
−128 ~ 127 | int8_t |
sbyte |
UInt8 |
0 ~ 255 | uint8_t |
byte |
Int16 |
−32,768 ~ 32,767 | int16_t |
short |
UInt16 |
0 ~ 65,535 | uint16_t |
ushort |
Int32 |
−2³¹ ~ 2³¹−1 | int32_t |
int |
UInt32 |
0 ~ 2³²−1 | uint32_t |
uint |
Int64 |
−2⁶³ ~ 2⁶³−1 | int64_t |
long |
UInt64 |
0 ~ 2⁶⁴−1 | uint64_t |
ulong |
整数可用于计数、索引、标识和数值计算。Array 下标是从 0 开始的整数;Map 支持以上八种整数作为键。
运算结果¶
整数的加、减、乘、取余及一元负号按精确整数运算处理,结果使用 Int64 或 UInt64,不保留较窄的输入类型:
- 负数结果使用
Int64。 - 非负结果不超过
Int64最大值时使用Int64,更大时使用UInt64。 - 超出 −2⁶³ ~ 2⁶⁴−1 的结果返回
Null,不回绕、不截断,也不自动改用浮点数。
整数除法产生 Double,例如 5 / 2 得到 2.5。取余结果的符号跟随被除数,例如 -9 % 4 得到 -1。
浮点数¶
| SDK 类型 | 精度 | C++ | C# |
|---|---|---|---|
Float |
32 位单精度 | float |
float |
Double |
64 位双精度 | double |
double |
浮点数适用于比例、透明度和连续变化的数值。表达式中的小数及科学计数法字面量使用 Double,如 0.5、1e3。
包含 Float 或 Double 的普通数字算术按 Double 计算,结果也是 Double。除零、对零取余、非有限数值参与算术或产生非有限结果时,返回 Null。
大整数转换为浮点数的精度
绝对值超过 2⁵³ 的整数并非都能由 Double 精确表示。将大整数参与浮点计算或结构值缩放时,可能损失精度。需要精确保存的标识等数据应避免这类转换。
字符串¶
String 保存文本。
| SDK 类型 | C++ | C# | 表达式字面量 |
|---|---|---|---|
String |
std::string、std::string_view、MarkupUI::String、MarkupUI::StringView |
string |
使用单引号,例如 'Player' |
表达式中的字符串字面量使用单引号:
<p>{{ 'Player: ' + Name }}</p>
<p>{{ Health + ' HP' }}</p>
+ 的任一侧为字符串时,执行文本拼接。数字文本不会自动解析成数值:'1' + 2 得到字符串 12,'6' / 2 则返回 Null。
字符串支持相等比较;大小比较按 UTF-8 字节序进行字典序比较,不是本地语言排序。字符串中的 HTML 标签作为文本显示,不因数据绑定而自动解析成界面结构。
颜色¶
| SDK 类型 | C++ | C# |
|---|---|---|
Color |
MarkupUI::Color |
MarkupUI.Sdk.Core.Color |
ColorF |
MarkupUI::ColorF |
MarkupUI.Sdk.Core.ColorF |
Color包含R、G、B、A四个UInt8分量,每个分量范围为 0~255。ColorF包含R、G、B、A四个Float分量,常规归一化颜色使用 0~1。
A 为透明度分量。两种颜色类型不因名称相近而自动按 255 进行归一化换算;使用时应按对应类型提供分量。
颜色支持同类型的相等与不等比较,不支持算术、大小比较或直接文本拼接。需要向 CSS 提供颜色文本时,应由应用或转换器给出目标接受的格式。
向量¶
| SDK 类型 | C++ | C# |
|---|---|---|
Vector2 |
MarkupUI::Vector2 |
System.Numerics.Vector2 |
Vector3 |
MarkupUI::Vector3 |
System.Numerics.Vector3 |
Vector2包含X、Y分量,Vector3包含X、Y、Z分量。- SDK 内置向量分量使用
Double;System.Numerics向量分量使用float,进入 SDK 时提升为Double,写回时转换为float,可能产生舍入。
向量作为可观察对象的属性提供给表达式。表达式不提供 Vector3(...) 这样的构造函数调用或向量字面量。
支持的运算¶
| 运算 | 行为 |
|---|---|
同维向量 +、- |
对应分量相加、相减 |
同维向量 *、/ |
对应分量相乘、相除;除数各分量均须非零 |
向量 * 数字、数字 * 向量 |
各分量乘该数字 |
向量 / 数字 |
各分量除以非零数字 |
一元 - |
各分量取负 |
结果保留向量维数。向量乘向量是逐分量乘法,不是点积或叉积。
<!-- Velocity 为 Vector3,Speed 为数字 -->
<p>{{ Velocity * Speed }}</p>
若 Velocity 为 (1, 2, 3)、Speed 为 2,文本输出为 Vector3(2, 4, 6)。该文本用于查看值,不是可反向解析的输入格式。
Vector2 与 Vector3 混算、向量与数字加减、数字除以向量、向量取余及向量与矩阵混算均不支持。SDK 不自动补维、归一化、换算单位或转换坐标系。
矩阵¶
Matrix4x4 是 4 行 4 列的矩阵,可表示平移、缩放、旋转及其组合。SDK 内置类型的十六个分量均为 Double。矩阵在应用代码中创建,再通过可观察对象的属性提供给页面。
| SDK 类型 | C++ | C# |
|---|---|---|
Matrix4x4 |
MarkupUI::Matrix4x4 |
MarkupUI.Sdk.Core.Matrix4x4、System.Numerics.Matrix4x4 |
矩阵使用以下约定:
- 字段与构造参数按行排列:
M11~M14、M21~M24、M31~M34、M41~M44。 - 使用行向量,
p' = p * M。 - 平移分量位于
M41、M42、M43。 A * B表示先应用 A,再应用 B。- 先缩放、再旋转、最后平移写为
S * R * T,其中 S 为缩放矩阵、R 为旋转矩阵、T 为平移矩阵。
MarkupUI.Sdk.Core.Matrix4x4 和 System.Numerics.Matrix4x4 均遵循以上约定。
分量排列¶
Mij 中的 i 表示行,j 表示列。字段和十六参数构造函数均按以下行顺序排列:
M11 M12 M13 M14
M21 M22 M23 M24
M31 M32 M33 M34
M41 M42 M43 M44
SDK 使用行向量约定,点或方向写作 p = [x, y, z, w],应用矩阵的数学公式为 p' = p * M。按行排列说明数据顺序,行向量说明乘法方向,两者含义不同。
x' = x*M11 + y*M21 + z*M31 + w*M41
y' = x*M12 + y*M22 + z*M32 + w*M42
z' = x*M13 + y*M23 + z*M33 + w*M43
w' = x*M14 + y*M24 + z*M34 + w*M44
点通常取 w = 1,方向取 w = 0,因此方向不受平移影响。这些公式用于解释矩阵含义,表达式目前不支持向量与矩阵相乘。
单位矩阵¶
单位矩阵不改变输入点,其四个对角分量为 1,其他分量为 0:
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
C++ 使用 MarkupUI::Matrix4x4::Identity(),C# 使用 MarkupUI.Sdk.Core.Matrix4x4.Identity。
零矩阵不是单位矩阵
零初始化得到所有分量均为 0 的零矩阵,不表示“不做变换”。需要初始变换时应使用单位矩阵。
矩阵数学含义
平移¶
平移分量位于 M41、M42、M43,分别表示 X、Y、Z 方向的位移:
1 0 0 0
0 1 0 0
0 0 1 0
tx ty tz 1
例如,沿三个轴分别平移 10、20、30:
#include <markupui/core/matrix4x4.h>
auto translation = MarkupUI::Matrix4x4::Identity();
translation.M41 = 10;
translation.M42 = 20;
translation.M43 = 30;
using Matrix4x4 = MarkupUI.Sdk.Core.Matrix4x4;
var translation = new Matrix4x4(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
10, 20, 30, 1);
SDK 内置的 C# 矩阵为只读结构。修改矩阵属性时,应创建新值并发送对应的属性变更通知。
缩放¶
缩放因子位于 M11、M22、M33,分别对应 X、Y、Z 轴;M44 保持为 1:
sx 0 0 0
0 sy 0 0
0 0 sz 0
0 0 0 1
三个因子相同表示等比缩放;负值表示对应轴反射,零值使该轴方向压缩为零,并使矩阵不可逆。
旋转¶
下面以绕 Z 轴旋转为例。设旋转角度为 θ,c = cos(θ)、s = sin(θ):
c s 0 0
-s c 0 0
0 0 1 0
0 0 0 1
该公式采用右手数学坐标约定及右手正角:绕 +Z 旋转 +90°,将 +X 方向转向 +Y。实际引擎或屏幕中的视觉方向由平台坐标约定决定。
sin、cos 在应用代码中计算,角度以弧度传入;角度值可通过 角度 * π / 180 转为弧度。矩阵本身不保存角度单位或旋转顺序,也没有表达式内的三角函数调用。
组合顺序¶
在行向量约定下,A * B 表示先应用 A,再应用 B。矩阵乘法通常不可交换,改变顺序会改变变换结果。
应用分别提供缩放、旋转和平移矩阵属性后,可在表达式中组合:
<p>{{ Scaling * Rotation * Translation }}</p>
这里先缩放,再旋转,最后平移。示例显示组合矩阵的文本结果;要用于实际界面变换,绑定目标或转换器还须支持相应矩阵值。
例如,先缩放 (2, 3, 1),再绕 Z 轴旋转 +90°,最后平移 (10, 20, 0),组合矩阵为:
0 2 0 0
-3 0 0 0
0 0 1 0
10 20 0 1
点 (1, 2, 0) 依次变为 (2, 6, 0)、(-6, 2, 0)、(4, 22, 0)。
绕中心点 q 旋转时,先将中心移到原点,再旋转,最后移回,即 T(-q) * R * T(q)。这里 T 表示平移矩阵,R 表示旋转矩阵,由应用准备,不是表达式函数。
支持的运算¶
| 运算 | 行为 |
|---|---|
矩阵 +、- 矩阵 |
对应分量相加、相减 |
矩阵 * 矩阵 |
标准矩阵乘法,不是逐分量乘法 |
矩阵 * 数字、数字 * 矩阵 |
所有分量乘该数字 |
矩阵 / 数字 |
所有分量除以非零数字 |
一元 - |
所有分量取负 |
矩阵除以矩阵、数字除以矩阵、矩阵与数字加减、矩阵取余及矩阵与向量相乘均不支持。
矩阵乘数字不等于空间缩放
Matrix4x4 * 2 会将全部十六个分量乘二,包括平移分量和 M44。空间缩放应由应用准备缩放矩阵,再按所需顺序进行矩阵乘法。
上述运算符描述的是模板表达式的行为,不表示 C++ 的 SDK 矩阵类型提供了相同的运算符重载。矩阵加法也不表示依次应用两个变换。
矩阵文本输出为 Matrix4x4(...),分量按行排列,仅用于查看值。没有矩阵表达式构造语法、逆矩阵或矩阵分解函数,也没有 Quaternion 类型。
C# 类型与平台适配¶
MarkupUI.Sdk.Core.Matrix4x4的分量使用double。System.Numerics.Matrix4x4的分量使用float。
两种类型均可作为属性用于绑定,字段顺序和变换组合顺序一致。System.Numerics.Matrix4x4 进入 SDK 时,各分量提升为 Double,不转置;写回该类型时转换为 float。提升精度不会恢复原本已损失的精度,写回 float 也可能产生舍入。
需要保留 double 精度时使用 SDK 内置类型。使用 System.Numerics.Matrix4x4 时,可以在 C# 中直接调用其变换构造方法:
var matrix = System.Numerics.Matrix4x4.CreateScale(2, 3, 1)
* System.Numerics.Matrix4x4.CreateRotationZ((float)System.Math.PI / 2)
* System.Numerics.Matrix4x4.CreateTranslation(10, 20, 0);
该组合也是先缩放、再旋转、最后平移,无需在绑定表达式中反转顺序。
平台矩阵需要明确转换
SDK 的矩阵约定不等同于所有引擎的原生矩阵。平台插件需要处理分量精度、行列向量约定、坐标轴和单位。
对象、集合¶
| 类型 | 含义 | C++ | C# |
|---|---|---|---|
Object |
对象 | MarkupUI::Binding::IObject |
System.ComponentModel.INotifyPropertyChanged |
Array |
有序列表 | MarkupUI::Binding::IArray<T> |
System.Collections.Generic.IList<T> |
Map |
字典 | MarkupUI::Binding::IMap<TKey, TValue> |
System.Collections.Generic.IDictionary<TKey, TValue> |
Set |
集合 | MarkupUI::Binding::ISet<T> |
System.Collections.Generic.ISet<T> |
KeyValue |
键值对 | MarkupUI::Binding::KeyValue<TKey, TValue> |
System.Collections.Generic.KeyValuePair<TKey, TValue> |
表中的对象与集合类型列出最常用的接口;KeyValue 一行列出键值对结构类型。
集合变更通知接口¶
| 通知 | C++ | C# |
|---|---|---|
| 属性变化 | MarkupUI::Binding::INotifyPropertyChanged |
System.ComponentModel.INotifyPropertyChanged |
| Array、Set 数据项变化 | MarkupUI::Binding::INotifyCollectionChanged<T> |
System.Collections.Specialized.INotifyCollectionChanged |
| Map 键值对变化 | MarkupUI::Binding::INotifyCollectionChanged<MarkupUI::Binding::KeyValue<TKey, TValue>> |
System.Collections.Specialized.INotifyCollectionChanged |
C++ 的 IArray、IMap 和 ISet 已继承对应的集合通知接口。C# 的标准集合接口不包含变更通知契约,需要由具体集合额外实现通知接口。
常用可观察实现¶
| 类型 | C++ | C# |
|---|---|---|
| 对象基类 | MarkupUI::Binding::Modeling::ObservableObject |
System.ComponentModel.INotifyPropertyChanged |
| Array | MarkupUI::Binding::Modeling::ObservableArray<T> |
System.Collections.ObjectModel.ObservableCollection<T> |
| Map | MarkupUI::Binding::Modeling::ObservableMap<TKey, TValue> |
MarkupUI.Sdk.Binding.ObservableDictionary<TKey, TValue> |
| Set | MarkupUI::Binding::Modeling::ObservableSet<T> |
MarkupUI.Sdk.Binding.ObservableHashSet<T> |
ObservableCollection<T> 属于 .NET 标准库;上表中的 C# 可观察字典和可观察 Set 由 SDK 提供。它们是具体集合类,不是 IObservableCollection 或 IObservableDictionary 接口。
对象和集合不进行逐项算术运算,也不因相等比较而递归比较所有字段或内容。
对象及属性访问见基础绑定,集合操作见集合绑定。命令使用独立的交互协议,不属于上述属性值类型。
空值与无效结果¶
Null 表示没有值,不等于 0、false 或空字符串。路径无法取得值,或表达式遇到类型不匹配、除零、越界算术等情况时,可能得到空或不可用结果。
<p>{{ Weapon.Name ?? 'No weapon' }}</p>
<p>{{ (Amount / Divisor) ?? 0 }}</p>
?? 仅为空或不可用结果提供缺省值,不替换 0、false 或空字符串。Unknown 表示未指定或不支持的类型,不是可供业务计算的值。
比较与逻辑判断¶
- 数字可跨整数宽度、有无符号及浮点类型按数值比较;布尔值不属于数字。
- 颜色、向量、矩阵支持同类型、逐分量的精确相等比较,不使用浮点容差,不支持大小比较。
- 不同的非数字类型不自动转换为同一类型,例如字符串
'1'不等于数值1。 - 逻辑判断中,空值为假,布尔值使用自身值,有限数字非零为真,字符串非空为真;有效对象和集合为真,包括空集合。
- 颜色、向量和矩阵用于逻辑判断时均为假,不根据分量是否非零决定真假。应显式提供布尔属性或比较表达式。
表达式的运算符、优先级和短路规则见绑定语法。