Create.java

package nl.tudelft.simulation.dsol.formalisms.flow;

import java.util.function.Supplier;

import org.djutils.event.EventType;
import org.djutils.exceptions.Throw;
import org.djutils.metadata.MetaData;
import org.djutils.metadata.ObjectDescriptor;

import nl.tudelft.simulation.dsol.SimRuntimeException;
import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
import nl.tudelft.simulation.dsol.simtime.dist.DistContinuousSimulationTime;
import nl.tudelft.simulation.dsol.simulators.DevsSimulatorInterface;
import nl.tudelft.simulation.dsol.statistics.SimCounter;
import nl.tudelft.simulation.jstats.distributions.DistDiscrete;
import nl.tudelft.simulation.jstats.distributions.DistDiscreteConstant;

/**
 * The Create flow object generates entities with a certain inter-arrival time.
 * <p>
 * Copyright (c) 2002-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
 * for project information <a href="https://simulation.tudelft.nl/dsol/manual/" target="_blank">DSOL Manual</a>. The DSOL
 * project is distributed under a three-clause BSD-style license, which can be found at
 * <a href="https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">DSOL License</a>.
 * </p>
 * @author <a href="https://www.linkedin.com/in/peterhmjacobs">Peter Jacobs </a>
 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
 * @param <T> the time type
 */
public class Create<T extends Number & Comparable<T>> extends FlowBlock<T, Create<T>>
{
    /** CREATE_EVENT is fired on creation. */
    public static final EventType CREATE_EVENT = new EventType(new MetaData("CREATE_EVENT", "Created entities)",
            new ObjectDescriptor("numberCreated", "number of entities created", Integer.class)));

    /** the inter-arrival time distribution. */
    private DistContinuousSimulationTime<T> intervalDist;

    /** the start time distribution for the generator, can be null when no start time distribution is used. */
    private DistContinuousSimulationTime<T> startTimeDist;

    /** the start time for the generator, can be null when no start time is used. */
    private T startTime;

    /** the distribution of the number of objects generated at each generation event. */
    private DistDiscrete batchSizeDist;

    /** the supplier of entities; to be filled with, e.g., a lambda expression. */
    private Supplier<Entity<T>> entitySupplier;

    /** the maximum number of creation events. */
    private long maxNumberCreationEvents = Long.MAX_VALUE;

    /** the maximum number of generated entities. */
    private long maxNumberGeneratedEntities = Long.MAX_VALUE;

    /** the end time for the generator; when null, there is no end time. */
    private T endTime = null;

    /** the number of creation events; note: not the number of generated entities. */
    private long numberCreationEvents = 0;

    /** the number of generated entities. */
    private long numberGeneratedEntities = 0;

    /** the next create event. */
    private SimEventInterface<T> nextCreateEvent = null;

    /** a potential count statistic; when null, no statistic is calculated. */
    private SimCounter<T> countStatistic;

    /**
     * Construct a new generator for objects in a simulation. Constructed objects are sent to the 'destination' of the Create
     * flow object when a destination has been indicated with the setDestination method.
     * @param id the id of the FlowObject
     * @param simulator is the on which the construction of the objects must be scheduled.
     */
    public Create(final String id, final DevsSimulatorInterface<T> simulator)
    {
        super(id, simulator);
        this.batchSizeDist = new DistDiscreteConstant(simulator.getModel().getDefaultStream(), 1);
    }

    /**
     * Generate a new entity, if allowed by the maximum number of creation events, the maximum number of created entities, and
     * the end time when the generator has to stop.
     */
    protected synchronized void generate()
    {
        Throw.whenNull(this.entitySupplier, "Entity supplier has not been initialized");
        Throw.whenNull(this.intervalDist, "Interval distribution has not been initialized");
        if (this.numberCreationEvents >= this.maxNumberCreationEvents)
        {
            this.nextCreateEvent = null;
            return;
        }
        if (this.endTime != null && getSimulator().getSimulatorTime().doubleValue() > this.endTime.doubleValue())
        {
            this.nextCreateEvent = null;
            return;
        }
        this.numberCreationEvents++;
        for (int i = 0; i < this.batchSizeDist.draw(); i++)
        {
            Entity<T> entity = this.entitySupplier.get();
            this.fireTimedEvent(Create.CREATE_EVENT, 1, getSimulator().getSimulatorTime());
            this.releaseEntity(entity);
            this.numberGeneratedEntities++;
            if (this.numberGeneratedEntities >= this.maxNumberGeneratedEntities)
            {
                this.nextCreateEvent = null;
                return;
            }
        }
        this.nextCreateEvent = getSimulator().scheduleEventRel(this.intervalDist.draw(), this, "generate", null);
    }

