---
# System prepended metadata

title: selector
tags: [' MCEA', content, ' technology']

---

---
title: selector
tags: content, MCEA, technology
---

# 實體選擇器(Selector)

1.19
\net\minecraft\command\arguments\selector\EntitySelector.java
\net\minecraft\command\arguments\selector\EntitySelectorParser.java

Parameters
```java
int limit; // number  limit
boolean includeNonPlayers; // type!=player
boolean currentWorldOnly;  // current world only
Predicate<Entity> filter;  // predicates
MinMaxBounds.FloatBound distance; // a pair number of distance range
Function<Vector3d, Vector3d> positionGetter; // 
AxisAlignedBB aabb; // align axis
BiConsumer<Vector3d, List<? extends Entity>> sorter;
boolean self;       // @s
String username;    // name
UUID uuid;          // uuid
EntityType<?> type; // type
boolean checkPermission;
```
> 實體選擇器是一個效率非常慢的功能，在大多情況應當盡可能避免使用實體選擇器

### Entity Selector Parser (實體選擇器解析器)
![](https://i.imgur.com/hEgKBmT.png =400x)




**Entity Selector Parser(ESP)** -> **Entity Selector(ES)**
(此簡寫非正式縮寫，僅用於在本文中可以更方便的表示所用)


`StringReader reader` : 被分析的選擇器

:::spoiler parser()
```java=
public EntitySelector parse() throws CommandSyntaxException {
   this.startPosition = this.reader.getCursor();
   this.suggestions = this::suggestNameOrSelector;
   if (this.reader.canRead() && this.reader.peek() == '@') {
      if (!this.allowSelectors) {
         throw ERROR_SELECTORS_NOT_ALLOWED.createWithContext(this.reader);
      }

      this.reader.skip();
      EntitySelector forgeSelector = net.minecraftforge.common.command.EntitySelectorManager.parseSelector(this);
      if (forgeSelector != null)
         return forgeSelector;
      this.parseSelector();
   } else {
      this.parseNameOrUUID();
   }

   this.finalizePredicates();
   return this.getSelector();
}
```
:::
>
ESP的功能主要是構建ES，並將ES產生之List傳遞給指令使用

我們會用以下的集合代號來描述群體：

$E:$ 實體的集合
$P:$ 玩家的集合

對於五大選擇器，其分類是這樣子的：




| Selector     | Description & Complexity     |
| -------- | -------- |
| @s     | 掃描自己本身，時間複雜度為 $O(1)$     |
| @e     | 掃描所有實體，時間複雜度為 $O(\|E\|)$   |
| @a     | 掃描所有玩家，時間複雜度為 $O(\|P\|)$，且$P\subseteq E$     |
| @r     | 掃描所有玩家並隨機排序，時間複雜度為 $O(\|P\|)$，且$P\subseteq E$     |
| @p     | 掃描所有玩家並最近排序，時間複雜度為 $O(\|P\|\log\|P\|)$，且$P\subseteq E$     |

Name與UUID的解析過程：

![](https://i.imgur.com/TPW9XYE.png)

選擇器會優先判斷輸入的字串是否是以`@`為起頭，
若為否會檢查UUID的格式，若UUID格式未通過則會判定為User name
檢查完@字符後會再確認是否以`[`為接續，若為是則之後的文字皆為selector options的內容


---

### Entity Selector

![](https://i.imgur.com/y495NzD.png)




起始呼叫情形一共有四種：

:::spoiler **findSingleEntity:** 用於選擇單一實體的
```java=
public Entity findSingleEntity(CommandSourceStack p_121140_) throws CommandSyntaxException {
    this.checkPermissions(p_121140_);
    List<? extends Entity> list = this.findEntities(p_121140_);
    if (list.isEmpty()) {
        throw EntityArgument.NO_ENTITIES_FOUND.create();
    } else if (list.size() > 1) {
        throw EntityArgument.ERROR_NOT_SINGLE_ENTITY.create();
    } else {
        return list.get(0);
    }
}
```
:::

:::spoiler **findEntities:** 用於選擇數個實體
```java=
public List<? extends Entity> findEntities(CommandSourceStack p_121161_) throws CommandSyntaxException {
    this.checkPermissions(p_121161_);
    if (!this.includesEntities) {
        return this.findPlayers(p_121161_);
    } else if (this.playerName != null) {
        ServerPlayer serverplayer = p_121161_.getServer().getPlayerList().getPlayerByName(this.playerName);
        return (List<? extends Entity>)(serverplayer == null ? Collections.emptyList() : Lists.newArrayList(serverplayer));
    } else if (this.entityUUID != null) {
        for(ServerLevel serverlevel1 : p_121161_.getServer().getAllLevels()) {
        Entity entity = serverlevel1.getEntity(this.entityUUID);
        if (entity != null) {
            return Lists.newArrayList(entity);
        }
        }

        return Collections.emptyList();
    } else {
        Vec3 vec3 = this.position.apply(p_121161_.getPosition());
        Predicate<Entity> predicate = this.getPredicate(vec3);
        if (this.currentEntity) {
        return (List<? extends Entity>)(p_121161_.getEntity() != null && predicate.test(p_121161_.getEntity()) ? Lists.newArrayList(p_121161_.getEntity()) : Collections.emptyList());
        } else {
        List<Entity> list = Lists.newArrayList();
        if (this.isWorldLimited()) {
            this.addEntities(list, p_121161_.getLevel(), vec3, predicate);
        } else {
            for(ServerLevel serverlevel : p_121161_.getServer().getAllLevels()) {
                this.addEntities(list, serverlevel, vec3, predicate);
            }
        }

        return this.sortAndLimit(vec3, list);
        }
    }
}
```
:::
:::spoiler **findSinglePlayer:** 用於選擇單一玩家
```java=
public ServerPlayer findSinglePlayer(CommandSourceStack p_121164_) throws CommandSyntaxException {
    this.checkPermissions(p_121164_);
    List<ServerPlayer> list = this.findPlayers(p_121164_);
    if (list.size() != 1) {
        throw EntityArgument.NO_PLAYERS_FOUND.create();
    } else {
        return list.get(0);
    }
}
```
:::
  
:::spoiler **findPlayers:** 用於選擇數個玩家
```java=
public List<ServerPlayer> findPlayers(CommandSourceStack p_121167_) throws CommandSyntaxException {
    this.checkPermissions(p_121167_);
    if (this.playerName != null) {
        ServerPlayer serverplayer2 = p_121167_.getServer().getPlayerList().getPlayerByName(this.playerName);
        return (List<ServerPlayer>)(serverplayer2 == null ? Collections.emptyList() : Lists.newArrayList(serverplayer2));
    } else if (this.entityUUID != null) {
        ServerPlayer serverplayer1 = p_121167_.getServer().getPlayerList().getPlayer(this.entityUUID);
        return (List<ServerPlayer>)(serverplayer1 == null ? Collections.emptyList() : Lists.newArrayList(serverplayer1));
    } else {
        Vec3 vec3 = this.position.apply(p_121167_.getPosition());
        Predicate<Entity> predicate = this.getPredicate(vec3);
        if (this.currentEntity) {
        if (p_121167_.getEntity() instanceof ServerPlayer) {
            ServerPlayer serverplayer3 = (ServerPlayer)p_121167_.getEntity();
            if (predicate.test(serverplayer3)) {
                return Lists.newArrayList(serverplayer3);
            }
        }

        return Collections.emptyList();
        } else {
        List<ServerPlayer> list;
        if (this.isWorldLimited()) {
            list = p_121167_.getLevel().getPlayers(predicate);
        } else {
            list = Lists.newArrayList();

            for(ServerPlayer serverplayer : p_121167_.getServer().getPlayerList().getPlayers()) {
                if (predicate.test(serverplayer)) {
                    list.add(serverplayer);
                }
            }
        }

        return this.sortAndLimit(vec3, list);
        }
    }
}
```
:::
>
在經過上述結構圖之後，他們會再藉由這四種情形建立滿足條件的實體：

他們彼此間的傳遞關係即如上述結構圖所示，
若**select**抓到的實體皆是玩家時，會改呼叫**selectPlayers**，
對於**select**和**selectPlayers**都會先判斷輸入值是否為username或uuid，
但這邊也請特別留意，搜尋username的方式是使用窮舉玩家的方式來跑，
只有uuid的搜尋，也就是4種中只有getEntityByUuid的時間為$O(1)$，


綜合並上述結果，基本上可以大致排序出一個純選擇器的效能消耗順序為：
`UUID < @s < @a = @r < @p < @e`

> UUID和@s相比基本上是差不多的，不過@s會多處理很多傳遞實體的東西，
> 相比於只需要計算Hash值的UUID，@s很容易就會消耗很多額外的效能

---

### 選擇器的執行順序
###### (ref. SelectorParser.findPlayers)
```
如果是Name
    則回傳符合Name的資料

如果是UUID
    則回傳符合UUID的實體資料

如果選擇器是@s
     則回傳來源實體，如果來源實體符合選擇器條件(selector predicate)

否則
     記錄每一個符合選擇器條件的實體到List中
     將此List依照條件進行排序
     回傳此List取出指定數量(limit)的部分
```
實體會根據條件先被篩選出來並建立成一個新的List，再將其排序並篩選數量，
這也就導致兩個問題：
<font color=red>1. 只要有進行sort，就一定會拖慢selector的效能，因為O(nlogn)恆大於O(n)
2. limit並不會加快selector的效能，即使是limit=1</font>

---

### sort

功能用於指定實體排序的方式，sort選項一共有4種：

* arbitrary: 預設的排序方式 (依照出生時間先後做排序)
* nearest: 最近排序，第一個為最近實體、最後一個為最遠實體
* furtherest: 最遠排序，第一個為最遠實體、最後一個為最近實體
* random: 隨機排序，使用Collection.Shuffle，時間為 $O(N)$

> 比較距離的排序法：合併排序(Merge Sort)，時間為 $O(N\log N)$
> 比較距離時會進行開根號的動作

---

### 範例、取得相同分數的實體集合

#### ──────〔Version 1〕分數相等判別法 ──────

```
# copy id for matching : O(E)
execute as @e[scores={id=1..}] run scoreboard players operation @s match_id = @s id
execute as @e[score={match_id=1..}] run function namespace:match
```
```
# namespace:match.mcfunction
tag @s add matched

# match target : O(E^2)
execute as @e[tag=!matched] if score @s id = @e[tag=matched,limit=1] id run tag @s add matched

scoreboard players reset @e[tag=matched] match_id
tag @e remove matched
```

時間複雜度為 $O(E^3)$
這個寫法乍看上去比較簡約，但是在效能上並不是最優解
因為在match target的步驟中，會需要使用 $O(E^2)$ 的時間把相等分數的實體都給賦予標籤

#### ──────〔Version 2〕分數遞次篩選法 ──────

```
# space:match.mcfunction
scoreboard players set 0-0-0-0-1 id 0
# copy id for matching : O(E)
execute as @e[scores={id=1..}] run scoreboard players operation @s match_id = @s id
# assign next ID for matching : O(E)
scoreboard players operation 0-0-0-0-1 match_id > @e match_id
# loop : O(SE)
execute if score 0-0-0-0-1 match_id matches 1.. run function space:match_loop
```
```
# space:match_loop.mcfunction
# match targets : O(E)
execute as @e[scores={match_id=1..}] if score @s id = 0-0-0-0-1 id run function space:matched
# update next ID for matching : O(E)
scoreboard players operation 0-0-0-0-1 match_id > @e match_id
# loop yet if match-id not run out : O(1)
execute if score 0-0-0-0-1 match_id matches 1.. run function space:match_loop
```

```
# space:matched.mcfunction
scoreboard players reset @s match_id
tag @s add matched
```

可以從內層向外解析：




| function | Big-O | T | 
| -------- | -------- | --- |
| matched  | $O(1)$ |  |
| match_loop  | $O(E)$ | $T(E+E+1)$ |
| match  | $O(SE)$ | $T(1+E+E+ST_2)≈T(2E+2SE)$ |

S代表不同ID的數量，並且可以保證，因為ID數量不會比現存的實體總數還多，
即使ID比較多也只會根據現存的實體來篩選下一個要進行檢查的ID，
故時間複雜度為$O(E^2)$

---

## 總結

實體選擇器是一個非常糟糕的操作手段：
1. 排序(sort)和數量限制(limit)是在記錄完滿足條件的實體後才排序與取出
   (在效能上，這樣跟沒取數量是一樣的) 
2. 取得名稱(user name)的方式是採用遍歷窮舉比對，以名稱操作只是在增加 $P$ 的數量
3. 在非必要情況，應當減少sort的選擇器條件，
   因為這會使原本的複雜度從 $O(|E|)$ 變成 $O(|E|\log |E|)$
4. 增加更多的選擇器條件並不會因此使效能變得更好，仍舊會遍歷每個實體並逐一比對條件
5. 


在一般情況，我自己更推薦使用@s和UUID來取代任何的實體選擇器，