Skip to content

使用指南 >=26.1

内置同步目标类型

AnvilLib Sync 框架内置了 3 种常见的同步目标。如果你的 @Sync 字段所属对象是以下之一,无需额外注册

目标类型标识查找方式维度分发
静态字段 (Class<?>)类全限定名 StringClass.forName(className)否(全局广播)
Entity 实例UUID根据 flow 在对应端的 Level 中查找是(仅追踪实体的玩家)
BlockEntity 实例BlockPos根据 flow 在对应端的 Level 中查找是(仅追踪该 chunk 的玩家)

这些内置类型的 SyncRegisterEntryAnvilLibSyncEntries 中已预注册。

基本用法

步骤 1:定义可同步类

java
@Sync(SyncDirection.BOTH)
public class PlayerData {
    public final SyncProxy<Integer> score = new SyncProxy<>(0);
    public final SyncProxy<String> displayName = new SyncProxy<>("");
    public final SyncProxy<Boolean> isActive = new SyncProxy<>(true);
}

@Sync 注解的方向由字节码注入自动应用到所有 SyncProxy 字段上,无需手动设置。

步骤 2:在 Entity / BlockEntity 中使用

java
public class MyBlockEntity extends BlockEntity {
    // 这些字段会自动同步 — MyBlockEntity 匹配内置的 BLOCK_ENTITY 策略
    public final SyncProxy<Integer> counter = new SyncProxy<>(0);
    public final SyncProxy<BlockPos> target = new SyncProxy<>(BlockPos.ZERO);
    public final SyncProxy<String> label = new SyncProxy<>("");

    // 仅服务端→客户端
    public final SyncProxy<Integer> remoteValue = new SyncProxy<>(0)
        .direction(SyncDirection.S2C);

    public MyBlockEntity(BlockPos pos, BlockState state) {
        super(MyBlockEntities.MY_TYPE, pos, state);
    }
}

因为 BlockEntity 是内置同步目标,框架已知道如何通过 BlockPos 定位它,无需额外注册。

步骤 3:读写字段

java
// 写入(自动触发同步)
blockEntity.counter.setValue(42);

// 读取
int current = blockEntity.counter.getValue();

自定义同步目标

如果你的 SyncProxy 字段所属的对象不是 ClassEntityBlockEntity(例如自定义的 Manager 类、Capability 对象等),需要通过 NeoForge 的注册机制向 SYNC_ENTRY_REGISTRY 注册一个自定义的 SyncRegisterEntry

注册流程

java
// 1. 创建 DeferredRegister
public static final DeferredRegister<SyncRegisterEntry<?, ?>> SYNC_ENTRIES =
    DeferredRegister.create(AnvilLibSyncRegistries.SYNC_ENTRY, "mymod");

// 2. 注册自定义 SyncRegisterEntry
//    以 "Team" 为例:每个 Team 有唯一的 String id,通过 id 在服务端查找
public static final DeferredHolder<SyncRegisterEntry<?, ?>, SyncRegisterEntry<Team, String>> TEAM_SYNC =
    SYNC_ENTRIES.register("team", () ->
        SyncRegisterEntry.create(
            Team.class,              // 承载 SyncProxy 的对象类型
            ByteBufCodecs.STRING_UTF8, // Team 标识的编解码器
            Team::getId,              // 从 Team 拿到 String id
            (ctx, id) -> {            // Finder: 收到包后如何找回 Team
                MinecraftServer server = ctx.player().getServer();
                if (server == null) return null;
                return server.getScoreboard().getPlayerTeam(id);
            }
        )
    );

// 3. 在模组构造中注册
public MyMod(IEventBus modBus) {
    SYNC_ENTRIES.register(modBus);
}

各参数含义

SyncRegisterEntry.create() 的每个参数都有明确意义:

参数含义Team 示例中的值
type承载字段的对象类型Team.class
idCodec标识如何在网络上编解码ByteBufCodecs.STRING_UTF8
idGetter如何从对象提取唯一标识Team::getId
finder收到同步包后,如何根据标识 + 上下文找到对象通过 Scoreboard 按 id 查找 Team

维度感知的自定义目标

如果对象与特定维度关联,可以启用维度分发来减少不必要的网络流量:

java
SYNC_ENTRIES.register("my_dimension_object", () ->
    SyncRegisterEntry.create(
        MyObject.class,
        MyObject.ID_CODEC,
        MyObject::getId,
        (ctx, id) -> MyObjectManager.get(id),  // Finder
        MyObject::getLevel                      // 维度提取器
    )
);

字节码注入(自动,无需用户操作)

当你在类上添加 @Sync 注解后,框架在类加载阶段自动完成以下工作(对用户完全透明):

  1. SyncTargetIndex 扫描所有模组的 @Sync 注解,建立目标类索引
  2. SyncClassProcessorProvider 对索引中的类触发注入
  3. SyncBytecodeInjector 使用 ASM 在构造函数/静态初始化器末尾注入:
    • proxy.setParent(this / Owner.class) — 让代理知道所属对象
    • proxy.setFieldName("fieldName") — 让代理知道字段名(网络同步时用于反射定位)
    • proxy.setDirection(SyncDirection.XXX) — 应用 @Sync 注解中定义的方向

用户无需做任何额外操作,只需声明字段为 public final SyncProxy<T> 并加 @Sync 注解即可。

完整示例:自定义同步

场景:同步游戏规则管理器中的值

java
// 1. 定义同步类(不是 Entity/BlockEntity/static)
@Sync(SyncDirection.S2C)  // 服务端→客户端单向同步
public class GameRuleData {
    public final SyncProxy<Integer> maxPlayers = new SyncProxy<>(10);
    public final SyncProxy<Boolean> pvpEnabled = new SyncProxy<>(true);
}

// 2. 管理器中持有同步数据
public class GameRuleManager {
    private static final Map<String, GameRuleData> rules = new HashMap<>();

    public GameRuleData get(String key) { return rules.get(key); }
}

// 3. 注册自定义 SyncRegisterEntry
public static final DeferredRegister<SyncRegisterEntry<?, ?>> ENTRIES =
    DeferredRegister.create(AnvilLibSyncRegistries.SYNC_ENTRY, "mymod");

public static final DeferredHolder<SyncRegisterEntry<?, ?>, SyncRegisterEntry<GameRuleData, String>>
    GAMERULE_SYNC = ENTRIES.register("game_rule", () ->
        SyncRegisterEntry.create(
            GameRuleData.class,
            ByteBufCodecs.STRING_UTF8,          // 用 String key 作为标识
            rule -> rule.name,                   // ID getter(假设 GameRuleData 有 name 字段)
            (ctx, id) -> GameRuleManager.get(id) // Finder: 按名称查找
        )
    );

注意事项

  • SyncProxy 字段必须为 public final
  • 字段所属对象必须是内置类型(static/Entity/BlockEntity)或已注册了 SyncRegisterEntry
  • 字节码注入对用户透明,不要手动调用 proxy.setParent() / setFieldName() / setDirection()
  • 自定义类型的 StreamCodec 不能为 null
  • SyncPayload 使用 Class.forName() 动态加载类,确保类在类路径中
  • finder 可能返回 null(对象不存在时),此时同步包被忽略

Released under the CC-BY-NC-SA 4.0 License.