Ver Fonte

docs(lib): 为几个基本模块撰写文档

dzp há 3 anos atrás
pai
commit
e221b2fe89
5 ficheiros alterados com 294 adições e 83 exclusões
  1. 29 22
      mud/pkuxkx/plugins/lib/sync.tin
  2. 83 28
      plugins/lib/event.tin
  3. 69 12
      plugins/lib/option.tin
  4. 77 19
      plugins/lib/speedo.tin
  5. 36 2
      plugins/lib/storage.tin

+ 29 - 22
mud/pkuxkx/plugins/lib/sync.tin

@@ -26,34 +26,37 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     {CONFIG}    {}
 };
 
-#nop sync.UUID 可以用来生成一个唯一的同步符号。;
+///=== {
+// #@ sync.UUID
+//    用来生成一个唯一的同步符号。
+// };
 #func {sync.UUID} {
     #return {@uuid{}};
 };
 
-/*
-sync.Wait 可以用来和服务器进行同步,该方法有两个参数:
-  1. 回调代码
-     回调代码会在与服务器同步之后执行。
-  2. 同步符号(可选)
-     同步符号参数用来对本次同步进行唯一标识,只能由字母、数字、汉字、减号构成。
-     不能包含空格、分号等在 TinTin++ 中有特殊含义的字符。
-     也不能包含在正则表达式中有特殊含义的字符
-     本参数为可选值,如果省略,则无法通过 sync.Ignore 取消回调
-*/
+///=== {
+// ## sync.Wait <回调代码> [<同步符号>]
+//    用来和服务器进行同步,参数说明如下:
+//    1. 回调代码
+//       回调代码会在与服务器同步之后执行。
+//    2. 同步符号(可选)
+//       同步符号参数用来对本次同步进行唯一标识,只能由大小写字母、数字、连字符、
+//       下划线、小数点、斜线构成
+//       本参数为可选值,如果省略,则无法通过 sync.Ignore 取消回调
+// };
 #alias {sync.Wait} {
     #local callback {%1};
     #local uuid     {%2};
 
     #if { {$callback} == {} } {
-        errLog 回调代码不能为空;
+        xtt.Usage sync.Wait 回调代码不能为空;
         #return;
     };
 
-	#if { {$uuid} == {%*{[^a-zA-Z0-9_./-]}%*} } {
-		errLog 同步符号不能这么写;
-		#return;
-	};
+    #if { {$uuid} == {%*{[^a-zA-Z0-9_./-]}%*} } {
+        xtt.Usage sync.Wait 同步符号不能这么写;
+        #return;
+    };
 
     #if { "$uuid" == "" } {
         #format uuid {%U};
@@ -64,15 +67,16 @@ sync.Wait 可以用来和服务器进行同步,该方法有两个参数:
     #send response R:sync-$uuid;
 };
 
-/*
-   函数版的 sync.Wait 相比别名版的 sync.Wait,省略了同步符号参数要求,
-   改由 sync 模块自行生成,并作为返回值返回给用户。
-*/
+///=== {
+// #@ sync.Wait <回调代码>
+//    函数版的 sync.Wait 相比别名版的 sync.Wait,省略了同步符号参数要求,
+//    改由 sync 模块自行生成,并作为返回值返回给用户。
+// };
 #func {sync.Wait} {
     #local callback {%1};
 
     #if { {$callback} == {} } {
-        errLog 回调代码不能为空;
+        xtt.Usage sync.Wait 回调代码不能为空;
         #return;
     };
 
@@ -93,7 +97,10 @@ sync.Wait 可以用来和服务器进行同步,该方法有两个参数:
     #var {gSyncHandlers[$uuid]} {$callback};
 };
 
-#nop sync.Ignore 可以忽略指定的同步信息,将不会再触发回调代码。;
+///=== {
+// ## sync.Ignore <同步符号>
+//    忽略指定的同步信息,将不会再触发回调代码。
+// };
 #alias {sync.Ignore} {
     #local uuid {%1};
     #unvar {gSyncHandlers[$uuid]};

+ 83 - 28
plugins/lib/event.tin

@@ -8,6 +8,11 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
 ===========
 */
 
+///=== {
+///// event 模块实现了一个事件驱动编程框架,
+///// 提供基本的事件驱动编程 API,允许用户定义、发射、订阅事件。
+///// };
+
 #var lib_event[META] {
     {NAME}      {事件驱动编程框架}
     {DESC}      {提供基本的事件驱动编程 API,允许用户定义、发射、订阅事件}
