iT邦幫忙

2021 iThome 鐵人賽

DAY 14
0
Software Development

在麥塊的農場裡寫 Lua系列 第 14

Day14 用 100 吋超大螢幕寫 Code 的感覺 - 用 metatable 改變預設行為

  • 分享至 

  • xImage
  •  

前兩天我已經學會用 CC: Tweaked 電腦讀取磁片和播放音樂
今天我要來寫 Code 啦 !!!
.....
不是本來就在寫 Code 嗎?
但這次我要在麥塊世界裡的超大螢幕寫 Code ~
來看看新的 Monitor 方塊長怎樣?
cctweaked-computer-monitor

.....
螢幕和電腦一比一相同大小,有點搞不清楚誰是誰 XD
這畫面上方是電腦主機,下方是螢幕
跟磁碟機一樣,螢幕可以接在電腦旁邊任何位置
而我在電腦主機輸入

monitor bottom ls

意思就是,執行 ls,但把輸出重新導向到下方螢幕
我目前的位置是新電腦的根目錄,所以螢幕上只顯示 rom

用超大螢幕寫 Code

雖然在現實世界,不會真的幹這種事(要也是拿來看電影打電動啊,怎麼會拿寫 Code)
但至少,我可以在麥塊裡面實現囉!
我發現螢幕方塊是可以堆疊的,兩個相鄰的螢幕會自動合併成更大的螢幕!
比較可惜的是,我原本以為可以無限制堆疊(也太貪心)
但實測後得知,最多可以堆出 長8方塊、高6方塊的大螢幕
但以人物比例來看,應該是有 100 吋吧 XD

我在電腦上輸入

monitor top edit rom/programs/monitor.lua

就形成這個畫面啦!順便找一堆村民來看我寫 Code ...
cctweaked-computer-big-monitor

接下來看看 monitor.lua 在做些什麼事
首先看到這一行很陌生

local monitor = peripheral.wrap(sName)

接著再看 /rom/apis/peripheral.lua
看到一個有趣的新東西 setmetatable

function wrap(name)
    expect(1, name, "string")

    local methods = peripheral.getMethods(name)
    if not methods then
        return nil
    end

    local result = setmetatable({}, {
        __name = "peripheral",
        name = name,
        type = peripheral.getType(name),
    })
    for _, method in ipairs(methods) do
        result[method] = function(...)
            return peripheral.call(name, method, ...)
        end
    end
    return result
end

Metatable on Lua

在 Lua,每個 table 都可以自定義自己的 metatable 來改變預設的行為
預設情況下,metatable 是 nil

t = { 1, 2, 3 }
print(getmetatable(t))  -- nil

我們可以透過 setmetatable 來改變其預設的 metatable
不過在 Lua,只能改 table 的 metatable
其他型別的 metatable,據了解可以透過 C/C++ 修改
還記得之前我印出 table 時,都是得到記憶體位址嗎?
改變 metatable 定義後,我們可以直接印出 table 裡面所有值

t = { 1, 2, 3 }
print(getmetatable(t))  -- nil
print(t)                -- table: 0x20e6b60
setmetatable(t, {
  __tostring = function(table)  -- 改變 table 被 print 的輸出
    local str = ''
    for _, value in pairs(table) do
      str = str .. value .. ','
    end
    return str
  end
})
print(t)                -- 1,2,3,

以下是一些可以透過 metatable 改變的行為,稱為 metamethod
但應該不止這些,有些 metamethod 我沒有深入探究

operator metamethod
+ __add
- __sub
* __mul
/ __div
- (負數) __unm
% __mod
^ (次方) __pow
== __eq
< __lt
<= __le
.. __concat

__name 可以改變預設的型別顯示,雖然我看不出用途...
__len 可以改變計算元素數量的方式
__pairs 可以改變 pairs, iparis 迭代函數取值的規則
__index 改變用 key index 取值的規則
__newindex 改變新增元素的規則
__tostring 改變 table 被 print 的輸出

以下是更多的範例

t1 = { 1, 2, 3 }
t2 = { 4, 5, 6 }
print(getmetatable(t1))     -- nil
print(t1)                   -- table: 0x23d87d0
mt = {
  __name = "mytable",
  __add = function(tableA, tableB)
    local result = setmetatable({}, mt) -- 這邊要記得設定 metatable,這樣新的 table 也仍然是自定義行為
    for i = 1, #tableA do
        table.insert(result, tableA[i])
    end
    for i = 1, #tableB do
        table.insert(result, tableB[i])
    end
    return result
  end,
  __index = function(table, key)        -- 這樣這個 table 就變成存取從 0 index 開始
    return table[key + 1]
  end
}
setmetatable(t1, mt)
setmetatable(t2, mt)
print(t1)                   -- mytable: 0x23d87d0

mt.__tostring = function(table) -- mt["__tostring"] 的語法糖
  local str = ''
  for _, value in pairs(table) do
    str = str .. value .. ','
  end
  return str
end
print(t1)                   -- 1,2,3,
print(t1 + t2)              -- 1,2,3,4,5,6,
print(t1[0])                -- 1

保護 metatable

metatable 可以做如上述這麼大幅度的行為改變
但 Lua 也提供我們方式可以保護 metatable
使用 setmetatable 設定 metatable 之後,就再也不能改變

t = { 1, 2, 3 }
mt = {
  __tostring = function(table)
    local str = ''
    for _, value in pairs(table) do
      str = str .. value .. ','
    end
    return str
  end,
  __metatable = "Access Denied"  -- 可以給任意值
}

setmetatable(t, mt)
print(t)            -- 1,2,3,
setmetatable(t, {}) -- input:15: cannot change a protected metatable

以上就是 Lua metatable 和 metamethod 的應用


上一篇
Day13 用磁碟機播放唱片
下一篇
Day15 Lua 的全域環境變數 _G 與 _ENV
系列文
在麥塊的農場裡寫 Lua30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言