---
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 (實體選擇器解析器)

**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的解析過程:

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

起始呼叫情形一共有四種:
:::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來取代任何的實體選擇器,