@@ -26,13 +31,27 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
 
 #func {__xtt_event_name_is_valid__} {
     #local name {%1};
-    #if { "$name" == "{[_a-zA-Z]([./_a-zA-Z0-9-]*[./_a-zA-Z0-9])?}" } {
+    #if { "$name" == "{[_a-zA-Z]([./_a-zA-Z0-9-]*[a-zA-Z0-9])?}" } {
         #return {true};
     };
-    
+
     #return {false};
 };
 
+///=== {
+// ## event.Define <名称> <类型> <模块> <说明>
+//    定义事件。事件在使用前必须先定义。事件经过定义后,可以用 event.List 查看。
+//    参数列表:
+//        - 名称:标识事件的唯一名称,只能以拉丁字母或下划线开头,后面跟若干个
+//                字母、数字、下划线(_)、斜线(/)、小数点(.) 组成。其中三个
+//                标点符号不能出现在末尾,只能出现在中间。
+//        - 类型:枚举值,{有参} 或者 {无参} 二选一。
+//                如果事件被定义为有参,则允许发射事件时携带参数,事件驱动会将
+//                参数传递给事件处理句柄。
+//        - 模块:标识事件所属模块,一般来说事件发射方为事件所属模块。
+//                这里要用标准的 PaoTin++ 模块描述符。
+//        - 说明:事件的简短说明。会出现在类似于 event.List 的用户交互界面。
+// };
 #alias {event.Define} {
     #local name     {%1};
     #local type     {%2};
@@ -40,7 +59,7 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     #local desc     {%4};
 
     #if { "@__xtt_event_name_is_valid__{{$name}}" != "true" } {
-        errLog 事件名称不是合法的标识符名称。;
+        xtt.Usage event.Define 事件名称不是合法的标识符名称;
         #return;
     };
 
@@ -49,7 +68,7 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     };
 
     #if { "$type" != "{有参|无参}" } {
-        errLog 事件类型只能是「有参」和「无参」两者之一,省略表示「无参」。;
+        xtt.Usage event.Define 事件类型参数值不正确;
         #return;
     };
 
@@ -60,6 +79,10 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     };
 };
 
+///=== {
+// ## event.List
+//    列出所有已定义的事件,以及目前已注册在这些事件上面的钩子。
+// };
 #alias {event.List} {
     #local event {};
 
@@ -91,7 +114,7 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
             #math count {$count + 1};
             #local lead {├};
             #if { $count == &gEventHandlers[$event][] } {
-                #Local lead {╰};
+                #local lead {╰};
             };
             #echo { $lead@str.Repeat{$len;─} %s        %-30s %s}{$hook}
                 {@genModuleLink{$gEventHandlers[$event][$hook][module];MOD}}
@@ -102,18 +125,24 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     #echo {%h};
 };
 
+///=== {
+// ## event.Emit <事件名称> [<回调钩子通配符>] [<事件参数>]
+//    发射事件。这将导致与回调钩子通配符相匹配的回调钩子被立即执行。
+//    默认会触发所有注册在本事件下的事件回调钩子。
+//    你可以参考 event.Handle 理解什么是事件回调钩子。
+// };
 #alias {event.Emit} {
     #local name  {%1};
     #local pHook {%2};
     #local args  {%3};
 
     #if { "@__xtt_event_name_is_valid__{{$name}}" != "true" } {
-        errLog 事件名称不是合法的标识符名称。;
+        xtt.Usage event.Emit 事件名称不是合法的标识符名称;
         #return;
     };
 
     #if { "$gValidEvent[$name]" == "" } {
-        errLog 未定义的事件名称: $name;
+        xtt.Usage event.Emit {未定义的事件名称: $name};
         #return;
     };
 
@@ -127,7 +156,7 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
         #local code     {$gEventHandlers[$name][$hook][code]};
         #nop 如果发射事件时指定了 pHook,则只唤醒指定的 hook,注意这里的 pHook 支持通配符;
         #if { "$pHook" != "" && "$hook" != "$pHook" } {
-            #return;
+            #continue;
         };
         #if { "$options[justOnce]" == "true" } {
             #unvar {gEventHandlers[$name][$hook]};
@@ -141,18 +170,28 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     };
 };
 
