最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

從零開(kāi)始的Minecraft - Nbt 序列化庫(kù)開(kāi)發(fā):添加功能:LocateAt注解

2023-02-16 15:04 作者:疑似叉叉星來(lái)的鷯八哥  | 我要投稿

項(xiàng)目開(kāi)篇:項(xiàng)目介紹 & 當(dāng)前開(kāi)發(fā)成果總結(jié)

上一篇:從零開(kāi)始的Minecraft - Nbt 序列化庫(kù)開(kāi)發(fā):當(dāng)上層接口變成“野接口”時(shí)會(huì)發(fā)生什么

項(xiàng)目github地址:https://github.com/Cmyna/mnbt-in-development-

摘要:本篇將簡(jiǎn)單介紹兩個(gè)功能的開(kāi)發(fā):自定義注解LocateAt,修改已有的Tag對(duì)象(OverrideTag)。并且簡(jiǎn)單說(shuō)些細(xì)碎的改動(dòng),比如名字改動(dòng)什么的。

前置知識(shí):本項(xiàng)目相關(guān)的上層接口的理解,java/kotlin的自定義注解相關(guān)內(nèi)容

先簡(jiǎn)單說(shuō)下其他方面的一些改動(dòng):首先Serdes更名為Codec,名字參考的是這篇網(wǎng)站上的討論: https://english.stackexchange.com/questions/76549/a-word-that-describes-both-encoding-and-decoding。同樣的原本Serdes下的兩個(gè)方法serialize,deserialize也被更名為encode和decode。

其次,在文章?從零開(kāi)始的Minecraft - Nbt序列化庫(kù)開(kāi)發(fā):嵌套類(lèi)型Nbt開(kāi)發(fā)(1) - 頂層接口改動(dòng) 中,我對(duì)CompoundTag的要求有誤解,Nbt CompoundTag有要求其中的所有子Tag名字不能重復(fù),因此表示CompoundTag的java Tag對(duì)象被改為T(mén)ag<Map<String, Tag<out Any>>> 而不是原來(lái)的Tag<Collection>。

然后是關(guān)于上一篇“野接口”,因?yàn)門(mén)agConverter/Codec在處理嵌套Tag的子Tag時(shí)是交給代理處理,這個(gè)過(guò)程不可避免的涉及到intent的接口增刪/修改。為了保持intent的健壯性,從Codec/TagConverter傳給代理proxy的intent都通過(guò)動(dòng)態(tài)代理創(chuàng)建,動(dòng)態(tài)代理將繼承傳入intent的所有接口,只增刪/修改覆蓋部分需要的接口和其方法。

然后是一個(gè)應(yīng)用類(lèi)Mnbt,主要是提供之前實(shí)現(xiàn)的各種功能接口的匯總。類(lèi)似于Gson中的Gson類(lèi),用戶(hù)可以通過(guò)這個(gè)類(lèi)調(diào)用該庫(kù)實(shí)現(xiàn)的所有功能。可以查看倉(cāng)庫(kù) api子項(xiàng)目中com.myna.mnbt.Mnbt的實(shí)現(xiàn)

自定義注解LocateAt:

這個(gè)注解是對(duì)現(xiàn)有從類(lèi)似于 javaBean 對(duì)象創(chuàng)建Tag,或者從Tag轉(zhuǎn)換為 javaBean對(duì)象的一個(gè)補(bǔ)充,主要目標(biāo)是提供重映射Class/Field到對(duì)應(yīng)的nbt結(jié)構(gòu)這一功能。在我的設(shè)想中,其功能類(lèi)似于Gson的SerializeName注解,或者是JackJson的@JsonProperty注解;可以對(duì)類(lèi)中的成員映射到另一個(gè)名字的Tag,例如以下的類(lèi)

但不僅只是重映射一個(gè)名字,我還希望注解可以映射更復(fù)雜的nbt結(jié)構(gòu),直接文字描述會(huì)比較復(fù)雜,我用下面一張圖作為例子以輔助解釋我所期望的映射方式:


LocateAt映射方式例子

圖中棕色和黃色的Tag都是Compound Tag,而綠色則可以是任意類(lèi)型的Tag。箭頭指示了Nbt 結(jié)構(gòu),或者是類(lèi)和其成員到Nbt結(jié)構(gòu)的映射關(guān)系,根 Compound Tag指的是最上層調(diào)用 TagConverter.createTag?中返回的Tag對(duì)象,或者是最上層TagConverter.toValue方法傳入的Tag對(duì)象