    /**
     * Provide a supplier for the entity to be generated.
     * @param entitySupplier the supplier to generate an entity
     * @return the Create instance for method chaining
     */
    public Create<T> setEntitySupplier(final Supplier<Entity<T>> entitySupplier)
    {
        this.entitySupplier = entitySupplier;
        if (this.nextCreateEvent == null)
        {
            this.nextCreateEvent = getSimulator().scheduleEventNow(this, "generate", null);
        }
        return this;
    }

    /**
     * Set a (new) start time distribution for this create block. An IllegalStateException will be thrown when the create block
     * has already generated its first entities.
     * @param startTimeDist the new startTime distribution
     * @return the Create instance for method chaining
     * @throws IllegalStateException when the create block has already generated entities
     */
    public Create<T> setStartTimeDist(final DistContinuousSimulationTime<T> startTimeDist)
    {
        Throw.when(this.numberCreationEvents > 0, IllegalStateException.class,
                "startTime distribution cannot be changed after entities have been created");
        this.startTimeDist = startTimeDist;
        if (this.nextCreateEvent != null)
        {
            getSimulator().cancelEvent(this.nextCreateEvent);
        }
        this.startTime = this.startTimeDist.draw();
        this.nextCreateEvent = getSimulator().scheduleEventRel(this.startTime, this, "generate", null);
        return this;
    }

    /**
     * Set a (new) non-stochastic start time for this create block. An IllegalStateException will be thrown when the create
     * block has already generated its first entities.
     * @param startTime the new startTime
     * @return the Create instance for method chaining
     * @throws IllegalStateException when the create block has already generated entities
     */
    public Create<T> setStartTime(final T startTime)
    {
        Throw.when(this.numberCreationEvents > 0, IllegalStateException.class,
                "startTime cannot be changed after entities have been created");
        this.startTimeDist = null;
        this.startTime = startTime;
        if (this.nextCreateEvent != null)
        {
            getSimulator().cancelEvent(this.nextCreateEvent);
        }
        this.nextCreateEvent = getSimulator().scheduleEventRel(startTime, this, "generate", null);
        return this;
    }

    /**
     * Set a new interarrival distribution. This is used by, e.g., classes that changes the arrival rate at set intervals.
     * @param intervalDist the new interarrival distribution
     * @return the Create instance for method chaining
     */
    public Create<T> setIntervalDist(final DistContinuousSimulationTime<T> intervalDist)
    {
        Throw.whenNull(intervalDist, "Interval distribution cannot be null");
        this.intervalDist = intervalDist;
        if (this.numberCreationEvents == 0)
        {
            // start means generation at t=0 or t ~ startTimeDist
            return this;
        }
        if (this.nextCreateEvent != null)
        {
            getSimulator().cancelEvent(this.nextCreateEvent);
        }
        this.nextCreateEvent = getSimulator().scheduleEventRel(this.intervalDist.draw(), this, "generate", null);
        return this;
    }

    /**
     * Set a new fixed batch size.
     * @param batchSize the new batch size
     * @return the Create instance for method chaining
     */
    public Create<T> setBatchSize(final int batchSize)
    {
        Throw.when(batchSize < 0, IllegalArgumentException.class, "Batch size should not be negative");
        this.batchSizeDist = new DistDiscreteConstant(getSimulator().getModel().getDefaultStream(), batchSize);
        return this;
    }

    /**
     * Set a new batch size distribution.
     * @param batchSizeDist the new batch size distribution
     * @return the Create instance for method chaining
     */
    public Create<T> setBatchSizeDist(final DistDiscrete batchSizeDist)
    {
        Throw.whenNull(batchSizeDist, "Batch size distribution cannot be null");
        this.batchSizeDist = batchSizeDist;
        return this;
    }

