com.fluxtion.compiler.generation.targets.InMemoryEventProcessor Maven / Gradle / Ivy
/*
* SPDX-FileCopyrightText: © 2024 Gregory Higgins
* SPDX-License-Identifier: AGPL-3.0-only
*/
package com.fluxtion.compiler.generation.targets;
import com.fluxtion.compiler.EventProcessorConfig;
import com.fluxtion.compiler.builder.filter.FilterDescription;
import com.fluxtion.compiler.generation.compiler.classcompiler.StringCompilation;
import com.fluxtion.compiler.generation.model.CbMethodHandle;
import com.fluxtion.compiler.generation.model.ExportFunctionData;
import com.fluxtion.compiler.generation.model.Field;
import com.fluxtion.compiler.generation.model.SimpleEventProcessorModel;
import com.fluxtion.compiler.generation.util.ClassUtils;
import com.fluxtion.runtime.EventProcessor;
import com.fluxtion.runtime.EventProcessorContext;
import com.fluxtion.runtime.StaticEventProcessor;
import com.fluxtion.runtime.annotations.AfterTrigger;
import com.fluxtion.runtime.audit.Auditor;
import com.fluxtion.runtime.callback.CallbackDispatcher;
import com.fluxtion.runtime.callback.EventProcessorCallbackInternal;
import com.fluxtion.runtime.callback.ExportFunctionAuditEvent;
import com.fluxtion.runtime.callback.InternalEventProcessor;
import com.fluxtion.runtime.dataflow.Tuple;
import com.fluxtion.runtime.dataflow.groupby.MutableTuple;
import com.fluxtion.runtime.event.Event;
import com.fluxtion.runtime.input.EventFeed;
import com.fluxtion.runtime.input.SubscriptionManager;
import com.fluxtion.runtime.input.SubscriptionManagerNode;
import com.fluxtion.runtime.lifecycle.BatchHandler;
import com.fluxtion.runtime.lifecycle.Lifecycle;
import com.fluxtion.runtime.node.ForkedTriggerTask;
import com.fluxtion.runtime.node.MutableEventProcessorContext;
import com.fluxtion.runtime.service.Service;
import com.fluxtion.runtime.service.ServiceListener;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.reflections.ReflectionUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@Slf4j
public class InMemoryEventProcessor implements EventProcessor, StaticEventProcessor, InternalEventProcessor, Lifecycle, BatchHandler {
private final SimpleEventProcessorModel simpleEventProcessorModel;
private final EventProcessorConfig config;
private final MutableEventProcessorContext context;
private final EventProcessorCallbackInternal callbackDispatcher;
private final SubscriptionManagerNode subscriptionManager;
private final BitSet dirtyBitset = new BitSet();
private final BitSet eventOnlyBitset = new BitSet();
private final BitSet postProcessBufferingBitset = new BitSet();
private final BitSet forkedTaskBitset = new BitSet();
private final List eventHandlers = new ArrayList<>();
private final List forkTriggerTaskNodes = new ArrayList<>();
private final Map, List> noFilterEventHandlerToBitsetMap = new HashMap<>();
private final Map> filteredEventHandlerToBitsetMap = new HashMap<>();
private final List auditors = new ArrayList<>();
private final Set> eventCompleteInvokeSet = new HashSet<>();
public boolean buffering = false;
private Object currentEvent;
public boolean processing = false;
private boolean isDefaultHandling;
private boolean initCalled = false;
private Object exportingWrapper;
private Consumer unKnownEventHandler = (e) -> {
};
public InMemoryEventProcessor(SimpleEventProcessorModel simpleEventProcessorModel, EventProcessorConfig config) {
this.simpleEventProcessorModel = simpleEventProcessorModel;
this.config = config;
try {
context = getNodeById(EventProcessorContext.DEFAULT_NODE_NAME);
callbackDispatcher = getNodeById(CallbackDispatcher.DEFAULT_NODE_NAME);
subscriptionManager = getNodeById(SubscriptionManager.DEFAULT_NODE_NAME);
subscriptionManager.setSubscribingEventProcessor(this);
context.setEventProcessorCallback(this);
} catch (Exception e) {
throw new RuntimeException("cannot build InMemoryEventProcessor", e);
}
}
@Override
@SneakyThrows
public void onEvent(Object event) {
if (buffering) {
triggerCalculation();
}
if (processing) {
callbackDispatcher.queueReentrantEvent(event);
} else {
processing = true;
onEventInternal(event);
callbackDispatcher.dispatchQueuedCallbacks();
processing = false;
}
}
@Override
public void bufferEvent(Object event) {
if (!config.isSupportBufferAndTrigger()) {
throw new UnsupportedOperationException("bufferEvent not supported");
}
buffering = true;
processEvent(event, true);
}
@Override
public void triggerCalculation() {
if (!config.isSupportBufferAndTrigger()) {
throw new UnsupportedOperationException("triggerCalculation not supported");
}
log.debug("dirtyBitset, after:{}", dirtyBitset);
log.debug("======== GRAPH CYCLE START TRIGGER ========");
for (int i = dirtyBitset.nextSetBit(0); i >= 0; i = dirtyBitset.nextSetBit(i + 1)) {
log.debug("event dispatch bitset index[{}] handler[{}::{}]",
i,
eventHandlers.get(i).callbackHandle.getVariableName(),
eventHandlers.get(i).callbackHandle.getMethod().getName()
);
eventHandlers.get(i).onEvent(null);
}
// dirtyBitset.clear();
dirtyBitset.or(postProcessBufferingBitset);
postProcessBufferingBitset.clear();
postEventProcessing();
buffering = false;
log.debug("======== GRAPH CYCLE END TRIGGER ========");
}
public void postEventProcessing() {
log.debug("======== eventComplete ========");
eventCompleteInvokeSet.clear();
for (int i = dirtyBitset.length(); (i = dirtyBitset.previousSetBit(i - 1)) >= 0; ) {
log.debug("check for postprocessing index[{}]", i);
if (eventHandlers.get(i).willInvokeEventComplete()) {
log.debug("event dispatch bitset index[{}] handler[{}::{}]",
i,
eventHandlers.get(i).callbackHandle.getVariableName(),
eventHandlers.get(i).onEventCompleteMethod.getName()
);
}
eventHandlers.get(i).eventComplete();
}
log.debug("======== GRAPH CYCLE END EVENT:[{}] ========", currentEvent);
auditors.stream()
.filter(a -> Auditor.FirstAfterEvent.class.isAssignableFrom(a.getClass()))
.forEach(Auditor::processingComplete);
log.debug("After event");
//call reinitialise for each element
forkTriggerTaskNodes.forEach(ForkTriggerTaskNode::reinitialise);
simpleEventProcessorModel.getEventEndMethods().forEach(this::invokeRunnable);
auditors.stream()
.filter(a -> !Auditor.FirstAfterEvent.class.isAssignableFrom(a.getClass()))
.forEach(Auditor::processingComplete);
dirtyBitset.clear();
log.debug("dirtyBitset, afterClear:{}", dirtyBitset);
forkedTaskBitset.clear();
log.debug("forkedTaskBitset, afterClear:{}", dirtyBitset);
currentEvent = null;
}
public void dispatchQueuedCallbacks() {
callbackDispatcher.dispatchQueuedCallbacks();
}
private void subclassDispatchSearch(BitSet updateBitset) {
if (updateBitset.isEmpty()) {
Set> eventClassSet = new HashSet<>();
filteredEventHandlerToBitsetMap.keySet().stream().map(FilterDescription::getEventClass).forEach(eventClassSet::add);
eventClassSet.addAll(noFilterEventHandlerToBitsetMap.keySet());
List> sortedClasses = ClassUtils.sortClassHierarchy(eventClassSet);
sortedClasses.stream()
.filter(c -> c.isInstance(currentEvent))
.findFirst()
.ifPresent(c -> {
if (Event.class.isAssignableFrom(c)) {
FilterDescription filterDescription = FilterDescription.build(currentEvent);
filterDescription.setEventClass((Class extends Event>) c);
filteredEventHandlerToBitsetMap.getOrDefault(filterDescription, Collections.emptyList()).forEach(updateBitset::set);
}
if (updateBitset.isEmpty()) {
noFilterEventHandlerToBitsetMap.getOrDefault(c, Collections.emptyList()).forEach(updateBitset::set);
}
});
}
}
private void processEvent(Object event, boolean buffer) {
currentEvent = event;
Object defaultEvent = checkForDefaultEventHandling(event);
log.debug("dirtyBitset, before:{}", dirtyBitset);
auditNewEvent(event);
final BitSet updateBitset = buffer ? eventOnlyBitset : dirtyBitset;
filteredEventHandlerToBitsetMap.getOrDefault(FilterDescription.build(defaultEvent), Collections.emptyList()).forEach(updateBitset::set);
if (updateBitset.isEmpty()) {
noFilterEventHandlerToBitsetMap.getOrDefault(defaultEvent.getClass(), Collections.emptyList()).forEach(updateBitset::set);
}
//find a potential sub class if empty
subclassDispatchSearch(updateBitset);
postProcessBufferingBitset.or(updateBitset);
//now actually dispatch
log.debug("dirtyBitset, after:{}", updateBitset);
log.debug("======== GRAPH CYCLE START EVENT:[{}] ========", event);
log.debug("======== process event ========");
boolean eventMatched = false;
for (int i = checkForForkedTask(-1, updateBitset); i >= 0; i = checkForForkedTask(i, updateBitset)) {
log.debug("event dispatch bitset index[{}] handler[{}::{}]",
i,
eventHandlers.get(i).callbackHandle.getVariableName(),
eventHandlers.get(i).callbackHandle.getMethod().getName()
);
eventMatched = true;
eventHandlers.get(i).onEvent(event);
}
if (!eventMatched) {
unKnownEventHandler.accept(event);
}
postProcessBufferingBitset.or(updateBitset);
if (!buffer) {
dirtyBitset.or(updateBitset);
log.debug("dirtyBitset, postProcessing:{}", dirtyBitset);
postEventProcessing();
}
updateBitset.clear();
}
private int checkForForkedTask(int startIndex, BitSet updateBitset) {
log.debug("checkForForkedTask startIndex:{}, updateBitset:{} forkedBitset:{}", startIndex, updateBitset, forkedTaskBitset);
final int nextDirtyIndex = updateBitset.nextSetBit(startIndex + 1);
final int endIndex = Math.max(nextDirtyIndex, updateBitset.length());
final int nextForkIndex = forkedTaskBitset.nextSetBit(startIndex + 1);
log.debug("checkForForkedTask endIndex:{}, nextDirtyIndex:{} nextForkIndex:{}", endIndex, nextDirtyIndex, nextForkIndex);
if ((nextForkIndex > 0) & ((nextForkIndex <= nextDirtyIndex) | (nextDirtyIndex < 0))) {
log.debug("joining on parent forked task bitset index[{}] handler[{}::{}]",
nextForkIndex,
eventHandlers.get(nextForkIndex).callbackHandle.getMethod().getDeclaringClass().getSimpleName(),
eventHandlers.get(nextForkIndex).callbackHandle.getMethod().getName()
);
eventHandlers.get(nextForkIndex).joinOnParentTask();
updateBitset.set(nextForkIndex);
return nextForkIndex;
}
return nextDirtyIndex;
}
@Override
public void onEventInternal(Object event) {
processEvent(event, false);
}
@Override
public boolean isDirty(Object node) {
return dirtySupplier(node).getAsBoolean();
}
@Override
public BooleanSupplier dirtySupplier(Object node) {
//TODO replace with map
return eventHandlers.stream()
.filter(n -> n.getCallbackHandle().getInstance() == node)
.map(n -> (BooleanSupplier) n::isDirty)
.findFirst()
.orElse(() -> false);
}
@Override
public void setDirty(Object node, boolean dirtyFlag) {
if (dirtyFlag) {
eventHandlers.stream()
.filter(n -> n.getCallbackHandle().getInstance() == node)
.forEach(Node::markDirty);
}
}
private Object checkForDefaultEventHandling(Object event) {
Object mapped = event;
if (isDefaultHandling && !simpleEventProcessorModel.getDispatchMap().containsKey(event.getClass())) {
mapped = new Object();
}
return mapped;
}
public void auditNewEvent(Object event) {
if (Event.class.isAssignableFrom(event.getClass())) {
auditors.stream()
.filter(a -> Auditor.FirstAfterEvent.class.isAssignableFrom(a.getClass()))
.forEach(a -> a.eventReceived((Event) event));
auditors.stream()
.filter(a -> !Auditor.FirstAfterEvent.class.isAssignableFrom(a.getClass()))
.forEach(a -> a.eventReceived((Event) event));
} else {
auditors.stream()
.filter(a -> Auditor.FirstAfterEvent.class.isAssignableFrom(a.getClass()))
.forEach(a -> a.eventReceived(event));
auditors.stream()
.filter(a -> !Auditor.FirstAfterEvent.class.isAssignableFrom(a.getClass()))
.forEach(a -> a.eventReceived(event));
}
}
public void nodeInvoked(Object node, String nodeName, String methodName, Object event) {
auditors.stream()
.filter(Auditor::auditInvocations)
.forEachOrdered(a -> a.nodeInvoked(node, nodeName, methodName, event));
}
@Override
public void batchPause() {
processing = true;
auditNewEvent(LifecycleEvent.BatchPause);
simpleEventProcessorModel.getBatchPauseMethods().forEach(this::invokeRunnable);
postEventProcessing();
callbackDispatcher.dispatchQueuedCallbacks();
processing = false;
}
@Override
public void batchEnd() {
processing = true;
auditNewEvent(LifecycleEvent.BatchEnd);
simpleEventProcessorModel.getBatchEndMethods().forEach(this::invokeRunnable);
postEventProcessing();
callbackDispatcher.dispatchQueuedCallbacks();
processing = false;
}
@Override
public void setContextParameterMap(Map © 2015 - 2025 Weber Informatics LLC | Privacy Policy