比如將右邊Foo類(lèi)的對(duì)象實(shí)例轉(zhuǎn)成一個(gè)Tag對(duì)象時(shí),如果沒(méi)有LocateAt注解,那么它只會(huì)被轉(zhuǎn)成一個(gè)特定名字的根CompoundTag, 然后下面帶三個(gè)子Tag,以其成員變量名作為子Tag的名字。但是如果使用了LocateAt注解,那么他可以不是這種結(jié)構(gòu),容載Foo對(duì)象數(shù)據(jù)的CompoundTag被映射到了 根CompoundTag->CompoundTag 1->CompoundTag 2,而其成員變量也被映射到了Compound Tag 2下的子nbt結(jié)構(gòu),如圖所示,成員1被映射到CompoundTag 2下的 Compound Tag3 -> Tag 1,成員2 被映射到?CompoundTag 2下的 Tag2,成員3 則被映射到 Compound Tag 2 下的Compound Tag4 -> Tag 3。

或者在使用LocateAt注解后從根CompoundTag轉(zhuǎn)成Foo對(duì)象時(shí),TagConverter會(huì)查找 根CompoundTag->Compound Tag 1 -> Compound Tag 2 并從這里開(kāi)始轉(zhuǎn)換,轉(zhuǎn)換Foo中的 成員1變量時(shí),會(huì)查找 Compound Tag 2-> Compound Tag 3 -> Tag 1,并將其嘗試轉(zhuǎn)成成員1變量類(lèi)型;諸如此類(lèi)。

在項(xiàng)目中,我是這么聲明LocateAt注解的:

這是一個(gè)kotlin注解,理論上也可以被用于java代碼中,其中有兩個(gè)變量:toTagPath,fromTagPath,分別為createTag和toValue兩個(gè)方法時(shí)映射的nbt結(jié)構(gòu),fromTagPath有默認(rèn)值,所以使用者可以不顯示聲明,當(dāng)使用默認(rèn)值“”(也就是空字符串)時(shí),期望TagConverter使用toTagPath作為替代。

該注解可以用在兩種類(lèi)型:CLASS和FIELD,也就是可以注解在類(lèi)或者成員變量上。

NbtPath 格式:

這里我一直沒(méi)有說(shuō)明注解中String變量的格式。在上面代碼和更上面的圖中都是一種類(lèi)url字符串形式。這里定義NbtPath的格式:開(kāi)頭以"mnbt://"表示絕對(duì)路徑,或者以"./"表示相對(duì)路徑。

路徑主體是以'/'分割的字符串,除了被"/"分割最末尾的子字符串外,路徑中任何中間字符串所代表的都是一個(gè)嵌套類(lèi)型的Nbt Tag對(duì)象(例如list tag或Compound tag)

如果Tag對(duì)象名字為空(例如ListTag下的子Tag),在路徑中以"#"開(kāi)頭,后面帶上數(shù)字則表明為其處于ListTag中的索引位置

LocateAt結(jié)構(gòu)抽象:

首先是對(duì)LocateAt定義的Nbt子結(jié)構(gòu)進(jìn)行抽象。因?yàn)長(zhǎng)ocateAt既可以用于注解類(lèi)也可以注解類(lèi)中字段(成員變量),所以我們額外定義一個(gè)抽象: DataEntryTag,表示這是一個(gè)類(lèi)中所有字段(成員變量)的數(shù)據(jù)入口Tag,如下圖所示:

類(lèi)上注解所映射的結(jié)構(gòu)

注解于類(lèi)上的LocateAt中路徑變量表示的就是這么一個(gè)結(jié)構(gòu),注意 根 CompoundTag并不包含在路徑中,如果注解路徑不為空(包含多個(gè)有效Tag名字),那么路徑最后一段表示的即為DataEntryTag,否則根CompoundTag為DataEntryTag。例如上面的TestClass注解例子,名為“compound tag 2”的Tag為DataEntryTag

從DataEntryTag到字段對(duì)應(yīng)的Tag如下圖:

字段上注解所映射的結(jié)構(gòu)

