All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.flash3388.flashlib.vision.control.SingleThreadVisionControl Maven / Gradle / Ivy

package com.flash3388.flashlib.vision.control;

import com.castle.util.throwables.ThrowableHandler;
import com.castle.util.throwables.Throwables;
import com.flash3388.flashlib.time.Clock;
import com.flash3388.flashlib.time.SystemNanoClock;
import com.flash3388.flashlib.time.Time;
import com.flash3388.flashlib.vision.Pipeline;
import com.flash3388.flashlib.vision.Source;
import com.flash3388.flashlib.vision.VisionException;
import com.flash3388.flashlib.vision.VisionResult;
import com.flash3388.flashlib.vision.analysis.Analysis;
import com.flash3388.flashlib.vision.control.event.NewResultEvent;
import com.flash3388.flashlib.vision.control.event.VisionListener;
import com.flash3388.flashlib.vision.processing.Processor;
import com.notifier.Controllers;
import com.notifier.EventController;

import java.util.Optional;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

public class SingleThreadVisionControl implements VisionControl {

    public static class Builder {

        private final Function> mRunner;

        private EventController mEventController;
        private Clock mClock;
        private ThrowableHandler mThrowableHandler;

        private Source mSource;
        private Pipeline> mPreProcessPipeline;
        private Processor, Optional> mProcessor;

        private Builder(Function> runner) {
            mRunner = runner;
            mThrowableHandler = Throwables.silentHandler();
        }

        public Builder eventController(EventController eventController) {
            mEventController = eventController;
            return this;
        }

        public Builder clock(Clock clock) {
            mClock = clock;
            return this;
        }

        public Builder onError(ThrowableHandler handler) {
            mThrowableHandler = handler;
            return this;
        }

        public Builder source(Source source) {
            mSource = source;
            return this;
        }

        public Builder preProcessWithOptions(Pipeline> pipeline) {
            mPreProcessPipeline = pipeline;
            return this;
        }

        public Builder preProcess(Pipeline pipeline) {
            mPreProcessPipeline = Pipeline.mapper(pipeline, VisionData::getData);
            return this;
        }

        public Builder processorWithOptions(Processor, Optional> processor) {
            mProcessor = processor;
            return this;
        }

        public Builder processor(Processor> processor) {
            mProcessor = Processor.mapper(processor, VisionData::getData);
            return this;
        }

        public SingleThreadVisionControl build() {
            if (mClock == null) {
                mClock = new SystemNanoClock();
            }
            if (mEventController == null) {
                mEventController = Controllers.newSyncExecutionController();
            }

            return new SingleThreadVisionControl(mRunner, mClock, mEventController,
                    mSource,
                    mProcessor.divergeIn(mPreProcessPipeline),
                    mThrowableHandler);
        }
    }

    public static  Builder withExecutorService(ScheduledExecutorService executorService, Time pollingTime) {
        Function> runner = (task)->
                executorService.scheduleAtFixedRate(task, pollingTime.value(), pollingTime.value(), pollingTime.unit());
        return new Builder(runner);
    }

    private final Function> mRunner;
    private final Clock mClock;
    private final EventController mEventController;
    private final VisionOptions mVisionOptions;

    private final Runnable mTask;

    private final AtomicReference> mFuture;
    private final AtomicReference mLatestResult;

    private SingleThreadVisionControl(Function> runner, Clock clock, EventController eventController,
                              Source source, Processor, Optional> processor, ThrowableHandler throwableHandler) {
        mRunner = runner;
        mClock = clock;
        mEventController = eventController;
        mVisionOptions = new VisionOptions();

        mFuture = new AtomicReference<>();
        mLatestResult = new AtomicReference<>();

        mTask = new Task(source,
                processor.pipeTo(new NewAnalysisHandler(eventController, clock, mLatestResult)),
                throwableHandler, mVisionOptions);
    }

    @Override
    public boolean isRunning() {
        return mFuture.get() != null;
    }

    @Override
    public synchronized void start() {
        if (isRunning()) {
            return;
        }

        Future future = mRunner.apply(mTask);
        mFuture.set(future);
    }

    @Override
    public synchronized void stop() {
        if (!isRunning()) {
            return;
        }

        Future future = mFuture.get();
        if (future != null) {
            future.cancel(true);
        }
    }

    @Override
    public  void setOption(VisionOption option, T value) {
        mVisionOptions.put(option, value);
    }

    @Override
    public  Optional getOption(VisionOption option) {
        return mVisionOptions.get(option);
    }

    @Override
    public  T getOptionOrDefault(VisionOption option, T defaultValue) {
        return mVisionOptions.getOrDefault(option, defaultValue);
    }

    @Override
    public Optional getLatestResult() {
        return getLatestResult(false);
    }

    @Override
    public Optional getLatestResult(boolean clear) {
        return Optional.ofNullable(clear ? mLatestResult.getAndSet(null) : mLatestResult.get());
    }

    @Override
    public Optional getLatestResult(Time maxTimestamp) {
        return getLatestResult(maxTimestamp, false);
    }

    @Override
    public Optional getLatestResult(Time maxTimestamp, boolean clear) {
        VisionResult result = clear ? mLatestResult.getAndSet(null) : mLatestResult.get();
        if (result == null) {
            return Optional.empty();
        }

        Time now = mClock.currentTime();
        Time passed = now.sub(result.getTimestamp());
        if (passed.after(maxTimestamp)) {
            return Optional.empty();
        }

        return Optional.of(result);
    }

    @Override
    public void addListener(VisionListener listener) {
        mEventController.registerListener(listener);
    }

    private static class Task implements Runnable {

        private final Source mSource;
        private final Pipeline> mPipeline;
        private final ThrowableHandler mThrowableHandler;
        private final VisionOptions mVisionOptions;

        private Task(Source source, Pipeline> pipeline,
                     ThrowableHandler throwableHandler, VisionOptions visionOptions) {
            mSource = source;
            mPipeline = pipeline;
            mThrowableHandler = throwableHandler;
            mVisionOptions = visionOptions;
        }

        @Override
        public void run() {
            try {
                S source = mSource.get();
                mPipeline.process(new VisionData<>(source, mVisionOptions));
            } catch (VisionException e) {
                mThrowableHandler.handle(e);
            }
        }
    }

    private static class NewAnalysisHandler implements Pipeline> {

        private final EventController mEventController;
        private final Clock mClock;
        private final AtomicReference mVisionResult;

        private NewAnalysisHandler(EventController eventController, Clock clock,
                                   AtomicReference visionResult) {
            mEventController = eventController;
            mClock = clock;
            mVisionResult = visionResult;
        }

        @Override
        public void process(Optional input) throws VisionException {
            if (!input.isPresent()) {
                return;
            }

            Analysis analysis = input.get();

            Time now = mClock.currentTime();
            VisionResult visionResult = new VisionResult(analysis, now);
            mVisionResult.set(visionResult);

            mEventController.fire(new NewResultEvent(visionResult), NewResultEvent.class,
                    VisionListener.class, VisionListener::onNewResult);
        }
    }
}