+///=== {
+// ## event.Handle <事件名称> <回调钩子> <所属模块> <回调代码>
+//    注册事件回调钩子。参数说明如下:
+//        - 事件名称: 本钩子要关联的事件的名称,需要事先用 event.Define 声明。
+//        - 回调钩子: 本次注册的钩子,可以在随后用来取消本钩子,或者当事件发射时,
+//                    发射方可以用正则表达式指定要触发哪些钩子。
+//        - 所属模块: 注册钩子所在的代码模块。必须是一个严格的 PaoTin++ 模块描述符。
+//        - 回调代码: 用来指明钩子被回调时要执行的代码。
+// };
 #alias {event.Handle} {
     #local name     {%1};
     #local hook     {%2};
     #local module   {%3};
     #local code     {%4};
 
-    #if { "$name" == "" || "$hook" == "" || "$module" == "" || "$code" == "" } {
+    #if { "$name" == "" || "$hook" == "" || "$module" == "" || {$code} == {} } {
+        xtt.Usage event.Handle;
         #return;
     };
 
     #if { "@__xtt_event_name_is_valid__{{$name}}" != "true" } {
-        errLog 事件名称不是合法的标识符名称。;
+        xtt.Usage event.Handle 事件名称不是合法的标识符名称;
         #return;
     };
 
@@ -162,34 +201,29 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     };
 };
 
-#alias {event.UnHandle} {
-    #local name {%1};
-    #local hook {%2};
-
-    #if { "$name" == "" || "$hook" == "" } {
-        #return;
-    };
-
-    #if { "@__xtt_event_name_is_valid__{{$name}}" != "true" } {
-        errLog 事件名称不是合法的标识符名称。;
-        #return;
-    };
-
-    #unvar {gEventHandlers[$name][$hook]};
-};
-
+///=== {
+// ## event.HandleOnce <事件名称> <回调钩子> <所属模块> <回调代码>
+//    注册事件回调钩子,但是本钩子只会被执行一次,然后会自动注销。
+//    参数说明如下:
+//        - 事件名称: 本钩子要关联的事件的名称,需要事先用 event.Define 声明。
+//        - 回调钩子: 本次注册的钩子,可以在随后用来取消本钩子,或者当事件发射时,
+//                    发射方可以用正则表达式指定要触发哪些钩子。
+//        - 所属模块: 注册钩子所在的代码模块。必须是一个严格的 PaoTin++ 模块描述符。
+//        - 回调代码: 用来指明钩子被回调时要执行的代码。
+// };
 #alias {event.HandleOnce} {
     #local name     {%1};
     #local hook     {%2};
     #local module   {%3};
     #local code     {%4};
 
-    #if { "$name" == "" || "$hook" == "" || "$module" == "" || "$code" == "" } {
+    #if { "$name" == "" || "$hook" == "" || "$module" == "" || {$code} == {} } {
+        xtt.Usage event.HandleOnce;
         #return;
     };
 
     #if { "@__xtt_event_name_is_valid__{{$name}}" != "true" } {
-        errLog 事件名称不是合法的标识符名称。;
+        xtt.Usage event.HandleOnce 事件名称不是合法的标识符名称;
         #return;
     };
 
@@ -199,3 +233,24 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
         {code}{$code}
     };
 };
+
+///=== {
+// ## event.UnHandle <事件名称> <事件回调钩子名称>
+//    注销已注册的事件回调钩子。
+// };
+#alias {event.UnHandle} {
+    #local name {%1};
+    #local hook {%2};
+
+    #if { "$name" == "" || "$hook" == "" } {
+        xtt.Usage event.UnHandle;
+        #return;
+    };
+
+    #if { "@__xtt_event_name_is_valid__{{$name}}" != "true" } {
+        xtt.Usage event.UnHandle 事件名称不是合法的标识符名称;
+        #return;
+    };
+
+    #unvar {gEventHandlers[$name][$hook]};
+};

+ 69 - 12
plugins/lib/option.tin

@@ -19,12 +19,28 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     #return {true};
 };
 
+///=== {
+///// 全局选项管理。
+/////
+///// 全局选项本质上就是一些变量,这个模块主要是为了提供一些公共 API,和一个统
+///// 一的管理方式,能够把散落在各处的选项集中管理。
+/////
+// ## option.Define <名称> <数据类型> <中文含义> [<默认值>]
+//    定义一个全局选项。选项被定义之后有一些公共 API 可供使用。
+//    选项必须经过定义之后才能使用。选项的名称按照大骆驼风格命名。
+//    目前仅支持 Bool 型选项。
+// };
 #alias {option.Define} {
     #local option   {%1};
     #local type     {%2};
     #local meaning  {%3};
     #local default  {%4};
 