和注解在類(lèi)上代表的nbt結(jié)構(gòu)類(lèi)似,注解在字段上的LocateAt路徑,如果路徑不為空,取路徑最后一段作為字段對(duì)應(yīng)的Tag對(duì)象名字,否則Tag對(duì)象名字為類(lèi)中字段名字。

在ReflectiveConverter中添加相關(guān)功能:

由此NbtPath字符串格式和LocateAt注解的定義,我們可以開(kāi)始為這個(gè)需求添加具體實(shí)現(xiàn)。由于處理POJO類(lèi)的TagConverter由ReflectiveConverter負(fù)責(zé),所以功能實(shí)現(xiàn)也集中在ReflectiveConverter中。具體功能已經(jīng)在github倉(cāng)庫(kù)gradle子項(xiàng)目api的com.myna.mnbt.converter.ReflectiveConverter中實(shí)現(xiàn)。由于整段功能代碼非常長(zhǎng)且復(fù)雜,所以我就不在此貼具體代碼,而是簡(jiǎn)要概括思路 & 偽代碼。

首先是TagConverter.toValue部分,這部分相對(duì)簡(jiǎn)單,首先以toValue函數(shù)傳入的Tag作為根Tag,根據(jù)是否有LocateAt注解找到DataEntryTag(當(dāng)然如果沒(méi)有注解或注解為空,則toValue傳入的Tag參數(shù)即為DataEntryTag),然后再?gòu)腄ataEntryTag開(kāi)始,根據(jù)類(lèi)字段注解(或沒(méi)有注解,則是類(lèi)字段名稱(chēng))找到字段對(duì)應(yīng)的Tag,再交給proxy轉(zhuǎn)成對(duì)應(yīng)的值,偽代碼如下:

然后是createTag函數(shù)部分的修改。這里有個(gè)特殊情況,即createTag時(shí)可能存在多個(gè)被LocateAt注解的類(lèi)/字段,而他們注解的路徑可能會(huì)部分重疊,例如:

我們可能希望在轉(zhuǎn)成Tag時(shí),這些重疊的路徑聲明不會(huì)影響到最終的Tag對(duì)象生成,例如Foo中字段i和j會(huì)被放在同一個(gè)tag1->tag2的CompoundTag結(jié)構(gòu)中,再比如Bar.bytearr字段生成的Tag會(huì)被放在foo->class tag1->tag1->byte array中,和foo.i共享一段相同的nbt結(jié)構(gòu)(foo->class tag1->tag1->tag2->int tag)。

但是很明顯,目前的ReflectiveConverter難以支持這種功能實(shí)現(xiàn),因?yàn)楝F(xiàn)在處理嵌套類(lèi)型Tag采用的代理模式,讓被代理的TagConverter無(wú)法知道代理者的信息,例如處理Foo對(duì)象時(shí)TagConverter并不知道它的上級(jí)Bar有個(gè)字段和其共享部分相同的nbt結(jié)構(gòu)。處理Foo對(duì)象的TagConverter會(huì)自行創(chuàng)建相同的Nbt結(jié)構(gòu),如果程序之前是先處理Bar的bytearr字段,那么新結(jié)構(gòu)不可避免地會(huì)和原結(jié)構(gòu)沖突。

一種解決這個(gè)問(wèn)題的方法是傳遞已經(jīng)創(chuàng)建的nbt結(jié)構(gòu)信息。舉個(gè)例子,我們模擬在轉(zhuǎn)化Bar對(duì)象到Tag對(duì)象過(guò)程中,ReflectiveConverter先處理bytearr字段再處理foo字段,那么ReflectiveConverter可以先創(chuàng)建一個(gè)根節(jié)點(diǎn),然后把bytearr字段轉(zhuǎn)成的nbt子結(jié)構(gòu)填入其中,然后再把這棵nbt樹(shù)的信息傳給代理proxy,下一層的ReflectiveConverter拿到這段結(jié)構(gòu)信息,則已經(jīng)知道class tag1->tag1這一段結(jié)構(gòu)已經(jīng)建立,那么在需要這段結(jié)構(gòu)時(shí)直接從傳入的信息取出對(duì)應(yīng)Tag即可,而避免創(chuàng)建新的相同nbt結(jié)構(gòu)。對(duì)于ReflectiveConverter,這段過(guò)程可以描述成一個(gè)更通用的代理形式:

  • 嘗試獲取上文nbt結(jié)構(gòu)信息,如果沒(méi)有,則創(chuàng)建一個(gè)新的nbt結(jié)構(gòu)信息并設(shè)置根節(jié)點(diǎn),否則用上文nbt結(jié)構(gòu)對(duì)應(yīng)的Tag作為當(dāng)前節(jié)點(diǎn)

  • 無(wú)論是處理類(lèi)LocateAt注解還是字段LocateAt注解,首先從nbt結(jié)構(gòu)信息獲取對(duì)應(yīng)的Tag,只有在已建立的nbt結(jié)構(gòu)沒(méi)有對(duì)應(yīng)節(jié)點(diǎn)時(shí)才創(chuàng)建新的Tag,并填入補(bǔ)充nbt結(jié)構(gòu)。

  • 創(chuàng)建子Tag時(shí),將nbt結(jié)構(gòu)信息傳給代理proxy,讓下層TagConverter也可以獲得已創(chuàng)建的nbt結(jié)構(gòu)信息

