Skip to content

Commit

Permalink
Improved: Performance of the entity cache
Browse files Browse the repository at this point in the history
  • Loading branch information
Bram1903 committed Mar 1, 2025
1 parent ed48a46 commit e288524
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,25 @@
import lombok.Getter;
import lombok.NonNull;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

@Getter
public class EntityCache {
private final AHIPlayer player;
private final ConcurrentHashMap<Integer, CachedEntity> cache;
private final EntityTracker entityTracker;
private final VehicleTracker vehicleTracker;

private final ConcurrentHashMap<Integer, CachedEntity> cache;
private final ConcurrentHashMap<Integer, Integer> passengerIndex;

public EntityCache(AHIPlayer player) {
this.player = player;
this.cache = new ConcurrentHashMap<>();
this.entityTracker = new EntityTracker(player, this);
this.vehicleTracker = new VehicleTracker(player, this);

this.cache = new ConcurrentHashMap<>();
this.passengerIndex = new ConcurrentHashMap<>();
}

public void onPacketSend(PacketSendEvent event) {
Expand All @@ -55,44 +58,62 @@ public Optional<CachedEntity> getCachedEntity(int entityId) {
}

public Optional<RidableEntity> getVehicleData(int entityId) {
return getCachedEntity(entityId)
.filter(entityData -> entityData instanceof RidableEntity)
.map(entityData -> (RidableEntity) entityData);
CachedEntity entity = cache.get(entityId);
if (entity instanceof RidableEntity) {
return Optional.of((RidableEntity) entity);
}
return Optional.empty();
}

public void addLivingEntity(int entityId, @NonNull CachedEntity cachedEntity) {
cache.put(entityId, cachedEntity);
if (cachedEntity instanceof RidableEntity) {
RidableEntity ridable = (RidableEntity) cachedEntity;
// Populate the secondary index based on its current passenger ID.
passengerIndex.put(ridable.getPassengerId(), entityId);
}
}

public void removeEntity(int entityId) {
cache.remove(entityId);
CachedEntity removed = cache.remove(entityId);
if (removed instanceof RidableEntity) {
RidableEntity ridable = (RidableEntity) removed;
// Remove from secondary index.
passengerIndex.remove(ridable.getPassengerId());
}
}

public void resetUserCache() {
cache.clear();
passengerIndex.clear();
}

public void updateVehiclePassenger(int entityId, int passengerId) {
getVehicleData(entityId).ifPresent(ridableEntityData -> ridableEntityData.setPassengerId(passengerId));
public void updateVehiclePassenger(int entityId, int newPassengerId) {
getVehicleData(entityId).ifPresent(ridableEntity -> {
int oldPassengerId = ridableEntity.getPassengerId();
if (oldPassengerId != newPassengerId) {
passengerIndex.remove(oldPassengerId);
ridableEntity.setPassengerId(newPassengerId);
passengerIndex.put(newPassengerId, entityId);
}
});
}

public float getVehicleHealth(int entityId) {
return getVehicleData(entityId).map(RidableEntity::getHealth).orElse(0.5f);
}

public boolean isUserPassenger(int entityId) {
return getVehicleData(entityId).map(ridableEntityData -> ridableEntityData.getPassengerId() == player.user.getEntityId()).orElse(false);
return getVehicleData(entityId)
.map(ridableEntity -> ridableEntity.getPassengerId() == player.user.getEntityId())
.orElse(false);
}

public int getPassengerId(int entityId) {
return getVehicleData(entityId).map(RidableEntity::getPassengerId).orElse(0);
}

public int getEntityIdByPassengerId(int passengerId) {
return cache.entrySet().stream()
.filter(entry -> entry.getValue() instanceof RidableEntity && ((RidableEntity) entry.getValue()).getPassengerId() == passengerId)
.map(Map.Entry::getKey)
.findFirst()
.orElse(0);
return passengerIndex.getOrDefault(passengerId, 0);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@
import java.util.Collections;
import java.util.List;

/**
* Listens for VehicleState events and manages the caching of various entity state details.
*/
public class VehicleTracker {
private final AHIPlayer player;
private final EntityCache entityCache;
Expand All @@ -52,71 +49,113 @@ public VehicleTracker(AHIPlayer player, EntityCache entityCache) {
}

public void onPacketSend(PacketSendEvent event) {
final Settings settings = configManager.getSettings();
if (!settings.getEntityData().isEnabled()) return;
if (settings.getEntityData().isPlayersOnly()) return;

final PacketTypeCommon type = event.getPacketType();
Settings settings = configManager.getSettings();
if (!settings.getEntityData().isEnabled() || settings.getEntityData().isPlayersOnly()) {
return;
}

PacketTypeCommon type = event.getPacketType();
if (PacketType.Play.Server.SET_PASSENGERS == type) {
handlePassengers(new WrapperPlayServerSetPassengers(event));
} else if (PacketType.Play.Server.ATTACH_ENTITY == type) {
handleAttachEntity(new WrapperPlayServerAttachEntity(event));
}
}

/**
* Processes SET_PASSENGERS packets.
*/
private void handlePassengers(WrapperPlayServerSetPassengers packet) {
int entityId = packet.getEntityId();
if (entityId == player.user.getEntityId() || !isValidVehicle(entityId)) return;
int vehicleId = packet.getEntityId();
if (!shouldProcess(vehicleId)) {
return;
}

int[] passengers = packet.getPassengers();
if (passengers.length > 0) {
updatePassengerState(entityId, passengers[0], true);
// If there are passengers, update with the first one.
updatePassengerState(vehicleId, passengers[0], true);
} else {
int passengerId = entityCache.getPassengerId(entityId);
updatePassengerState(entityId, passengerId, false);
// If no passengers are present, consider the vehicle empty.
int currentPassenger = entityCache.getPassengerId(vehicleId);
updatePassengerState(vehicleId, currentPassenger, false);
}
}

/**
* Processes ATTACH_ENTITY packets.
*/
private void handleAttachEntity(WrapperPlayServerAttachEntity packet) {
int entityId = packet.getHoldingId();
if (entityId == player.user.getEntityId() || !isValidVehicle(entityId)) return;
int vehicleId = packet.getHoldingId();
if (!shouldProcess(vehicleId)) {
return;
}

int passengerId = packet.getAttachedId();
if (entityId > 0) {
updatePassengerState(entityId, passengerId, true);
if (vehicleId > 0) {
updatePassengerState(vehicleId, passengerId, true);
} else {
int reversedEntityId = entityCache.getEntityIdByPassengerId(passengerId);
updatePassengerState(reversedEntityId, passengerId, false);
int cachedVehicleId = entityCache.getEntityIdByPassengerId(passengerId);
updatePassengerState(cachedVehicleId, passengerId, false);
}
}

/**
* Updates the passenger state in the cache and, when applicable, sends a vehicle health update.
*
* @param vehicleId the id of the vehicle entity.
* @param passengerId the id of the passenger.
* @param entering true if the passenger is entering, false if leaving.
*/
private void updatePassengerState(int vehicleId, int passengerId, boolean entering) {
// Update the cached passenger state (use -1 for leaving).
entityCache.updateVehiclePassenger(vehicleId, entering ? passengerId : -1);
// If a passenger is entering or the player's entity is involved, send a health update.
if (entering || player.user.getEntityId() == passengerId) {
float healthValue = entering ? entityCache.getVehicleHealth(vehicleId) : 0.5F;
sendVehicleHealthUpdate(vehicleId, healthValue);
}
}

/**
* Determines whether a given entity id should be processed.
*
* @param entityId the entity id to check.
* @return true if it is not the player and represents a valid rideable vehicle.
*/
private boolean shouldProcess(int entityId) {
return entityId != player.user.getEntityId() && isValidVehicle(entityId);
}

/**
* Checks if the entity is a valid rideable vehicle.
*
* @param entityId the entity id.
* @return true if the entity is present in the cache and is rideable.
*/
private boolean isValidVehicle(int entityId) {
return entityCache.getCachedEntity(entityId)
.map(CachedEntity::getEntityType)
.map(RidableEntities::isRideable)
.orElse(false);
}

private void sendVehicleHealthUpdate(int vehicleId, float healthValue) {
AHIPlatform.getInstance().getScheduler().runAsyncTask((o) -> {
/**
* Sends an asynchronous vehicle health update to the client.
*
* @param vehicleId the vehicle entity id.
* @param healthValue the health value.
*/
private void sendVehicleHealthUpdate(final int vehicleId, final float healthValue) {
AHIPlatform.getInstance().getScheduler().runAsyncTask(task -> {
List<EntityData> metadata = Collections.singletonList(
new EntityData(
player.metadataIndex.HEALTH,
EntityDataTypes.FLOAT,
healthValue
)
);

player.user.sendPacketSilently(new WrapperPlayServerEntityMetadata(vehicleId, metadata));
});
}
}
}

0 comments on commit e288524

Please sign in to comment.