+    #if { "$option" == "" || "$type" == "" || "$meaning" == "" } {
+        xtt.Usage option.Define;
+        #return;
+    };
+
     #if { "$gOptions[$option]" != "" } {
         errLog 选项「$option」已存在,请检查代码。;
         #return;
@@ -49,22 +65,30 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     };
 };
 
+///=== {
+// ## option.List
+//    列出所有的选项。
+// };
 #alias {option.List} {
     #echo {};
-	#echo {<128>%+20s %+10s %+30s %+20s} {选项名称} {选项类型} {选项含义} {选项当前值};
-	#draw Yellow scroll line 1 1 1 83;
+    #echo {<128>%+20s %+10s %+30s %+20s} {选项名称} {选项类型} {选项含义} {选项当前值};
+    #draw Yellow scroll line 1 1 1 83;
 
     #local option {};
-	#foreach {*gOptions[]} {option} {
-		#echo {%+20s %+10s %+30s %+20s}
+    #foreach {*gOptions[]} {option} {
+        #echo {%+20s %+10s %+30s %+20s}
             {$gOptions[$option][name]}
             {$gOptions[$option][type]}
             {$gOptions[$option][meaning]}
             {$gOptions[$option][value]};
-	};
+    };
     #echo {};
 };
 
+///=== {
+// #@ option.Get <选项名称>
+//    获取选项的当前值。
+// };
 #func {option.Get} {
     #local {option} {%1};
 
@@ -76,6 +100,10 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     #return {$gOptions[$option][value]};
 };
 
+///=== {
+// #@ option.Set <选项名称> <选项新值>
+//    设置选项的当前值,返回之前的旧值。
+// };
 #func {option.Set} {
     #local {option} {%1};
     #local {value}  {%2};
@@ -91,25 +119,42 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     #return {$old};
 };
 
+///=== {
+// ## option.Set <选项名称> <选项值>
+//    设置选项的当前值。
+// };
+#alias {option.Set} {
+    #local {option} {%1};
+    #local {value}  {%2};
+
+    #local _ {@option.Set{$option;{$value}}};
+};
+
+///=== {
+// #@ option.IsEnable <选项名称>
+//    检查选项是否已被开启。
+// };
 #func {option.IsEnable} {
     #local option {%1};
     #local value {@option.Get{$option}};
     #return @isTrue{$value};
 };
 
+///=== {
+// #@ option.IsDisable <选项名称>
+//    检查选项是否已被禁用。
+// };
 #func {option.IsDisable} {
     #local option {%1};
     #local value {@option.Get{$option}};
     #return @isFalse{$value};
 };
 
-#alias {option.Set} {
-    #local {option} {%1};
-    #local {value}  {%2};
-
-    #local _ {@option.Set{$option;{$value}}};
-};
-
+///=== {
+// #@ option.Toggle <选项名称>
+//    翻转选项的当前值。如果是启用则改为禁用;如果是禁用则改为启用。
+//    本函数将返回之前的值。
+// };
 #func {option.Toggle} {
     #local {option} {%1};
 
@@ -126,18 +171,30 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     #return {$value};
 };
 
+///=== {
+// ## option.Toggle <选项名称>
+//    翻转选项的当前值。如果是启用则改为禁用;如果是禁用则改为启用。
+// };
 #alias {option.Toggle} {
     #local {option} {%1};
 
     #local _ {@option.Toggle{$option}};
 };
 
+///=== {
+// ## option.Enable <选项名称>
+//    启用该选项。
+// };
 #alias {option.Enable} {
     #local {option} {%1};
 
     option.Set $option {true};
 };
 
+///=== {
+// ## option.Disable <选项名称>
+//    禁用该选项。
+// };
 #alias {option.Disable} {
     #local {option} {%1};
 

+ 77 - 19
plugins/lib/speedo.tin

@@ -6,6 +6,14 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
 你可以在遵照 GPLv3 协议的基础之上使用、修改及重新分发本程序。
 */
 
+///=== {
+///// speedo(测速仪)模块为各种数据提供测速支持。
+/////
+///// 在游戏中,我们常常需要记录和测算数据的变化率或者一段时间的平均值。
+///// 本模块可以用来实现这个目的。短的比如气血、精神、内力的变化率,长的比如
+///// 经验值、潜能、金钱收益等,都可以用它来测算。
+// };
+
 #var lib_speedo[META] {
     {NAME}      {测速仪}
     {DESC}      {为各种数据提供测速支持,气血/精神/经验值/存款都可以}
@@ -17,20 +25,16 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     #return {true};
 };
 