為此,我聲明了一個(gè)新的繼承自CreatTagIntent的intent:BuiltCompoundSubTree

并且ReflectiveConverter.createTag方法被改寫(xiě)為以下偽代碼邏輯:

以上偽代碼省略了大量的分支邏輯判斷,異常判斷,還有一些函數(shù)功能細(xì)節(jié),具體實(shí)現(xiàn)可以取github倉(cāng)庫(kù)查看。

看完以上你可能會(huì)發(fā)現(xiàn)這個(gè)過(guò)程完全只需要修改到ReflectiveConverter,那如果一個(gè)POJO字段要被轉(zhuǎn)換成listTag中的某一個(gè)子Tag呢?我在一開(kāi)始開(kāi)發(fā)這個(gè)功能時(shí)確實(shí)想到了ListTag,并且如上文也聲明了在路徑中表示ListTag索引的方式,但最終我還是放棄對(duì)重映射進(jìn)listTag元素功能,倒也不是開(kāi)發(fā)這個(gè)功能上有多少困難,主要是listTag本身特點(diǎn)導(dǎo)致使用邏輯會(huì)變得很復(fù)雜。比如將某個(gè)類(lèi)/字段轉(zhuǎn)成listTag特定索引下的一個(gè)nbt子結(jié)構(gòu),那就不得不考慮listTag前面的元素是否為空。二進(jìn)制數(shù)據(jù)的list tag要求元素不可為空,但如果轉(zhuǎn)換結(jié)果出現(xiàn)部分元素為空的ListTag對(duì)象,那勢(shì)必導(dǎo)致最終ListTag索引發(fā)生變動(dòng)(或者是直接拋出異常)。LocateAt注解重映射的本意是方便用戶(hù)序列化/反序列化對(duì)象,增加靈活性,但TagConverter在處理包含listTag索引的路徑時(shí)難以保證最終映射結(jié)果會(huì)到那個(gè)索引上。因?yàn)檫@種不可靠性,LocateAt注解分出了ToTagPath和FromTagPath兩個(gè)參數(shù),并且要求ToTagPath不包含任何nbt listTag格式的索引路徑。如果用戶(hù)真的有將字段映射到一個(gè)ListTag索引下的某個(gè)nbt子結(jié)構(gòu)這一需求,可以考慮自行實(shí)現(xiàn)一個(gè)TagConverter接口并注冊(cè)進(jìn)Mnbt對(duì)象中(就是文章最開(kāi)頭提到的Mnbt應(yīng)用類(lèi),其提供了一個(gè)注冊(cè)TagConverter函數(shù))


從零開(kāi)始的Minecraft - Nbt 序列化庫(kù)開(kāi)發(fā):添加功能:LocateAt注解的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
大城县| 驻马店市| 竹北市| 晋江市| 大方县| 广汉市| 清流县| 瓦房店市| 昌都县| 称多县| 安陆市| 安塞县| 榕江县| 惠水县| 阿巴嘎旗| 宕昌县| 渝北区| 屏东市| 阿拉尔市| 正宁县| 清丰县| 榆中县| 台中县| 布拖县| 陇西县| 乐平市| 兰州市| 渝中区| 永年县| 金溪县| 双流县| 衡阳县| 万宁市| 平山县| 湖州市| 商城县| 洮南市| 石家庄市| 中山市| 枞阳县| 南靖县|