    /**
     * Set a new value for the maximum number of creation events.
     * @param maxNumberCreationEvents the new maximum number of creation events
     * @return the Create instance for method chaining
     */
    public Create<T> setMaxNumberCreationEvents(final long maxNumberCreationEvents)
    {
        this.maxNumberCreationEvents = maxNumberCreationEvents;
        return this;
    }

    /**
     * Set a new value for the maximum number of entities to be created.
     * @param maxNumberGeneratedEntities the new the maximum number of entities to be created
     * @return the Create instance for method chaining
     */
    public Create<T> setMaxNumberGeneratedEntities(final long maxNumberGeneratedEntities)
    {
        this.maxNumberGeneratedEntities = maxNumberGeneratedEntities;
        return this;
    }

    /**
     * Set a new value for the end time when the generator has to stop generating entities. A null value means that there is no
     * end time. The end time is inclusive. If entities are to be generated exactly at the end time, this is done before
     * checking for the end time.
     * @param endTime the new value for the end time when the generator has to stop, can be null
     * @return the Create instance for method chaining
     */
    public Create<T> setEndTime(final T endTime)
    {
        this.endTime = endTime;
        return this;
    }

    /**
     * Turn on the default statistics for this flow block.
     * @return the Create instance for method chaining
     */
    public Create<T> setDefaultStatistics()
    {
        if (!hasDefaultStatistics())
        {
            super.setDefaultFlowBlockStatistics();
            this.countStatistic = new SimCounter<>("Create.CountEntity:" + getBlockNumber(),
                    getId() + " generated entity count", getSimulator().getModel(), this, Create.CREATE_EVENT);
            this.countStatistic.initialize();
        }
        return this;
    }

    @Override
    public void receiveEntity(final Entity<T> entity)
    {
        throw new SimRuntimeException("Create block should not receive any entities");
    }

    /**
     * Return the batch size distribution.
     * @return the batch size distribution
     */
    public DistDiscrete getBatchSizeDist()
    {
        return this.batchSizeDist;
    }

    /**
     * Return the interarrival distribution.
     * @return the interarrival distribution
     */
    public DistContinuousSimulationTime<T> getIntervalDist()
    {
        return this.intervalDist;
    }

    /**
     * Return the maximum number of entities to be generated.
     * @return the maximum number of entities to be generated
     */
    public long getMaxNumberGeneratedEntities()
    {
        return this.maxNumberGeneratedEntities;
    }

    /**
     * Return the maximum number of create events.
     * @return the maximum number of create events
     */
    public long getMaxNumberCreationEvents()
    {
        return this.maxNumberCreationEvents;
    }

    /**
     * Return the startTime distribution of the generator.
     * @return the startTime distribution of the generator, can be null
     */
    public DistContinuousSimulationTime<T> getStartTimeDist()
    {
        return this.startTimeDist;
    }

    /**
     * Return the start time of the generator, either provided or drawn from the start time distribution. When startTime is
     * null, generation of entities will start immediately when the model starts.
     * @return the start time of the generator, either provided or drawn from the start time distribution, can be null
     */
    public T getStartTime()
    {
        return this.startTime;
    }

    /**
     * Return the time when entity generation has to stop. This value can be null (no stop time).
     * @return endTime the time when entity generation has to stop, can be null
     */
    public T getEndTime()
    {
        return this.endTime;
    }

    /**
     * Return the number of creation events. This is not equal to the number of generated entities, since that depends on the
     * batch size (which is drawn from a distribution, and can be zero or larger than one for each generation).
     * @return the number of creation events
     */
    public long getNumberCreationEvents()
    {
        return this.numberCreationEvents;
    }

    /**
     * Return the number of generated entities.
     * @return the number of generated entities
     */
    public long getNumberGeneratedEntities()
    {
        return this.numberGeneratedEntities;
    }

    /**
     * Return the next event when an entity will be created. If null, no further entities will be created.
     * @return the next event when an entity will be created, can be null
     */
    public SimEventInterface<T> getNextCreateEvent()
    {
        return this.nextCreateEvent;
    }

    /**
     * Return whether statistics are turned on for this create block.
     * @return whether statistics are turned on for this create block.
     */
    public boolean hasDefaultStatistics()
    {
        return this.countStatistic != null;
    }

    /**
     * Return the count statistic for this create block.
     * @return the count statistic for this create block.
     */
    public SimCounter<T> getCountStatistic()
    {
        return this.countStatistic;
    }

}