-#alias {speedo.Add} {
-    #local key          {%1};
-    #local value        {@defaultNum{%2;0}};
-    #local monotone     {@default{%3;false}};
-    #local keepalive    {@defaultNum{%4;60}};
-
-    #if { @isEmpty{$key} } {
-        #return;
-    };
-
-    #math value {$value + @speedo.Get{{$key}}};
-    speedo.Set {$key} {$value} {$monotone} {$keepalive};
-};
-
+///=== {
+// ## speedo.Set <数据名> <数据值> <单调性> <保活时间>
+//    往测速仪中灌注一个数据(覆盖模式)。
+//    单调性决定了数据的变化方向发生翻转时,是累积还是重置之前的统计信息。
+//    如果设置为真,则会重置,否则会累积。比如说一个数据一会儿变大一会儿变小,
+//    在累积模式下,可能一段时间之后,统计出来的结果会是平均变化率很小接近于无。
+//    而重置模式下,永远都可以给出最近一段时间单调变化的变化率。
+//    保活时间参数用来排除停滞变化的时间不予统计。如果最近连续两次更新时间间隔
+//    超过保活时间,则超出部分将会被扣除不参与计算。
+// };
 #alias {speedo.Set} {
     #local key          {%1};
     #local value        {@defaultNum{%2;0}};
@@ -69,11 +73,34 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
         #var {gSpeedo[$key][paused]} {false};
         #math {gSpeedo[$key][deduction]} {$gSpeedo[$key][deduction] + $interval - $keepalive};
     };
-    
+
     #var {gSpeedo[$key][value]}     {$value};
     #var {gSpeedo[$key][update]}    {$now};
 };
 
+///=== {
+// ## speedo.Add <数据名> <数据值> <单调性> <保活时间>
+//    类似于 speedo.Set,但数据值会累加而不是覆盖。其余相同。
+// };
+#alias {speedo.Add} {
+    #local key          {%1};
+    #local value        {@defaultNum{%2;0}};
+    #local monotone     {@default{%3;false}};
+    #local keepalive    {@defaultNum{%4;60}};
+
+    #if { @isEmpty{$key} } {
+        xtt.Usage speedo.Add;
+        #return;
+    };
+
+    #math value {$value + @speedo.Get{{$key}}};
+    speedo.Set {$key} {$value} {$monotone} {$keepalive};
+};
+
+///=== {
+// #@ speedo.Get <数据名>
+//    获取测速仪中储存的数据值。如果该数据之前尚未被存储过,则返回 0。
+// };
 #func {speedo.Get} {
     #local key {%1};
 
@@ -88,11 +115,19 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     #return {$gSpeedo[$key][value]};
 };
 
-#alias {speedo.GetUpdateTime} {
+///=== {
+// #@ speedo.GetUpdateTime <数据名>
+//    获得该数据最近一次更新的时间。
+// };
+#func {speedo.GetUpdateTime} {
     #local key {%1};
     #return {$gSpeedo[$key][update]};
 };
 
+///=== {
+// #@ speedo.GetSpeed <数据名> [<间隔时间>]
+//    获得该数据最近一段时间的变化率。如果省略间隔时间则给出每秒变化值。
+// };
 #func {speedo.GetSpeed} {
     #local key      {%1};
     #local interval {%2};
@@ -105,6 +140,10 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     #return {$speedInfo[speed]};
 };
 
+///=== {
+// #@ speedo.GetSpeedInfo <数据名> [<间隔时间>]
+//    获得该数据最近一段时间的变化率和相关信息。如果省略间隔时间则给出每秒变化值。
+// };
 #func {speedo.GetSpeedInfo} {
     #local key      {%1};
     #local interval {@defaultNum{%2;1}};
@@ -137,11 +176,20 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     };
 };
 
+///=== {
+// ## speedo.Pause <数据名>
+//    暂停计数。暂停之后,直到下次继续,这段期间会在计算中扣除,以获得更精确的
+//    统计数据。
+// };
 #alias {speedo.Pause} {
     #local key {%1};
     #var {gSpeedo[$key][paused]} {true};
 };
 
+///=== {
+// ## speedo.Resume <数据名>
+//    继续计数。参见 speedo.Pause
+// };
 #alias {speedo.Resume} {
     #local key {%1};
 
@@ -153,6 +201,10 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     };
 };
 
+///=== {
+// ## speedo.Reset <数据名>
+//    复位计数器。
+// };
 #alias {speedo.Reset} {
     #local key {%1};
 
