public EntityRef newInstance(BlockFamily blockFamily, EntityRef blockEntity) {
    if (blockFamily == null) {
      return EntityRef.NULL;
    }

    EntityBuilder builder = entityManager.newBuilder("engine:blockItemBase");
    if (blockFamily.getArchetypeBlock().getLuminance() > 0) {
      builder.addComponent(new LightComponent());
    }

    // Copy the components from block prefab into the block item
    for (Component component : blockEntity.iterateComponents()) {
      if (component.getClass().getAnnotation(AddToBlockBasedItem.class) != null) {
        builder.addComponent(entityManager.getComponentLibrary().copy(component));
      }
    }

    ItemComponent item = builder.getComponent(ItemComponent.class);
    if (blockFamily.getArchetypeBlock().isStackable()) {
      item.stackId = "block:" + blockFamily.getURI().toString();
      item.stackCount = (byte) 1;
    }

    BlockItemComponent blockItem = builder.getComponent(BlockItemComponent.class);
    blockItem.blockFamily = blockFamily;

    return builder.build();
  }
  @Override
  public EntityRef deserializeEntity(EntityData.Entity entityData) {
    EntityRef entity = entityManager.createEntityRefWithId(entityData.getId());
    if (entityData.hasParentPrefab()
        && !entityData.getParentPrefab().isEmpty()
        && prefabManager.exists(entityData.getParentPrefab())) {
      Prefab prefab = prefabManager.getPrefab(entityData.getParentPrefab());
      for (Component component : prefab.listComponents()) {
        String componentName = ComponentUtil.getComponentClassName(component.getClass());
        if (!containsIgnoreCase(componentName, entityData.getRemovedComponentList())) {
          entity.addComponent(componentLibrary.copy(component));
        }
      }
      entity.addComponent(new EntityInfoComponent(entityData.getParentPrefab()));
    }
    for (EntityData.Component componentData : entityData.getComponentList()) {
      Class<? extends Component> componentClass = getComponentClass(componentData);
      if (componentClass == null) continue;

      if (!entity.hasComponent(componentClass)) {
        entity.addComponent(deserializeComponent(componentData));
      } else {
        deserializeComponentOnto(entity.getComponent(componentClass), componentData);
      }
    }
    return entity;
  }
  /**
   * Use this method instead of {@link #newInstance(BlockFamily)} to modify entity properties like
   * the persistence flag before it gets created.
   *
   * @param blockFamily must not be null
   */
  public EntityBuilder newBuilder(BlockFamily blockFamily, int quantity) {
    EntityBuilder builder = entityManager.newBuilder("engine:blockItemBase");
    if (blockFamily.getArchetypeBlock().getLuminance() > 0) {
      builder.addComponent(new LightComponent());
    }

    // Copy the components from block prefab into the block item
    Prefab prefab = Assets.getPrefab(blockFamily.getArchetypeBlock().getPrefab());
    if (prefab != null) {
      for (Component component : prefab.iterateComponents()) {
        if (component.getClass().getAnnotation(AddToBlockBasedItem.class) != null) {
          builder.addComponent(entityManager.getComponentLibrary().copy(component));
        }
      }
    }

    DisplayNameComponent displayNameComponent = builder.getComponent(DisplayNameComponent.class);
    displayNameComponent.name = blockFamily.getDisplayName();

    ItemComponent item = builder.getComponent(ItemComponent.class);
    if (blockFamily.getArchetypeBlock().isStackable()) {
      item.stackId = "block:" + blockFamily.getURI().toString();
      item.stackCount = (byte) quantity;
    }

    BlockItemComponent blockItem = builder.getComponent(BlockItemComponent.class);
    blockItem.blockFamily = blockFamily;

    return builder;
  }
  private void serializeComponentFull(
      Component component,
      boolean ignoreIfNoFields,
      FieldSerializeCheck<Component> fieldCheck,
      EntityData.PackedEntity.Builder entityData,
      ByteString.Output entityFieldIds,
      ByteString.Output componentFieldCounts,
      boolean componentInitial) {
    ComponentMetadata<?> componentMetadata = componentLibrary.getMetadata(component.getClass());
    if (componentMetadata == null) {
      logger.error("Unregistered component type: {}", component.getClass());
      return;
    }

    Serializer serializer = typeSerializationLibrary.getSerializerFor(componentMetadata);
    byte fieldCount = 0;
    for (ReplicatedFieldMetadata field : componentMetadata.getFields()) {
      if (fieldCheck.shouldSerializeField(field, component, componentInitial)) {
        PersistedData fieldValue = serializer.serialize(field, component, serializationContext);
        if (!fieldValue.isNull()) {
          entityFieldIds.write(field.getId());

          entityData.addFieldValue(((ProtobufPersistedData) fieldValue).getValue());
          fieldCount++;
        }
      }
    }

    if (fieldCount != 0 || !ignoreIfNoFields) {
      entityData.addComponentId(idTable.get(component.getClass()));
      componentFieldCounts.write(fieldCount);
    }
  }
  private EntityData.Entity serializeEntityFull(EntityRef entityRef) {
    EntityData.Entity.Builder entity = EntityData.Entity.newBuilder();
    entity.setId(entityRef.getId());
    for (Component component : entityRef.iterateComponents()) {
      if (component.getClass().equals(EntityInfoComponent.class)) continue;

      EntityData.Component componentData = serializeComponent(component);
      if (componentData != null) {
        entity.addComponent(componentData);
      }
    }
    return entity.build();
  }
  private void cleanUpTemporaryEntity(EntityRef entity) {
    Prefab prefab = entity.getParentPrefab();

    for (Component comp : entity.iterateComponents()) {
      if (!COMMON_BLOCK_COMPONENTS.contains(comp.getClass())
          && (prefab == null || !prefab.hasComponent(comp.getClass()))) {
        entity.removeComponent(comp.getClass());
      }
    }
    entity.removeComponent(NetworkComponent.class);

    if (prefab != null) {
      for (Component comp : prefab.iterateComponents()) {
        Component currentComp = entity.getComponent(comp.getClass());
        if (currentComp == null) {
          entity.addComponent(entityManager.getComponentLibrary().copy(comp));
        } else {
          ComponentMetadata<?> metadata =
              entityManager.getComponentLibrary().getMetadata(comp.getClass());
          boolean changed = false;
          for (FieldMetadata field : metadata.getFields()) {
            Object expected = field.getValue(comp);
            if (!Objects.equal(expected, field.getValue(currentComp))) {
              field.setValue(currentComp, expected);
              changed = true;
            }
          }
          if (changed) {
            entity.saveComponent(currentComp);
          }
        }
      }
    }
    entityManager.destroyEntityWithoutEvents(entity);
  }
  private EntityData.PackedEntity.Builder serializeEntityFull(
      EntityRef entityRef, FieldSerializeCheck<Component> fieldCheck) {
    EntityData.PackedEntity.Builder entity = EntityData.PackedEntity.newBuilder();
    ByteString.Output fieldIds = ByteString.newOutput();
    ByteString.Output componentFieldCounts = ByteString.newOutput();
    for (Component component : entityRef.iterateComponents()) {
      if (!componentSerializeCheck.serialize(componentLibrary.getMetadata(component.getClass()))) {
        continue;
      }

      serializeComponentFull(
          component, false, fieldCheck, entity, fieldIds, componentFieldCounts, true);
    }
    entity.setFieldIds(fieldIds.toByteString());
    entity.setComponentFieldCounts(componentFieldCounts.toByteString());

    return entity;
  }
  private void serializeComponentDelta(
      Component oldComponent,
      Component newComponent,
      FieldSerializeCheck<Component> fieldCheck,
      EntityData.PackedEntity.Builder entityData,
      ByteString.Output entityFieldIds,
      ByteString.Output componentFieldCounts,
      boolean componentInitial) {
    ComponentMetadata<?> componentMetadata = componentLibrary.getMetadata(oldComponent.getClass());
    if (componentMetadata == null) {
      logger.error("Unregistered component type: {}", oldComponent.getClass());
      return;
    }

    byte fieldCount = 0;
    Serializer serializer = typeSerializationLibrary.getSerializerFor(componentMetadata);
    for (ReplicatedFieldMetadata field : componentMetadata.getFields()) {
      if (fieldCheck.shouldSerializeField(field, newComponent, componentInitial)) {
        Object oldValue = field.getValue(oldComponent);
        Object newValue = field.getValue(newComponent);
        if (!Objects.equal(oldValue, newValue)) {
          PersistedData data = serializer.serializeValue(field, newValue, serializationContext);
          if (!data.isNull()) {
            entityFieldIds.write(field.getId());
            entityData.addFieldValue(((ProtobufPersistedData) data).getValue());
            fieldCount++;
          } else {
            logger.error(
                "Exception serializing component type: {}, field: {} - returned null",
                componentMetadata,
                field);
          }
        }
      }
    }

    if (fieldCount > 0) {
      entityData.addComponentId(idTable.get(newComponent.getClass()));
      componentFieldCounts.write(fieldCount);
    }
  }
  /**
   * Transforms a block entity with the change of block type. This is driven from the delta between
   * the old and new block type prefabs, but takes into account changes made to the block entity.
   *
   * @param blockEntity The entity to update
   * @param oldType The previous type of the block
   * @param type The new type of the block
   */
  private void updateBlockEntityComponents(
      EntityRef blockEntity,
      Block oldType,
      Block type,
      Set<Class<? extends Component>> retainComponents) {
    BlockComponent blockComponent = blockEntity.getComponent(BlockComponent.class);

    Optional<Prefab> oldPrefab = oldType.getPrefab();
    EntityBuilder oldEntityBuilder = entityManager.newBuilder(oldPrefab.orElse(null));
    oldEntityBuilder.addComponent(
        new BlockComponent(oldType, new Vector3i(blockComponent.getPosition())));
    BeforeEntityCreated oldEntityEvent =
        new BeforeEntityCreated(oldPrefab.orElse(null), oldEntityBuilder.iterateComponents());
    blockEntity.send(oldEntityEvent);
    for (Component comp : oldEntityEvent.getResultComponents()) {
      oldEntityBuilder.addComponent(comp);
    }

    Optional<Prefab> newPrefab = type.getPrefab();
    EntityBuilder newEntityBuilder = entityManager.newBuilder(newPrefab.orElse(null));
    newEntityBuilder.addComponent(
        new BlockComponent(type, new Vector3i(blockComponent.getPosition())));
    BeforeEntityCreated newEntityEvent =
        new BeforeEntityCreated(newPrefab.orElse(null), newEntityBuilder.iterateComponents());
    blockEntity.send(newEntityEvent);
    for (Component comp : newEntityEvent.getResultComponents()) {
      newEntityBuilder.addComponent(comp);
    }

    for (Component component : blockEntity.iterateComponents()) {
      if (!COMMON_BLOCK_COMPONENTS.contains(component.getClass())
          && !entityManager
              .getComponentLibrary()
              .getMetadata(component.getClass())
              .isRetainUnalteredOnBlockChange()
          && !newEntityBuilder.hasComponent(component.getClass())
          && !retainComponents.contains(component.getClass())) {
        blockEntity.removeComponent(component.getClass());
      }
    }

    blockComponent.setBlock(type);
    blockEntity.saveComponent(blockComponent);

    HealthComponent health = blockEntity.getComponent(HealthComponent.class);
    if (health == null && type.isDestructible()) {
      blockEntity.addComponent(
          new HealthComponent(type.getHardness(), type.getHardness() / BLOCK_REGEN_SECONDS, 1.0f));
    } else if (health != null && !type.isDestructible()) {
      blockEntity.removeComponent(HealthComponent.class);
    } else if (health != null && type.isDestructible()) {
      health.maxHealth = type.getHardness();
      health.currentHealth = Math.min(health.currentHealth, health.maxHealth);
      blockEntity.saveComponent(health);
    }

    for (Component comp : newEntityBuilder.iterateComponents()) {
      copyIntoPrefab(blockEntity, comp, retainComponents);
    }
  }
  private EntityData.Entity serializeEntityDelta(EntityRef entityRef, Prefab prefab) {
    EntityData.Entity.Builder entity = EntityData.Entity.newBuilder();
    entity.setId(entityRef.getId());
    entity.setParentPrefab(prefab.getName());
    for (Component component : entityRef.iterateComponents()) {
      if (component.getClass().equals(EntityInfoComponent.class)) continue;

      Component prefabComponent = prefab.getComponent(component.getClass());
      EntityData.Component componentData;
      if (prefabComponent == null) {
        componentData = serializeComponent(component);
      } else {
        componentData = serializeComponent(prefabComponent, component);
      }

      if (componentData != null) {
        entity.addComponent(componentData);
      }
    }
    for (Component prefabComponent : prefab.listComponents()) {
      if (!entityRef.hasComponent(prefabComponent.getClass())) {
        entity.addRemovedComponent(ComponentUtil.getComponentClassName(prefabComponent.getClass()));
      }
    }
    return entity.build();
  }
  private EntityData.Component serializeComponent(Component base, Component delta) {
    ComponentMetadata<?> componentMetadata = componentLibrary.getMetadata(base.getClass());
    if (componentMetadata == null) {
      logger.log(Level.SEVERE, "Unregistered component type: " + base.getClass());
      return null;
    }

    EntityData.Component.Builder componentMessage = EntityData.Component.newBuilder();
    if (useLookupTables) {
      componentMessage.setTypeIndex(componentIdTable.inverse().get(base.getClass()));
    } else {
      componentMessage.setType(ComponentUtil.getComponentClassName(delta));
    }

    boolean changed = false;
    for (FieldMetadata field : componentMetadata.iterateFields()) {
      try {
        Object origValue = field.getValue(base);
        Object deltaValue = field.getValue(delta);

        if (!Objects.equal(origValue, deltaValue)) {
          EntityData.Value value = field.serialize(deltaValue);
          componentMessage.addField(
              EntityData.NameValue.newBuilder().setName(field.getName()).setValue(value).build());
          changed = true;
        }
      } catch (IllegalAccessException e) {
        logger.log(
            Level.SEVERE, "Exception during serializing component type: " + base.getClass(), e);
      } catch (InvocationTargetException e) {
        logger.log(
            Level.SEVERE, "Exception during serializing component type: " + base.getClass(), e);
      }
    }
    if (changed) {
      return componentMessage.build();
    }
    return null;
  }
  @Override
  public EntityData.Component serializeComponent(Component component) {
    ComponentMetadata<?> componentMetadata = componentLibrary.getMetadata(component.getClass());
    if (componentMetadata == null) {
      logger.log(Level.SEVERE, "Unregistered component type: " + component.getClass());
      return null;
    }
    EntityData.Component.Builder componentMessage = EntityData.Component.newBuilder();
    if (useLookupTables) {
      componentMessage.setTypeIndex(componentIdTable.inverse().get(component.getClass()));
    } else {
      componentMessage.setType(ComponentUtil.getComponentClassName(component));
    }

    for (FieldMetadata field : componentMetadata.iterateFields()) {
      try {
        Object rawValue = field.getValue(component);
        if (rawValue == null) continue;

        EntityData.Value value = field.serialize(rawValue);
        if (value == null) continue;

        componentMessage.addField(
            EntityData.NameValue.newBuilder().setName(field.getName()).setValue(value).build());
      } catch (IllegalAccessException e) {
        logger.log(
            Level.SEVERE,
            "Exception during serializing component type: " + component.getClass(),
            e);
      } catch (InvocationTargetException e) {
        logger.log(
            Level.SEVERE,
            "Exception during serializing component type: " + component.getClass(),
            e);
      }
    }

    return componentMessage.build();
  }
  private Component deserializeOnto(
      Component component,
      EntityData.Component componentData,
      ComponentMetadata componentMetadata) {
    try {
      for (EntityData.NameValue field : componentData.getFieldList()) {
        FieldMetadata fieldInfo = componentMetadata.getField(field.getName());
        if (fieldInfo == null) continue;

        Object value = fieldInfo.deserialize(field.getValue());
        if (value == null) continue;
        fieldInfo.setValue(component, value);
      }
      return component;
    } catch (InvocationTargetException e) {
      logger.log(
          Level.SEVERE, "Exception during serializing component type: " + component.getClass(), e);
    } catch (IllegalAccessException e) {
      logger.log(
          Level.SEVERE, "Exception during serializing component type: " + component.getClass(), e);
    }
    return null;
  }
  private EntityData.PackedEntity.Builder serializeEntityDelta(
      EntityRef entityRef, Prefab prefab, FieldSerializeCheck<Component> fieldCheck) {
    EntityData.PackedEntity.Builder entity = EntityData.PackedEntity.newBuilder();
    entity.setParentPrefabUri(prefab.getName());
    Set<Class<? extends Component>> presentClasses = Sets.newHashSet();

    ByteString.Output fieldIds = ByteString.newOutput();
    ByteString.Output componentFieldCounts = ByteString.newOutput();
    for (Component component : entityRef.iterateComponents()) {
      if (!componentSerializeCheck.serialize(componentLibrary.getMetadata(component.getClass()))) {
        continue;
      }

      presentClasses.add(component.getClass());

      Component prefabComponent = prefab.getComponent(component.getClass());

      if (prefabComponent == null) {
        serializeComponentFull(
            component, false, fieldCheck, entity, fieldIds, componentFieldCounts, true);
      } else {
        serializeComponentDelta(
            prefabComponent, component, fieldCheck, entity, fieldIds, componentFieldCounts, true);
      }
    }
    entity.setFieldIds(fieldIds.toByteString());
    entity.setComponentFieldCounts(componentFieldCounts.toByteString());

    for (Component prefabComponent : prefab.iterateComponents()) {
      if (!presentClasses.contains(prefabComponent.getClass())
          && componentSerializeCheck.serialize(
              componentLibrary.getMetadata(prefabComponent.getClass()))) {
        entity.addRemovedComponent(idTable.get(prefabComponent.getClass()));
      }
    }
    return entity;
  }