/* * Copyright 2014 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.logic.delay; import com.google.common.collect.Ordering; import com.google.common.collect.SortedSetMultimap; import com.google.common.collect.TreeMultimap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.engine.Time; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.entitySystem.entity.lifecycleEvents.BeforeDeactivateComponent; import org.terasology.entitySystem.entity.lifecycleEvents.OnActivatedComponent; import org.terasology.entitySystem.event.ReceiveEvent; import org.terasology.entitySystem.systems.BaseComponentSystem; import org.terasology.entitySystem.systems.RegisterMode; import org.terasology.entitySystem.systems.RegisterSystem; import org.terasology.entitySystem.systems.UpdateSubscriberSystem; import org.terasology.registry.In; import org.terasology.registry.Share; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; /** */ @RegisterSystem(RegisterMode.AUTHORITY) @Share(value = DelayManager.class) public class DelayedActionSystem extends BaseComponentSystem implements UpdateSubscriberSystem, DelayManager { private static final Logger logger = LoggerFactory.getLogger(DelayedActionSystem.class); @In private Time time; private SortedSetMultimap<Long, EntityRef> delayedOperationsSortedByTime = TreeMultimap.create(Ordering.natural(), Ordering.arbitrary()); private SortedSetMultimap<Long, EntityRef> periodicOperationsSortedByTime = TreeMultimap.create(Ordering.natural(), Ordering.arbitrary()); // ONLY use this for testing. DO NOT use this during regular usage. void setTime(Time t) { time = t; } @Override public void update(float delta) { final long currentWorldTime = time.getGameTimeInMs(); invokeDelayedOperations(currentWorldTime); invokePeriodicOperations(currentWorldTime); } private void invokeDelayedOperations(long currentWorldTime) { List<EntityRef> operationsToInvoke = new LinkedList<>(); Iterator<Long> scheduledOperationsIterator = delayedOperationsSortedByTime.keySet().iterator(); long processedTime; while (scheduledOperationsIterator.hasNext()) { processedTime = scheduledOperationsIterator.next(); if (processedTime > currentWorldTime) { break; } operationsToInvoke.addAll(delayedOperationsSortedByTime.get(processedTime)); scheduledOperationsIterator.remove(); } operationsToInvoke.stream().filter(EntityRef::exists).forEach(delayedEntity -> { final DelayedActionComponent delayedActions = delayedEntity.getComponent(DelayedActionComponent.class); // If there is a DelayedActionComponent, proceed. Else report an error to the log. if (delayedActions != null) { final Set<String> actionIds = delayedActions.removeActionsUpTo(currentWorldTime); saveOrRemoveComponent(delayedEntity, delayedActions); if (!delayedActions.isEmpty()) { delayedOperationsSortedByTime.put(delayedActions.getLowestWakeUp(), delayedEntity); } for (String actionId : actionIds) { delayedEntity.send(new DelayedActionTriggeredEvent(actionId)); } } else { logger.error("ERROR: This entity is missing a DelayedActionComponent. " + "So skipping delayed actions for this entity", delayedEntity); } }); } private void invokePeriodicOperations(long currentWorldTime) { List<EntityRef> operationsToInvoke = new LinkedList<>(); Iterator<Long> scheduledOperationsIterator = periodicOperationsSortedByTime.keySet().iterator(); long processedTime; while (scheduledOperationsIterator.hasNext()) { processedTime = scheduledOperationsIterator.next(); if (processedTime > currentWorldTime) { break; } operationsToInvoke.addAll(periodicOperationsSortedByTime.get(processedTime)); scheduledOperationsIterator.remove(); } operationsToInvoke.stream().filter(EntityRef::exists).forEach(periodicEntity -> { final PeriodicActionComponent periodicActionComponent = periodicEntity.getComponent(PeriodicActionComponent.class); // If there is a PeriodicActionComponent, proceed. Else report an error to the log. if (periodicActionComponent != null) { final Set<String> actionIds = periodicActionComponent.getTriggeredActionsAndReschedule(currentWorldTime); saveOrRemoveComponent(periodicEntity, periodicActionComponent); if (!periodicActionComponent.isEmpty()) { periodicOperationsSortedByTime.put(periodicActionComponent.getLowestWakeUp(), periodicEntity); } for (String actionId : actionIds) { periodicEntity.send(new PeriodicActionTriggeredEvent(actionId)); } } else { logger.error("ERROR: This entity is missing a DelayedActionComponent. " + "So skipping delayed actions for this entity", periodicEntity); } }); } @ReceiveEvent public void delayedComponentActivated(OnActivatedComponent event, EntityRef entity, DelayedActionComponent delayedActionComponent) { delayedOperationsSortedByTime.put(delayedActionComponent.getLowestWakeUp(), entity); } @ReceiveEvent public void periodicComponentActivated(OnActivatedComponent event, EntityRef entity, PeriodicActionComponent periodicActionComponent) { periodicOperationsSortedByTime.put(periodicActionComponent.getLowestWakeUp(), entity); } @ReceiveEvent public void delayedComponentDeactivated(BeforeDeactivateComponent event, EntityRef entity, DelayedActionComponent delayedActionComponent) { delayedOperationsSortedByTime.remove(delayedActionComponent.getLowestWakeUp(), entity); } @ReceiveEvent public void periodicComponentDeactivated(BeforeDeactivateComponent event, EntityRef entity, PeriodicActionComponent periodicActionComponent) { delayedOperationsSortedByTime.remove(periodicActionComponent.getLowestWakeUp(), entity); } @Override public void addDelayedAction(EntityRef entity, String actionId, long delay) { long scheduleTime = time.getGameTimeInMs() + delay; DelayedActionComponent delayedActionComponent = entity.getComponent(DelayedActionComponent.class); if (delayedActionComponent != null) { final long oldWakeUp = delayedActionComponent.getLowestWakeUp(); delayedActionComponent.addActionId(actionId, scheduleTime); entity.saveComponent(delayedActionComponent); final long newWakeUp = delayedActionComponent.getLowestWakeUp(); if (oldWakeUp < newWakeUp) { delayedOperationsSortedByTime.remove(oldWakeUp, entity); delayedOperationsSortedByTime.put(newWakeUp, entity); } else { // Even if the oldWakeUp time is greater than or equal to the new one, the next action should still be added // to the delayedOperationsSortedByTime mapping. delayedOperationsSortedByTime.put(scheduleTime, entity); } } else { delayedActionComponent = new DelayedActionComponent(); delayedActionComponent.addActionId(actionId, scheduleTime); entity.addComponent(delayedActionComponent); } } @Override public void addPeriodicAction(EntityRef entity, String actionId, long initialDelay, long period) { long scheduleTime = time.getGameTimeInMs() + initialDelay; PeriodicActionComponent periodicActionComponent = entity.getComponent(PeriodicActionComponent.class); if (periodicActionComponent != null) { final long oldWakeUp = periodicActionComponent.getLowestWakeUp(); periodicActionComponent.addScheduledActionId(actionId, scheduleTime, period); entity.saveComponent(periodicActionComponent); final long newWakeUp = periodicActionComponent.getLowestWakeUp(); if (oldWakeUp < newWakeUp) { periodicOperationsSortedByTime.remove(oldWakeUp, entity); periodicOperationsSortedByTime.put(newWakeUp, entity); } else { // Even if the oldWakeUp time is greater than or equal to the new one, the next action should still be added // to the delayedOperationsSortedByTime mapping. periodicOperationsSortedByTime.put(scheduleTime, entity); } } else { periodicActionComponent = new PeriodicActionComponent(); periodicActionComponent.addScheduledActionId(actionId, scheduleTime, period); entity.addComponent(periodicActionComponent); } } @Override public void cancelDelayedAction(EntityRef entity, String actionId) { DelayedActionComponent delayedComponent = entity.getComponent(DelayedActionComponent.class); long oldWakeUp = delayedComponent.getLowestWakeUp(); delayedComponent.removeActionId(actionId); long newWakeUp = delayedComponent.getLowestWakeUp(); if (!delayedComponent.isEmpty() && oldWakeUp < newWakeUp) { delayedOperationsSortedByTime.remove(oldWakeUp, entity); delayedOperationsSortedByTime.put(newWakeUp, entity); } else if (delayedComponent.isEmpty()) { delayedOperationsSortedByTime.remove(oldWakeUp, entity); } saveOrRemoveComponent(entity, delayedComponent); } @Override public void cancelPeriodicAction(EntityRef entity, String actionId) { PeriodicActionComponent periodicActionComponent = entity.getComponent(PeriodicActionComponent.class); long oldWakeUp = periodicActionComponent.getLowestWakeUp(); periodicActionComponent.removeScheduledActionId(actionId); long newWakeUp = periodicActionComponent.getLowestWakeUp(); if (!periodicActionComponent.isEmpty() && oldWakeUp < newWakeUp) { periodicOperationsSortedByTime.remove(oldWakeUp, entity); periodicOperationsSortedByTime.put(newWakeUp, entity); } else if (periodicActionComponent.isEmpty()) { periodicOperationsSortedByTime.remove(oldWakeUp, entity); } saveOrRemoveComponent(entity, periodicActionComponent); } @Override public boolean hasDelayedAction(EntityRef entity, String actionId) { DelayedActionComponent delayedComponent = entity.getComponent(DelayedActionComponent.class); return delayedComponent != null && delayedComponent.containsActionId(actionId); } @Override public boolean hasPeriodicAction(EntityRef entity, String actionId) { PeriodicActionComponent periodicActionComponent = entity.getComponent(PeriodicActionComponent.class); return periodicActionComponent != null && periodicActionComponent.containsActionId(actionId); } private void saveOrRemoveComponent(EntityRef delayedEntity, DelayedActionComponent delayedActionComponent) { if (delayedActionComponent.isEmpty()) { delayedEntity.removeComponent(DelayedActionComponent.class); } else { delayedEntity.saveComponent(delayedActionComponent); } } private void saveOrRemoveComponent(EntityRef periodicEntity, PeriodicActionComponent periodicActionComponent) { if (periodicActionComponent.isEmpty()) { periodicEntity.removeComponent(PeriodicActionComponent.class); } else { periodicEntity.saveComponent(periodicActionComponent); } } // Deprecated methods @ReceiveEvent(components = {DelayedActionComponent.class}) public void getDelayedAction(HasDelayedActionEvent event, EntityRef entity) { event.setResult(hasDelayedAction(entity, event.getActionId())); } @ReceiveEvent(components = {DelayedActionComponent.class}) public void cancelDelayedAction(CancelDelayedActionEvent event, EntityRef entity) { cancelDelayedAction(entity, event.getActionId()); } @ReceiveEvent public void addDelayedAction(AddDelayedActionEvent event, EntityRef entity) { addDelayedAction(entity, event.getActionId(), event.getDelay()); } }