变量系统
dot 前缀变量系统与五种命名空间
Aria 的变量系统是语言最核心的设计之一。与传统语言使用关键字(let、const、var)不同,Aria 通过 dot 前缀模式将变量分发到不同的存储层。
设计理念
var、val、global、server、client 不是关键字,而是普通标识符。通过 . 运算符访问时,编译器将其分发到对应的存储层。这种设计让变量声明既直观又统一。
5 种命名空间对应 3 层存储:
| 前缀 | 存储层 | 脚本可写 | 线程安全 | 特殊行为 |
|---|---|---|---|---|
var. | LocalStorage | 可写 | 否 | — |
val. | LocalStorage | 不可写(静默忽略) | 否 | 仅宿主可注入(只读槽) |
global. | GlobalStorage | 可写 | 是(ConcurrentHashMap) | 跨上下文共享 |
server. | GlobalStorage | 可写 | 是 | 读取触发 listener |
client. | GlobalStorage | 可写 | 是 | 写入触发 listener |
命名空间完全隔离:裸标识符(无前缀)、
var.、val.是三个互不相通的命名空间, 读写互不回退——var.x = 10之后读裸名x得到的是none(另一个变量),反之亦然。
var.xxx — 局部可变变量
存储在 LocalStorage 的 varVariables(ConcurrentHashMap)中。可以重复赋值。
注意 var.name 与裸标识符 name 是两个不同的变量(命名空间隔离,读写互不回退):
例外:函数调用位置的裸名会回退到 var 存储查找——var.f = -> {...} 之后
可以直接写 f() 调用(详见函数)。
val.xxx — 宿主注入的只读槽
存储在 LocalStorage 的 valVariables 中,使用 ValueReference(区别于 var 的 VariableReference)。
脚本对 val. 的任何写入都会被静默忽略(no-op)——val 不是"脚本内常量",
而是宿主(Java 端)通过 forceSetValue 注入、脚本只读的变量槽:
脚本中需要临时变量或常量时,请使用裸名(当前执行内有效)或 var.(持久存储):
Java 端注入 val
只有 Java 宿主端可以写入 val 变量(forceSetLocalValue / 引用上的 forceSetValue),
这保证了宿主注入的值(如控件句柄、环境常量)不会被脚本污染:
注意:引用上的普通
setValue对 val 同样是 no-op(与脚本写入同一语义), 宿主注入必须使用forceSetValue/forceSetLocalValue。
例外:类体内的 val.x = ... 是字段声明,与 var.x 等价(见类与继承);
模块顶层的 export val.x = ... 也正常导出(见模块系统)。
global.xxx — 全局变量
存储在 GlobalStorage 的 globalVariables(ConcurrentHashMap)中。所有 Context 共享同一个 GlobalStorage 实例,因此 global 变量跨上下文可见,天然线程安全。
典型用途:跨脚本共享状态、全局计数器、配置项。
server.xxx — 服务端变量
存储在 GlobalStorage 的 serverVariables 中,使用 ServerReference。读取时触发 ServerVariableListener,允许宿主程序在脚本读取变量时动态提供值。
适用场景:脚本需要从宿主程序获取实时数据(如游戏状态、配置信息)。
client.xxx — 客户端变量
存储在 GlobalStorage 的 clientVariables 中,使用 ClientReference。写入时触发 ClientVariableListener,允许宿主程序响应脚本的状态变更。
适用场景:脚本向宿主程序推送状态变更(如 UI 更新、事件通知)。
裸标识符查找规则
不带前缀的标识符(裸标识符)通过 ScopeStack 查找。ScopeStack 是一个数组实现的块级作用域栈,查找时从栈顶向下遍历:
查找流程(ScopeStack.get(key)):
- 从当前层(栈顶)向下逐层查找
- 如果本栈未找到,沿
parent链查找 - 如果仍未找到,在当前层自动创建一个新的引用(初始值为
none)
裸标识符不会回退到 var / val 存储(也不会反向回退);
读取未定义的裸名得到 none,不报错。
~= 初始化运算符
~= 是 Aria 特有的初始化运算符。语义:如果变量已存在则获取当前值,不存在则初始化为右侧的值。
典型用途:在可能被多次执行的代码块中安全地初始化变量,避免重复赋值覆盖已有状态。
self 和 args 特殊标识符
self
当前对象引用,在类方法中使用。Context 内部维护一个 self 字段,创建函数调用上下文时通过 createCallContext(self, args) 注入。
args
函数参数列表。Context 内部维护一个 IValue<?>[] 数组,通过索引访问各参数:
越界读取 args[i] 返回 none(不抛异常)。
self 默认为 NoneValue.NONE,args 默认为空数组。
作用域规则
Aria 的作用域由 3 层存储 + ScopeStack 共同构成:
存储层级
函数体作用域隔离
函数(-> {})调用时创建全新的 ScopeStack,与定义处的裸名临时变量完全隔离——
体内读外层裸名得 none,体内写裸名也不影响外层。函数体与外层共享的只有
var / val(LocalStorage)、global / server / client(GlobalStorage)与 self / args。
跨函数共享的状态请使用 var. 存储。
异步(async / await)
async { ... } 是一个表达式,把块体提交到线程池并发执行,立即返回一个 Promise;await 阻塞当前线程取回结果。
实现保证:
- 真并发:块体在
ThreadPoolManager的 worker 线程执行,主线程不阻塞(直到await);块体只执行一次。 - 作用域隔离:块体用全新
ScopeStack——外层裸名不可见;共享的是GlobalStorage/var存储与self/args。 - 沙箱传播:当前沙箱配置随任务传播到 worker 线程——受限沙箱下 async 块体同样受命名空间/资源限制约束。
- 异常:块体抛出的异常会让 Promise 进入 rejected,
await时抛出。
并发注意:async 块体与主线程共享
var/global存储。推荐块体「只读外层 + 计算 + 返回」;多个线程并发修改同一变量与普通多线程一样需自行同步。