@@ -170,12 +222,18 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     #var {gSpeedo[$key][update]}    {$now};
 };
 
+///=== {
+// ## speedo.List <间隔时间> <过滤条件>
+//    列出测速仪中符合条件的数据。如果省略过滤条件(支持正则表达式),则列出所
+//    有的数据。
+//    间隔时间用来指定统计区间,默认给出 1 秒的统计值。
+// };
 #alias {speedo.List} {
     #local interval {@defaultNum{%1;1}};
     #local pattern  {@default{%2;{%*}}};
 
-	#echo {<128>     %-14s %+10s %+10s %+14s %-20s %-20s %+10s} {统计对象} {统计结果} {平均值} {测算周期(秒)} {样本开始时间} {样本截止时间} {发呆时间};
-	#draw Yellow scroll line 1 1 1 110;
+    #echo {<128>     %-14s %+10s %+10s %+14s %-20s %-20s %+10s} {统计对象} {统计结果} {平均值} {测算周期(秒)} {样本开始时间} {样本截止时间} {发呆时间};
+    #draw Yellow scroll line 1 1 1 110;
 
     #local key {};
     #foreach {*gSpeedo[]} {key} {
@@ -184,7 +242,7 @@ PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
         };
 
         #local speedInfo {@speedo.GetSpeedInfo{{$key};{$interval}}};
-	    #echo {<168>     %-14s %+10s %+10s %+14s %t  %t  %+10s}
+        #echo {<168>     %-14s %+10s %+10s %+14s %t  %t  %+10s}
             {$key} {$speedInfo[delta]} {$speedInfo[speed]} {$interval}
             {{%Y-%m-%d %H:%M:%S}{$speedInfo[begin]}}
             {{%Y-%m-%d %H:%M:%S}{$speedInfo[end]}}

+ 36 - 2
plugins/lib/storage.tin

@@ -2,10 +2,24 @@
 
 /*
 本文件属于 PaoTin++ 的一部分。
-PaoTin++ © 2020~2022 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 享有并保留一切法律权利
+PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 享有并保留一切法律权利
 你可以在遵照 GPLv3 协议的基础之上使用、修改及重新分发本程序。
 */
 
+///=== {
+///// storage 模块实现了一个通用的本地存储引擎。
+///// 可以用来存储和载入变量,这允许其它模块可以持久化自己的数据。
+///// 变量会被存储在本地文件系统当中,称之为「存储文件」。
+/////
+///// 存储文件按规定会统一存放在 data 目录下,支持文件重定位。也就是说,
+///// 对于存储文件 file1 来说,其可能的物理存放位置为:
+/////   1. var/data/file1.tin (优先)
+/////   2. data/file1.tin     (其次)
+/////
+///// 各模块可以通过 API storage.Save 按需创建自己的存储文件,在其中存储一个或多个
+///// 变量,并在需要时通过 API storage.Load 提取这些变量。
+///// };
+
 #var lib_storage[META] {
     {NAME}      {通用存储引擎}
     {DESC}      {可以存储和载入变量,这允许其它模块可以持久化自己的数据}
@@ -14,13 +28,23 @@ PaoTin++ © 2020~2022 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
 
 #func {lib_storage.Init} {
     #local _ {@mkdir{data}};
+    storage.Load {storage} {storage-globals};
     #return {true};
 };
 
+///=== {
+// ## storage.Save <文件名> <变量名1> [...]
+//    将由变量名列表所指定的变量及其值存储到指定的存储文件中。
+// };
 #alias {storage.Save} {
     #local file {%1};
     #local vars {%2};
 
+    #if { "$file" == "" || "$vars" == "" } {
+        xtt.Usage storage.Save;
+        #return;
+    };
+
     #class comm-store-tmp open;
     #local var {};
     #foreach {$vars} {var} {
@@ -39,9 +63,19 @@ PaoTin++ © 2020~2022 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 
     };
 };
 
+///=== {
+// ## storage.Load <文件名> <变量名1> [...]
+//    从指定的存储文件中加载变量。
+//    存储文件中实际存储的变量可能更多一些,但本函数可以只加载其中一部分变量。
+// };
 #alias {storage.Load} {
     #local file {%1};
-    #local vars  {%2};
+    #local vars {%2};
+
+    #if { "$file" == "" || "$vars" == "" } {
+        xtt.Usage storage.Load;
+        #return;
+    };
 
     #line quiet #class comm-store-tmp {assign} {load-file data/${file}.tin};
     #local var {};