many-sessions improvements, type fixes, more javadocs (#2094)

* some javadoc and type alignment improvements

* some javadoc and type alignment improvements

* example adapter

* alignment to previous changes

* improvements to many session testing

* add missing annotation
This commit is contained in:
Jonathan Shook
2024-11-20 17:25:30 -06:00
committed by GitHub
parent 2229f919a2
commit 130977fa34
123 changed files with 949 additions and 393 deletions

View File

@@ -24,6 +24,16 @@ import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
import io.nosqlbench.nb.api.labels.NBLabels;
import io.nosqlbench.nb.api.components.core.NBComponent;
/**
* <P>This service allows for the dynamic instancing of {@link DriverAdapter}s as services,
* using a well-defined method signature instead of (just) a no-args constructor. Since all key elements
* of the nosqlbench runtime are assembled into a component tree, each one requires an attachment
* point (parent) and child identifiers (label names and values) that uniquely describe it. This would typically be
* encoded as a constructor signature, however, there is no SPI mechanism which makes this easy to manage across JDPA
* and non-JDPA runtimes. So instead, we indirect this to a higher level service which has one and only one
* responsibility: to provide an instance through the well-defined API of {@link #load(NBComponent, NBLabels)}.
* </P>
*/
public interface DriverAdapterLoader {
public <A extends CycleOp<?>,B extends Space> DriverAdapter<A,B> load(NBComponent parent, NBLabels childLabels);
public <A extends CycleOp<?>, B extends Space> DriverAdapter<A, B> load(NBComponent parent, NBLabels childLabels);
}

View File

@@ -18,12 +18,12 @@ package io.nosqlbench.adapters.api.activityimpl;
import com.codahale.metrics.Timer;
import groovy.lang.Binding;
import io.nosqlbench.adapters.api.activityimpl.uniform.DriverAdapter;
import io.nosqlbench.adapters.api.activityimpl.uniform.Space;
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
import io.nosqlbench.adapters.api.evalctx.*;
import io.nosqlbench.adapters.api.metrics.ThreadLocalNamedTimers;
import io.nosqlbench.adapters.api.templating.ParsedOp;
import io.nosqlbench.nb.api.components.core.NBComponent;
import io.nosqlbench.nb.api.engine.metrics.instruments.MetricCategory;
import io.nosqlbench.nb.api.labels.NBLabels;
import io.nosqlbench.nb.api.errors.OpConfigError;
@@ -47,7 +47,7 @@ import java.util.function.LongFunction;
* @param <OP>
* The type of operation
*/
public abstract class BaseOpDispenser<OP extends CycleOp<?>,SPACE extends Space>
public abstract class BaseOpDispenser<OP extends CycleOp<?>, SPACE extends Space>
extends NBBaseComponent implements OpDispenser<OP> {
protected final static Logger logger = LogManager.getLogger(BaseOpDispenser.class);
public static final String VERIFIER = "verifier";
@@ -58,7 +58,6 @@ public abstract class BaseOpDispenser<OP extends CycleOp<?>,SPACE extends Space>
public static final String STOP_TIMERS = "stop-timers";
private final String opName;
protected final DriverAdapter<? extends OP, ? extends SPACE> adapter;
private final NBLabels labels;
public final Timer verifierTimer;
private boolean instrument;
@@ -81,11 +80,9 @@ public abstract class BaseOpDispenser<OP extends CycleOp<?>,SPACE extends Space>
private final CycleFunction<Boolean> _verifier;
private final ThreadLocal<CycleFunction<Boolean>> tlVerifier;
protected BaseOpDispenser(final DriverAdapter<? extends OP, ? extends SPACE> adapter, final ParsedOp op) {
super(adapter);
protected BaseOpDispenser(final NBComponent parentC, final ParsedOp op, LongFunction<? extends SPACE> spaceF) {
super(parentC);
opName = op.getName();
this.adapter = adapter;
this.spaceF = adapter.getSpaceFunc(op);
labels = op.getLabels();
this.timerStarts = op.takeOptionalStaticValue(START_TIMERS, String.class)
@@ -140,19 +137,19 @@ public abstract class BaseOpDispenser<OP extends CycleOp<?>,SPACE extends Space>
try {
initBlocks.forEach((initName, stringTemplate) -> {
GroovyCycleFunction<?> initFunction =
new GroovyCycleFunction<>(initName,stringTemplate,verifierImports,verifierStaticImports,variables);
new GroovyCycleFunction<>(initName, stringTemplate, verifierImports, verifierStaticImports, variables);
logger.info("configured verifier init:" + initFunction);
initFunction.setVariable("_parsed_op",op);
initFunction.setVariable("_parsed_op", op);
initFunction.apply(0L);
});
} catch (Exception e) {
throw new OpConfigError("error in verifier-init:" + e.getMessage(),e);
throw new OpConfigError("error in verifier-init:" + e.getMessage(), e);
}
Map<String, ParsedTemplateString> namedVerifiers = op.getTemplateMap().takeAsNamedTemplates(VERIFIER);
List<CycleFunction<Boolean>> verifierFunctions = new ArrayList<>();
try {
namedVerifiers.forEach((verifierName,stringTemplate) -> {
namedVerifiers.forEach((verifierName, stringTemplate) -> {
GroovyBooleanCycleFunction verifier =
new GroovyBooleanCycleFunction(verifierName, stringTemplate, verifierImports, verifierStaticImports, variables);
logger.info("configured verifier:" + verifier);
@@ -163,13 +160,13 @@ public abstract class BaseOpDispenser<OP extends CycleOp<?>,SPACE extends Space>
}
try {
op.takeAsOptionalStringTemplate(EXPECTED_RESULT)
.map(tpl -> new GroovyObjectEqualityFunction(op.getName()+"-"+EXPECTED_RESULT, tpl, verifierImports, verifierStaticImports, variables))
op.takeAsOptionalStringTemplate(EXPECTED_RESULT)
.map(tpl -> new GroovyObjectEqualityFunction(op.getName() + "-" + EXPECTED_RESULT, tpl, verifierImports, verifierStaticImports, variables))
.map(vl -> {
logger.info("Configured equality verifier: " + vl);
return vl;
})
.ifPresent(verifierFunctions::add);
.ifPresent(verifierFunctions::add);
} catch (Exception gre) {
throw new OpConfigError("error in verifier:" + gre.getMessage(), gre);
}
@@ -182,23 +179,20 @@ public abstract class BaseOpDispenser<OP extends CycleOp<?>,SPACE extends Space>
return this.opName;
}
public DriverAdapter<? extends OP, ? extends SPACE> getAdapter() {
return this.adapter;
}
private void configureInstrumentation(final ParsedOp pop) {
instrument = pop.takeStaticConfigOr("instrument", false);
if (this.instrument) {
final int hdrDigits = pop.getStaticConfigOr("hdr_digits", 4);
successTimer = create().timer(
"successfor_"+getOpName(),
"successfor_" + getOpName(),
hdrDigits,
MetricCategory.Core,
"Successful result timer for specific operation '" + pop.getName() + "'"
);
errorTimer = create().timer(
"errorsfor_"+getOpName(),
"errorsfor_" + getOpName(),
hdrDigits,
MetricCategory.Core,
"Errored result timer for specific operation '" + pop.getName() + "'"

View File

@@ -78,12 +78,12 @@ public interface OpDispenser<OP extends CycleOp<?>> extends LongFunction<OP>, Op
* additional processing if a caller wants to execute the operation
* multiple times, as for retries.
*
* @param value The cycle number which serves as the seed for any
* @param cycle The cycle number which serves as the seed for any
* generated op fields to be bound into an operation.
* @return an executable operation
*/
OP getOp(long value);
OP getOp(long cycle);
CycleFunction<Boolean> getVerifier();

View File

@@ -20,8 +20,8 @@ import io.nosqlbench.adapters.api.activityimpl.uniform.DriverAdapter;
import io.nosqlbench.adapters.api.activityimpl.uniform.Space;
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
import io.nosqlbench.adapters.api.templating.ParsedOp;
import io.nosqlbench.nb.api.components.core.NBComponent;
import java.util.function.BiFunction;
import java.util.function.LongFunction;
/**
@@ -77,24 +77,86 @@ import java.util.function.LongFunction;
* matches the driver name.
* </p>
*
* @param <OPTYPE> The parameter type of the actual operation which will be used
* to hold all the details for executing an operation,
* generally something that implements {@link Runnable}.
* @param <OPTYPE>
* The parameter type of the actual operation which will be used
* to hold all the details for executing an operation,
* generally something that implements {@link Runnable}.
*/
public interface OpMapper<OPTYPE extends CycleOp<?>, SPACETYPE extends Space>
extends BiFunction<ParsedOp, LongFunction<SPACETYPE>, OpDispenser<? extends OPTYPE>> {
// extends BiFunction<ParsedOp, LongFunction<SPACETYPE>, OpDispenser<? extends OPTYPE>>
{
/**
* Interrogate the parsed command, and provide a new
* This method is responsible for interrogating the fields in the provided {@link ParsedOp} template object,
* determining what adapter-specific operation it maps to, and creating the associated {@link OpDispenser} for
* that type.
*
* @param op
* The {@link ParsedOp} which is the parsed version of the user-provided op template.
* This contains all the fields provided by the user, as well as explicit knowledge of
* which ones are static and dynamic.
* <H2>Implementation Notes</H2>
*
* <P>It is important to be familiar with the structure of the {@link ParsedOp}, since this is the runtime model
* API for an op template. It provides everything you need to turn various op fields into proper lambdas, which
* can then be composed together to make higher-order lambdas. The returned {@link OpDispenser} is essentially
* a singular {@link LongFunction} which captures all of the just-in-time construction patterns needed within.</P>
*
* <H3>Op Mapping</H3>
* <p>
* Generally speaking, implementations of this method should interrogate the op fields in the ParsedOp to determine
* the specific op that matches the user's intentions. It is <EM>Highly</EM> reccommended that each of the valid
* op types is presented as an example in the associated adapter documentation. (Each adapter must have a
* self-named markdown help file in it's source tree.) Good op mappers are based on specific examples which are
* documented, as this is the only way a user knows what op types are available.
* </p>
*
* <p>
* What determines the type of op can be based on something explicit, like the value of a {@code type} field, or it
* can be based on whether
* certain fields are present or not. Advanced implementations might take into account which fields are provided as
* static values and which are specified as (dynamic) bindings. The op mapping phase is meant to qualify and
* pre-check that the fields provided are valid and specific for a given type of operation.
* </p>
*
* <p>All of the effective logic for op mapping must be contained within the
* {@link #apply(NBComponent, ParsedOp, LongFunction)} method. This includes what happens within the constructor of
* any {@link OpDispenser}. What happens within {@link OpDispenser} implementations (the second phase), however,
* should do as little qualification of field values as possible, focusing simply on constructing the type of
* operation for which they are designed. This suggest the following conventions:
* <UL>
* <LI>Type-mapping logic (determine which op type) is done in the main body of
* {@link #apply(NBComponent, ParsedOp, LongFunction)}, and nothing else. Once the desired op dispenser (based on
* the intended op type) is determined, it is immediately constructed and returned.
* </LI>
* <LI>
* Lambda-construction logic is contained in the constructor of the returned op dispenser. This pre-bakes
* as much of the op construction behavior as possible, building only a single lambda to do the heavy lifting
* later.
* </LI>
* <LI>When {@link OpDispenser#apply(long)}</LI> is called with a cycle value, it only needs to call the lambda to
* return a fully-formed op, ready to be executed via its {@link CycleOp#apply(long)} method.
* </UL>
* </p>
*
* @param adapterC
* The adapter component. This is passed as an {@link NBComponent} because it is not valid to rely
* on the driver adapter instance directly for anything. All logic for an op should be captured
* in its mapper and dispenser, and all (other) state for it should be captured within the space.
* However, the mapper exists within the nosqlbench runtime as part of the component tree, so it
* is included for that reason alone. (You'll need it for super construtors in some cases)
* @param pop
* The {@link ParsedOp} which is the parsed version of the user-provided op template. This contains all the
* fields provided by the user, as well as explicit knowledge of which ones are static and dynamic. It provides
* convenient lambda-construction methods to streamline the effort of creating the top-level op lambda.
* @param spaceInitF
* @return An OpDispenser which can be used to synthesize real operations.
* This is the pre-baked lambda needed to access the specific {@link SPACETYPE} for a given cycle, if or when it
* is needed. Not all op types need this, since they may have all the state needed fully captured within the
* native type. For those that do, ensure that you are accessing the value through this function lazily and
* only within the stack. Using this function to do anything but build more lambdas is probably a
* programming error.
* @return An OpDispenser which can be used to synthesize directly executable operations.
*/
@Override
OpDispenser<OPTYPE> apply(ParsedOp op, LongFunction<SPACETYPE> spaceInitF);
OpDispenser<OPTYPE> apply(
NBComponent adapterC,
ParsedOp pop,
LongFunction<SPACETYPE> spaceInitF
);
}

View File

@@ -20,6 +20,11 @@ package io.nosqlbench.adapters.api.activityimpl.uniform;
import io.nosqlbench.adapters.api.activityimpl.uniform.flowtypes.CycleOp;
/**
* This example of a space uses the <EM>SelfT</EM> technique to enable
* the self type to be used in method signatures and return types.
* @param <SelfT>
*/
public class BaseSpace<SelfT extends BaseSpace<SelfT> > implements Space {
private final String spaceName;

View File

@@ -101,7 +101,7 @@ public class ConcurrentIndexCache<T> implements Iterable<T> {
return currentCache.get(key);
}
}
logger.debug(() -> "returning index[ " + key + " ] for [ " + label + " ] cache");
logger.trace(() -> "returning index[ " + key + " ] for [ " + label + " ] cache");
return value;
}

View File

@@ -36,70 +36,95 @@ import java.util.function.Function;
import java.util.function.LongFunction;
/**
* <P>The DriverAdapter interface is the replacement
* for ActivityTypes. This interface takes a simpler
* approach. Specifically, all of the core logic which was being pasted into each
* driver type is centralized, and only the necessary interfaces
* needed for construction new operations and shared context are exposed.
* This means all drivers can now benefit from cross-cutting enhancements
* in the core implementation.
* <P>The DriverAdapter interface is the top level API for implementing
* operations in NoSQLBench. It defines the related APIs needed to fully realized an adapter
* at runtime. A driver adapter can map op templates from YAML form to a fully executable
* form in native Java code, just as an application might do with a native driver. It can also
* do trivial operations, like simply calling {@code System.out.println(...)}. When you specify an adapter by name,
* you are choosing both the available operations, and the rules for converting a YAML op template into those
* operations. This is a two-step process: The adapter provides mapping logic for converting high-level templates from
* users into op types, and separate dispensing logic which can efficiently create these ops at runtime. When used
* together, they power <EM>op synthesis</EM> -- efficient and deterministic construction of runtime operations using
* procedural generation methods.
* </p>
*
* <p>
* Generally speaking, a driver adapter is responsible for
* <UL>
* <LI>Defining a class type for holding related state known as a {@link Space}.
* <UL><LI>The type of this is specified as
* generic parameter {@link SPACETYPE}.</LI></UL></LI>
* <LI>Defining a factory method for constructing an instance of the space.</LI>
* <LI>Recognizing the op templates that are documented for it via an {@link OpMapper}
* and assigning them to an op implementation.
* <UL><LI>The base type of these ops is specified as generic
* parameter {@link OPTYPE}.</LI></UL>
* </LI>
* <LI>Constructing dispensers for each matching op implementation with a matching {@link OpDispenser}</LI>
* implementation.
* </UL>
* <P>At runtime, the chain of these together ({@code cycle -> op mapping -> op dispensing -> op}) is cached
* as a look-up table of op dispensers. This results in the simpler logical form {@code cycle -> op synthesis ->
* operation}.
* </P>
*
* @param <OPTYPE> The type of Runnable operation which will be used to wrap
* all operations for this driver adapter. This allows you to
* add context or features common to all operations of this
* type.
* @param <SPACETYPE> The type of context space used by this driver to hold
* cached instances of clients, session, or other native driver
* esoterica. This is the shared state which might be needed
* during construction of R type operations, or even for individual
* operations.
* <H3>Variable Naming Conventions</H3>
* <p>
* Within the related {@link DriverAdapter} APIs, the following conventions are (more often) used, and will be found
* everywhere:
* <UL>
* <LI>{@code namedF} describes a namedFunction variable. Functional patterns are used everywhere in these APIs
* .</LI>
* <LI>{@code namedC} describes a namedComponent variable. All key elements of the nosqlbench runtime are
* part of a component tree.</LI>
* <LI>{@code pop} describes a {@link ParsedOp} instance.</LI>
* </UL>
* </P>
* <H3>Generic Parameters</H3>
* <p>
* When a new driver adapter is defined with the generic parameters below, it becomes easy to build out a matching
* DriverAdapter with any modern IDE.</P>
*
* @param <OPTYPE>
* The type of {@link CycleOp} which will be used to wrap all operations for this driver adapter. This allows you
* to add context or features common to all operations of this type. This can be a simple <a
* href="https://en.wikipedia.org/wiki/Marker_interface_pattern">Marker</a> interface, or it can be something more
* concrete that captures common logic or state across all the operations used for a given adapter. It is highly
* advised to <EM>NOT</EM> leave it as simply {@code CycleOp<?>}, since specific op implementations offer much
* better performance.
* @param <SPACETYPE>
* The type of context space used by this driver to hold cached instances of clients, session, or other native
* driver state. This is the shared state which might be needed during construction operations for an adapter.
* <EM>No other mechanism is provided nor intended for holding adapter-specific state. You must store it in
* this type. This includes client instances, codec mappings, or anything else that a single instance of an
* application would need to effectively use a given native driver.</EM>
*/
public interface DriverAdapter<OPTYPE extends CycleOp<?>, SPACETYPE extends Space> extends NBComponent {
/**
* <p>
* <H2>Op Mapping</H2>
* An Op Mapper is a function which can look at the parsed
* fields in a {@link ParsedOp} and create an OpDispenser.
* An OpDispenser is a function that will produce a special
* type {@link OPTYPE} that this DriverAdapter implements as its
* op implementation.</p>
* An Op Mapper is a function which can look at a {@link ParsedOp} and create a matching {@link OpDispenser}.
* An OpDispenser is a function that will produce a special type {@link OPTYPE} that this DriverAdapter implements
* as its op implementation. There may be many different ops supported by an adapter, thus there may be similarly
* many dispensers.</p>
*
* <p>
* The function that is returned is responsible for creating another function.
* This might seem counter-intuitive but it is very intentional because
* of these design constraints:
* <UL>
* <LI>Mapping op semantics to a type of operation must be very clear
* and flexible. Performance is not important at this layer because this is all done
* during initialization time for an activity.</LI>
* <LI>Synthesizing executable operations from a known type of operational template
* must be done very efficiently. This part is done during activity execution, so
* having the details of how you are going to create an op for execution already
* sorted out is important.</LI>
* </UL>
* Both {@link OpMapper} and {@link OpDispenser} are functions. The role of {@link OpMapper} is to
* map the op template provided by the user to an op implementation provided by the driver adapter,
* and then to create a factor function for it (the {@link OpDispenser}).</p>
*
* To clarify the distinction between these two phases, the first is canonically
* called <em>op mapping</em> in the documentation. The second is called
* <em>op synthesis</em>.
* <p>These roles are split for a very good reason: Mapping what the user wants to do with an op template
* is resource intenstive, and should be as pre-baked as possible. This phase is the <EM>op mapping</EM> phase.
* It is essential that the mapping logic be very clear and maintainable. Performance is not as important
* at this phase, because all of the mapping logic is run during initialization of an activity.
* </p>
*
* <p>
* <H2>A note on implementation strategy:</H2>
* Generally speaking, implementations of this method should interrogate the op fields
* in the ParsedOp and return an OpDispenser that matches the user's intentions.
* This can be based on something explicit, like the value of a {@code type} field,
* or it can be based on whether certain fields are present or not. Advanced implementations
* might take into account which fields are provided as static values and which are
* specified as bindings. In any case, the op mapping phase is meant to qualify and
* pre-check that the fields provided are valid and specific for a given type of operation.
* What happens within {@link OpDispenser} implementations (the second phase), however, should do
* as little qualification of field values as possible, focusing simply on constructing
* the type of operation for which they are designed.
* Conversely, <EM>op dispensing</EM> (the next phase) while an activity is running should be as efficient as
* possible.
* </p>
*
* @return a synthesizer function for {@link OPTYPE} op generation
* @return a dispensing function for {@link OPTYPE} op generation
*/
OpMapper<OPTYPE, SPACETYPE> getOpMapper();
@@ -108,7 +133,8 @@ public interface DriverAdapter<OPTYPE extends CycleOp<?>, SPACETYPE extends Spac
* the fields in the op template before they are interpreted canonically.
* At this level, the transform is applied once to the input map
* (once per op template) to yield the map that is provided to
* {@link OpMapper} implementations.
* {@link OpMapper} implementations. <EM>This is here to make backwards compatibility
* possible for op templates which have changed. Avoid using it unless necessary.</EM>
*
* @return A function to pre-process the op template fields.
*/
@@ -121,28 +147,29 @@ public interface DriverAdapter<OPTYPE extends CycleOp<?>, SPACETYPE extends Spac
* routing it to the correct error handler, or naming it in logs, or naming
* metrics, override this method in your activity.
*
* @return A function that can reliably and safely map an instance of Throwable to a stable name.
* @return A function that can reliably and safely map an instance of Throwable to a stable adapter-specific name.
*/
default Function<Throwable, String> getErrorNameMapper() {
return t -> t.getClass().getSimpleName();
}
default List<Function<Map<String,Object>,Map<String,Object>>> getOpFieldRemappers() {
default List<Function<Map<String, Object>, Map<String, Object>>> getOpFieldRemappers() {
return List.of(f -> f);
}
// ConcurrentSpaceCache<SPACETYPE> getSpaceCache();
/**
* This method allows each driver adapter to create named state which is automatically
* <P>This method allows each driver adapter to create named state which is automatically
* cached and re-used by name. For each (driver,space) combination in an activity,
* a distinct space instance will be created. In general, adapter developers will
* use the space type associated with an adapter to wrap native driver instances
* one-to-one. As such, if the space implementation is a {@link AutoCloseable},
* it will be explicitly shutdown as part of the activity shutdown.
* it will be explicitly shutdown as part of the activity shutdown.</P>
*
* <p>It is not necessary to implement a space for a stateless driver adapter, or one
* which puts all state into each op instance.</p>
*
* @return A function which can initialize a new Space, which is a place to hold
* object state related to retained objects for the lifetime of a native driver.
* object state related to retained objects for the lifetime of a native driver.
*/
default LongFunction<SPACETYPE> getSpaceInitializer(NBConfiguration cfg) {
return n -> (SPACETYPE) new Space() {
@@ -162,8 +189,11 @@ public interface DriverAdapter<OPTYPE extends CycleOp<?>, SPACETYPE extends Spac
* markdown file for this driver adapter.</li>
* <li>&lt;resources&gt;/docs/&lt;adaptername&gt;/ - A directory containing any type of file which
* is to be included in docs under the adapter name, otherwise known as the {@link Service#selector()}</li>
* <li>&lt;resources&gt;/docs/&lt;adaptername&gt;.md</li>
* </ul>
* path &lt;resources&gt;/docs/&lt;adaptername&gt;. Specifically, the file
*
* <P><EM>A build will fail if any driver adapter implementation is missing at least one self-named
* markdown doc file.</EM></P>
*
* @return A {@link DocsBinder} which describes docs to include for a given adapter.
*/
@@ -184,7 +214,7 @@ public interface DriverAdapter<OPTYPE extends CycleOp<?>, SPACETYPE extends Spac
default String getAdapterName() {
Service svc = this.getClass().getAnnotation(Service.class);
if (svc==null) {
if (svc == null) {
throw new RuntimeException("The Service annotation for adapter of type " + this.getClass().getCanonicalName() + " is missing.");
}
return svc.selector();

View File

@@ -23,25 +23,25 @@ import java.util.function.LongFunction;
/**
* <H2>CycleOp: f(cycle) -> T</H2>
* <p>A CycleOp of T is an operation which takes a long input value
* and produces a value of type T. It is implemented as
* <p>A CycleOp of T is an operation which takes a long input value and produces a value of type T. It is implemented as
* {@link LongFunction} of T.</p>
*
* <h2>Designer Notes</h2>
* <p>
* If you are using the value in this call to select a specific type of behavior, it is very
* likely a candidate for factoring into separate op implementations.
* The {@link OpMapper}
* and {@link OpDispenser} abstractions are meant to move
* op type selection and scheduling to earlier in the activity.
* If you are using the value in this call to select a specific type of behavior, i.e.
* among a variety of operation types, it is very likely a candidate for factoring
* into separate op implementations. By using the <pre>cycle -> mapper -> dispenser -> op</pre>
* pattern to move as much of the initialization logic forward, you will have
* much more efficient operations and much cleaner code. The {@link OpMapper}
* and {@link OpDispenser} abstractions are meant to move op type selection and scheduling to earlier in the activity.
* </p>
*
*/
public interface CycleOp<T> extends LongFunction<T> {
/**
* <p>Run an action for the given cycle.</p>
*
* @param value The cycle value for which an operation is run
* @param value
* The cycle value for which an operation is run
*/
@Override
T apply(long value);

View File

@@ -32,7 +32,7 @@ public class DryCycleOpDispenserWrapper<S extends Space, RESULT> extends BaseOpD
ParsedOp pop,
OpDispenser<CycleOp<RESULT>> realDispenser
) {
super(adapter, pop);
super(adapter, pop, adapter.getSpaceFunc(pop));
this.realDispenser = realDispenser;
logger.warn(
"initialized {} for dry run only. " +

View File

@@ -32,7 +32,7 @@ public class EmitterCycleOpDispenserWrapper<O,S extends Space,R> extends BaseOpD
ParsedOp pop,
OpDispenser<CycleOp<R>> realDispenser
) {
super(adapter, pop);
super(adapter, pop, adapter.getSpaceFunc(pop));
this.realDispenser = realDispenser;
logger.warn(
"initialized {} for to emit the result type to stdout. ",

View File

@@ -29,7 +29,7 @@ public class EmitterOpDispenserWrapper extends BaseOpDispenser<CycleOp<?>, Space
public EmitterOpDispenserWrapper(DriverAdapter<CycleOp<?>,Space> adapter, ParsedOp pop,
OpDispenser<? extends CycleOp<?>> realDispenser) {
super(adapter, pop);
super(adapter, pop, adapter.getSpaceFunc(pop));
this.realDispenser = realDispenser;
}
@Override