public final class I2cDeviceSynchImpl extends I2cDeviceSynchReadHistoryImpl implements I2cDeviceSynch, Engagable
I2cDeviceSynchImpl
is a utility class that makes it easy to read or write data to
an instance of I2cDevice
. Its functionality is exposed through the I2cDeviceSynch
interface. Please see that interface, and the I2cDeviceSynchImpl
constructor here, for
further information.Modifier and Type | Class and Description |
---|---|
protected class |
I2cDeviceSynchImpl.Callback |
protected static class |
I2cDeviceSynchImpl.CONTROLLER_PORT_MODE |
protected static class |
I2cDeviceSynchImpl.READ_CACHE_STATUS |
protected static class |
I2cDeviceSynchImpl.WRITE_CACHE_STATUS |
protected class |
I2cDeviceSynchImpl.WriteCacheStatus |
I2cDeviceSynch.HeartbeatAction, I2cDeviceSynch.ReadMode, I2cDeviceSynch.ReadWindow
HardwareDeviceHealth.HealthStatus
HardwareDevice.Manufacturer
Modifier and Type | Field and Description |
---|---|
protected I2cDeviceSynchImpl.Callback |
callback |
protected java.lang.Object |
callbackLock |
protected java.lang.Object |
concurrentClientLock |
protected I2cController |
controller |
protected I2cDeviceSynchImpl.CONTROLLER_PORT_MODE |
controllerPortMode |
protected int |
cregWrite |
protected static int |
dibCacheOverhead |
protected boolean |
disableReadWindows |
protected java.lang.Object |
engagementLock |
protected HardwareDeviceHealthImpl |
hardwareDeviceHealth |
protected boolean |
hasReadWindowChanged |
protected I2cDeviceSynch.HeartbeatAction |
heartbeatAction |
protected java.util.concurrent.ExecutorService |
heartbeatExecutor |
protected I2cAddr |
i2cAddr |
protected I2cDevice |
i2cDevice |
protected int |
iregWriteFirst |
protected boolean |
isClosing |
protected boolean |
isControllerLegacy |
protected boolean |
isEngaged |
protected boolean |
isHooked |
protected boolean |
isI2cDeviceOwned |
protected boolean |
isReadWindowSentToControllerInitialized |
protected boolean |
isWriteCoalescingEnabled |
protected boolean |
loggingEnabled |
protected java.lang.String |
loggingTag |
protected static int |
msCallbackLockAbandon |
protected static int |
msCallbackLockWaitQuantum |
protected int |
msHeartbeatInterval |
protected java.lang.String |
name |
protected long |
nanoTimeReadCacheValid |
protected byte[] |
readCache |
protected java.util.concurrent.locks.Lock |
readCacheLock |
protected I2cDeviceSynchImpl.READ_CACHE_STATUS |
readCacheStatus |
protected TimeWindow |
readCacheTimeWindow |
protected java.util.concurrent.atomic.AtomicInteger |
readerWriterCount |
protected java.util.concurrent.locks.ReadWriteLock |
readerWriterGate |
protected java.util.concurrent.atomic.AtomicInteger |
readerWriterPreventionCount |
protected I2cDeviceSynch.ReadWindow |
readWindow |
protected I2cDeviceSynch.ReadWindow |
readWindowActuallyRead |
protected I2cDeviceSynch.ReadWindow |
readWindowSentToController |
protected RobotUsbModule |
robotUsbModule |
protected ElapsedTime |
timeSinceLastHeartbeat |
protected byte[] |
writeCache |
protected java.util.concurrent.locks.Lock |
writeCacheLock |
protected I2cDeviceSynchImpl.WriteCacheStatus |
writeCacheStatus |
historyQueue, historyQueueCapacity, historyQueueLock
Constructor and Description |
---|
I2cDeviceSynchImpl(I2cDevice i2cDevice,
boolean isI2cDeviceOwned)
Instantiates an
I2cDeviceSynchImpl instance on the indicated I2cDevice . |
I2cDeviceSynchImpl(I2cDevice i2cDevice,
I2cAddr i2cAddr,
boolean isI2cDeviceOwned)
Instantiate an
I2cDeviceSynchImpl instance on the indicated I2cDevice
using the indicated I2C address. |
Modifier and Type | Method and Description |
---|---|
protected void |
acquireReaderLockShared() |
protected void |
adjustHooking() |
protected void |
assignReadWindow(I2cDeviceSynch.ReadWindow newWindow) |
void |
close()
Closes this device
|
protected static byte[] |
concatenateByteArrays(byte[] left,
byte[] right) |
protected void |
disableReadsAndWrites() |
void |
disengage()
Disengage the object from underlying services it uses to render its function.
|
protected void |
enableReadsAndWrites() |
void |
enableWriteCoalescing(boolean enable)
Enables or disables an optimization wherein writes to two sets of adjacent register
ranges may be coalesced into a single I2c transaction if the second write comes along
while the first is still queued for writing.
|
void |
engage()
(Re)enage the object with its underlying services.
|
void |
ensureReadWindow(I2cDeviceSynch.ReadWindow windowNeeded,
I2cDeviceSynch.ReadWindow windowToSet)
Ensure that the current register window covers the indicated set of registers.
|
protected void |
forceDrainReadersAndWriters() |
java.lang.String |
getConnectionInfo()
Get connection information about this device in a human readable format
|
java.lang.String |
getDeviceName()
Returns a string suitable for display to the user as to the type of device.
|
HardwareDeviceHealth.HealthStatus |
getHealthStatus() |
I2cDeviceSynch.HeartbeatAction |
getHeartbeatAction()
Returns the current action, if any, to take upon expiration of the heartbeat interval.
|
int |
getHeartbeatInterval()
Returns the interval within which communication must be received by the I2C device lest
a timeout occur.
|
I2cAddr |
getI2cAddr()
Deprecated.
|
I2cAddr |
getI2cAddress()
Returns the I2C address currently in use to communicate with an I2C hardware device
|
boolean |
getLogging() |
java.lang.String |
getLoggingTag() |
HardwareDevice.Manufacturer |
getManufacturer()
Returns an indication of the manufacturer of this device.
|
I2cDeviceSynch.ReadWindow |
getReadWindow()
Returns the current register window used for reading.
|
java.lang.String |
getUserConfiguredName()
Returns the human-recognizable name of this device, if same has been set.
|
int |
getVersion()
Version
|
protected I2cDeviceSynchImpl.WRITE_CACHE_STATUS |
getWriteCacheStatus() |
protected void |
gracefullyDrainReadersAndWriters() |
protected void |
hook() |
protected void |
initWriteCacheStatus(I2cDeviceSynchImpl.WRITE_CACHE_STATUS status) |
boolean |
isArmed()
Returns whether, as of this instant, this device client is alive and operational in
its normally expected mode; that is, whether it is currently in communication
with its underlying hardware or whether it is in some other state.
|
boolean |
isEngaged()
Returns whether the object is currently in the engaged state.
|
protected boolean |
isOpenForReading() |
protected boolean |
isOpenForWriting() |
boolean |
isWriteCoalescingEnabled()
Answers as to whether write coalescing is currently enabled on this device.
|
protected void |
log(int verbosity,
java.lang.String message) |
protected void |
log(int verbosity,
java.lang.String format,
java.lang.Object... args) |
protected boolean |
newReadsAndWritesAllowed() |
byte[] |
read(int ireg,
int creg)
Read a contiguous set of device I2C registers.
|
byte |
read8(int ireg)
Read the byte at the indicated register.
|
protected boolean |
readCacheIsValid() |
protected boolean |
readCacheValidityCurrentOrImminent() |
TimestampedData |
readTimeStamped(int ireg,
int creg)
Reads and returns a contiguous set of device I2C registers, together with a best-available
timestamp of when the actual I2C read occurred.
|
TimestampedData |
readTimeStamped(int ireg,
int creg,
I2cDeviceSynch.ReadWindow readWindowNeeded,
I2cDeviceSynch.ReadWindow readWindowSet)
Advanced: Atomically calls ensureReadWindow() with the last two parameters and then
readTimeStamped() with the first two without the possibility of a concurrent client
interrupting in the middle.
|
protected byte[] |
readWriteCache() |
protected void |
releaseReaderLockShared() |
void |
resetDeviceConfigurationForOpMode()
Resets the device's configuration to that which is expected at the beginning of an OpMode.
|
void |
setHealthStatus(HardwareDeviceHealth.HealthStatus status) |
void |
setHeartbeatAction(I2cDeviceSynch.HeartbeatAction action)
Sets the action to take when the current heartbeat interval expires.
|
void |
setHeartbeatInterval(int msHeartbeatInterval)
Sets the interval within which communication must be received by the I2C device lest
a timeout may occur.
|
void |
setI2cAddr(I2cAddr newAddress)
Deprecated.
|
void |
setI2cAddress(I2cAddr i2cAddr)
Configures a new I2C address to use
|
void |
setLogging(boolean enabled)
Turn logging on or off.
|
void |
setLoggingTag(java.lang.String loggingTag)
Set the tag to use when logging is on.
|
void |
setReadWindow(I2cDeviceSynch.ReadWindow newWindow)
Set the set of registers that we will read and read and read again on every hardware cycle
|
protected void |
setReadWindowInternal(I2cDeviceSynch.ReadWindow newWindow) |
void |
setUserConfiguredName(java.lang.String name)
Informs the device of a name by which it would be recognized by the user.
|
protected void |
setWriteCacheStatus(I2cDeviceSynchImpl.WRITE_CACHE_STATUS status) |
protected void |
unhook() |
protected long |
waitForIdleWriteCache() |
protected void |
waitForValidReadCache() |
protected void |
waitForWriteCompletionInternal(I2cWaitControl writeControl) |
void |
waitForWriteCompletions(I2cWaitControl waitControl)
Waits for the most recent write to complete according to the behavior specified in writeControl.
|
void |
write(int ireg,
byte[] data)
Writes data to a set of registers, beginning with the one indicated, using
I2cWaitControl.ATOMIC semantics. |
void |
write(int ireg,
byte[] data,
I2cWaitControl waitControl)
Writes data to a set of registers, beginning with the one indicated.
|
void |
write8(int ireg,
int data)
Writes a byte to the indicated register using
I2cWaitControl.ATOMIC semantics. |
void |
write8(int ireg,
int data,
I2cWaitControl waitControl)
Writes a byte to the indicated register.
|
addToHistoryQueue, getHistoryQueue, getHistoryQueueCapacity, setHistoryQueueCapacity
protected I2cAddr i2cAddr
protected I2cDevice i2cDevice
protected boolean isI2cDeviceOwned
protected I2cController controller
protected boolean isControllerLegacy
protected HardwareDeviceHealthImpl hardwareDeviceHealth
protected RobotUsbModule robotUsbModule
protected boolean isHooked
protected boolean isEngaged
protected java.util.concurrent.atomic.AtomicInteger readerWriterPreventionCount
protected java.util.concurrent.locks.ReadWriteLock readerWriterGate
protected java.util.concurrent.atomic.AtomicInteger readerWriterCount
protected boolean isClosing
protected I2cDeviceSynchImpl.Callback callback
protected boolean loggingEnabled
protected java.lang.String loggingTag
protected java.lang.String name
protected ElapsedTime timeSinceLastHeartbeat
protected TimeWindow readCacheTimeWindow
protected byte[] readCache
protected byte[] writeCache
protected static final int dibCacheOverhead
protected java.util.concurrent.locks.Lock readCacheLock
protected java.util.concurrent.locks.Lock writeCacheLock
protected static final int msCallbackLockWaitQuantum
protected static final int msCallbackLockAbandon
protected boolean isWriteCoalescingEnabled
protected final java.lang.Object engagementLock
protected final java.lang.Object concurrentClientLock
protected final java.lang.Object callbackLock
protected boolean disableReadWindows
protected volatile I2cDeviceSynch.ReadWindow readWindow
protected volatile I2cDeviceSynch.ReadWindow readWindowActuallyRead
protected volatile I2cDeviceSynch.ReadWindow readWindowSentToController
protected volatile boolean isReadWindowSentToControllerInitialized
protected volatile boolean hasReadWindowChanged
protected volatile long nanoTimeReadCacheValid
protected volatile I2cDeviceSynchImpl.READ_CACHE_STATUS readCacheStatus
protected final I2cDeviceSynchImpl.WriteCacheStatus writeCacheStatus
protected volatile I2cDeviceSynchImpl.CONTROLLER_PORT_MODE controllerPortMode
protected volatile int iregWriteFirst
protected volatile int cregWrite
protected volatile int msHeartbeatInterval
protected volatile I2cDeviceSynch.HeartbeatAction heartbeatAction
protected volatile java.util.concurrent.ExecutorService heartbeatExecutor
public I2cDeviceSynchImpl(I2cDevice i2cDevice, I2cAddr i2cAddr, boolean isI2cDeviceOwned)
I2cDeviceSynchImpl
instance on the indicated I2cDevice
using the indicated I2C address.i2cDevice
- the I2cDevice
that the new I2cDeviceSynchImpl
is
to be a client ofi2cAddr
- the I2C address to which communications will be targetedisI2cDeviceOwned
- If true, then when this I2cDeviceSynchImpl
closes, the
underlying I2cDevice
is closed as well; otherwise, it is
not. Typically, if the provided I2cDevice
is retrieved from
an OpMode's hardware map, one passes false to isI2cDeviceOwned
,
as such @link I2cDevice}s should remain functional across multiple
OpMode invocations.HardwareDevice.close()
,
HardwareDevice.close()
public I2cDeviceSynchImpl(I2cDevice i2cDevice, boolean isI2cDeviceOwned)
I2cDeviceSynchImpl
instance on the indicated I2cDevice
.
When this constructor is used, setI2cAddress(I2cAddr)
must be called later in order
for the instance to be functional.@Deprecated public void setI2cAddr(I2cAddr newAddress)
I2cDeviceSynchSimple
setI2cAddr
in interface I2cDeviceSynchSimple
newAddress
- the new I2C addresspublic void setI2cAddress(I2cAddr i2cAddr)
I2cAddrConfig
setI2cAddress
in interface I2cAddrConfig
i2cAddr
- the new I2C address to usepublic I2cAddr getI2cAddress()
I2cAddressableDevice
getI2cAddress
in interface I2cAddressableDevice
@Deprecated public I2cAddr getI2cAddr()
I2cDeviceSynchSimple
getI2cAddr
in interface I2cDeviceSynchSimple
public void engage()
Engagable
protected void hook()
protected void adjustHooking()
public boolean isEngaged()
Engagable
public boolean isArmed()
I2cDeviceSynchSimple
isArmed
in interface I2cDeviceSynchSimple
Engagable.engage()
public void disengage()
Engagable
protected void unhook()
protected void disableReadsAndWrites()
protected void enableReadsAndWrites()
protected boolean newReadsAndWritesAllowed()
protected void gracefullyDrainReadersAndWriters()
protected void forceDrainReadersAndWriters()
public HardwareDevice.Manufacturer getManufacturer()
HardwareDevice
getManufacturer
in interface HardwareDevice
public java.lang.String getDeviceName()
HardwareDevice
getDeviceName
in interface HardwareDevice
public java.lang.String getConnectionInfo()
HardwareDevice
getConnectionInfo
in interface HardwareDevice
public int getVersion()
HardwareDevice
getVersion
in interface HardwareDevice
public void resetDeviceConfigurationForOpMode()
HardwareDevice
resetDeviceConfigurationForOpMode
in interface HardwareDevice
public void close()
HardwareDevice
close
in interface HardwareDevice
public void setHealthStatus(HardwareDeviceHealth.HealthStatus status)
setHealthStatus
in interface HardwareDeviceHealth
public HardwareDeviceHealth.HealthStatus getHealthStatus()
getHealthStatus
in interface HardwareDeviceHealth
public void setReadWindow(I2cDeviceSynch.ReadWindow newWindow)
I2cDeviceSynch
setReadWindow
in interface I2cDeviceSynch
newWindow
- the register window to read. May be null, indicating that no reads are to occur.I2cDeviceSynch.getReadWindow()
protected void setReadWindowInternal(I2cDeviceSynch.ReadWindow newWindow)
protected void assignReadWindow(I2cDeviceSynch.ReadWindow newWindow)
public I2cDeviceSynch.ReadWindow getReadWindow()
I2cDeviceSynch
getReadWindow
in interface I2cDeviceSynch
I2cDeviceSynch.setReadWindow(ReadWindow)
public void ensureReadWindow(I2cDeviceSynch.ReadWindow windowNeeded, I2cDeviceSynch.ReadWindow windowToSet)
I2cDeviceSynch
ensureReadWindow
in interface I2cDeviceSynch
windowNeeded
- Test the current register window, if any, against this window
to see if an update to the current register window is needed in
order to cover it. May be null, indicating that an update to the
current register window is always neededwindowToSet
- If an update to the current register window is needed, then this
is the window to which it will be set. May be null.I2cDeviceSynch.setReadWindow(ReadWindow)
,
I2cDeviceSynchSimple.read8(int)
public byte read8(int ireg)
I2cDeviceSynchSimple
I2cDeviceSynchSimple.readTimeStamped(int, int)
for a
complete description.read8
in interface I2cDeviceSynchSimple
ireg
- the register number to readI2cDeviceSynchSimple.read(int, int)
,
I2cDeviceSynchSimple.readTimeStamped(int, int)
,
I2cDeviceSynch.ensureReadWindow(I2cDeviceSynch.ReadWindow, I2cDeviceSynch.ReadWindow)
public byte[] read(int ireg, int creg)
I2cDeviceSynchSimple
I2cDeviceSynchSimple.readTimeStamped(int, int)
for a
complete description.read
in interface I2cDeviceSynchSimple
ireg
- the register number of the first byte register to readcreg
- the number of bytes / registers to readI2cDeviceSynchSimple.read8(int)
,
I2cDeviceSynchSimple.readTimeStamped(int, int)
,
I2cDeviceSynch.ensureReadWindow(I2cDeviceSynch.ReadWindow, I2cDeviceSynch.ReadWindow)
public TimestampedData readTimeStamped(int ireg, int creg)
I2cDeviceSynchSimple
You can always just call this method without worrying at all about
read windows
,
that will work, but usually it is more efficient to take some thought and care as to what set
of registers the I2C device controller is being set up to read, as adjusting that window
of registers incurs significant extra time.
If the current read window can't be used to read the requested registers, then
a new read window will automatically be created as follows. If the current read window is non
null and wholly contains the registers to read but can't be read because it is a used-up
ReadMode#ONLY_ONCE
window,
a new read fresh window will be created with the same set of registers. Otherwise, a
window that exactly covers the requested set of registers will be created.
readTimeStamped
in interface I2cDeviceSynchSimple
ireg
- the register number of the first byte register to readcreg
- the number of bytes / registers to readI2cDeviceSynchSimple.read(int, int)
,
I2cDeviceSynchSimple.read8(int)
,
I2cDeviceSynch.ensureReadWindow(I2cDeviceSynch.ReadWindow, I2cDeviceSynch.ReadWindow)
protected boolean isOpenForReading()
protected boolean isOpenForWriting()
protected void acquireReaderLockShared() throws java.lang.InterruptedException
java.lang.InterruptedException
protected void releaseReaderLockShared()
public TimestampedData readTimeStamped(int ireg, int creg, I2cDeviceSynch.ReadWindow readWindowNeeded, I2cDeviceSynch.ReadWindow readWindowSet)
I2cDeviceSynch
readTimeStamped
in interface I2cDeviceSynch
ireg
- the register number of the first byte register to readcreg
- the number of bytes / registers to readreadWindowNeeded
- the read window we requirereadWindowSet
- the read window to set if the required read window is not currentI2cDeviceSynch.ensureReadWindow(ReadWindow, ReadWindow)
,
I2cDeviceSynchSimple.readTimeStamped(int, int)
protected boolean readCacheValidityCurrentOrImminent()
protected boolean readCacheIsValid()
public void write8(int ireg, int data)
I2cDeviceSynchSimple
I2cWaitControl.ATOMIC
semantics.write8
in interface I2cDeviceSynchSimple
ireg
- the register number that is to be writtendata
- the byte which is to be written to that registerI2cDeviceSynchSimple.write(int, byte[], I2cWaitControl)
public void write8(int ireg, int data, I2cWaitControl waitControl)
I2cDeviceSynchSimple
I2cDeviceSynchSimple.write(int, byte[], I2cWaitControl)
.write8
in interface I2cDeviceSynchSimple
ireg
- the register number that is to be writtendata
- the byte which is to be written to that registerwaitControl
- controls the behavior of waiting for the completion of the writeI2cDeviceSynchSimple.write(int, byte[], I2cWaitControl)
public void write(int ireg, byte[] data)
I2cDeviceSynchSimple
I2cWaitControl.ATOMIC
semantics.write
in interface I2cDeviceSynchSimple
ireg
- the first of the registers which is to be writtendata
- the data which is to be written to the registersI2cDeviceSynchSimple.write(int, byte[], I2cWaitControl)
public void write(int ireg, byte[] data, I2cWaitControl waitControl)
I2cDeviceSynchSimple
write
in interface I2cDeviceSynchSimple
ireg
- the first of the registers which is to be writtendata
- the data which is to be written to the registerswaitControl
- controls the behavior of waiting for the completion of the writeprotected static byte[] concatenateByteArrays(byte[] left, byte[] right)
public void waitForWriteCompletions(I2cWaitControl waitControl)
I2cDeviceSynchSimple
waitForWriteCompletions
in interface I2cDeviceSynchSimple
waitControl
- controls the behavior of waiting for the completion of the write
Note that a value of I2cWaitControl.NONE
is essentially a no-op.protected byte[] readWriteCache()
protected void waitForWriteCompletionInternal(I2cWaitControl writeControl) throws java.lang.InterruptedException
java.lang.InterruptedException
protected void setWriteCacheStatus(I2cDeviceSynchImpl.WRITE_CACHE_STATUS status)
protected I2cDeviceSynchImpl.WRITE_CACHE_STATUS getWriteCacheStatus()
protected void initWriteCacheStatus(I2cDeviceSynchImpl.WRITE_CACHE_STATUS status)
protected long waitForIdleWriteCache() throws java.lang.InterruptedException
java.lang.InterruptedException
protected void waitForValidReadCache() throws java.lang.InterruptedException
java.lang.InterruptedException
I2cDeviceSynchImpl.WriteCacheStatus.waitForIdle()
public void enableWriteCoalescing(boolean enable)
I2cDeviceSynchSimple
enableWriteCoalescing
in interface I2cDeviceSynchSimple
enable
- whether to enable write coalescing or notI2cDeviceSynchSimple.isWriteCoalescingEnabled()
public boolean isWriteCoalescingEnabled()
I2cDeviceSynchSimple
isWriteCoalescingEnabled
in interface I2cDeviceSynchSimple
I2cDeviceSynchSimple.enableWriteCoalescing(boolean)
public void setUserConfiguredName(java.lang.String name)
RobotConfigNameable
setUserConfiguredName
in interface RobotConfigNameable
public java.lang.String getUserConfiguredName()
RobotConfigNameable
getUserConfiguredName
in interface RobotConfigNameable
RobotConfigNameable.setUserConfiguredName(String)
public void setLogging(boolean enabled)
I2cDeviceSynchSimple
setLogging
in interface I2cDeviceSynchSimple
enabled
- whether to enable logging or notpublic boolean getLogging()
getLogging
in interface I2cDeviceSynchSimple
I2cDeviceSynchSimple.setLogging(boolean)
public void setLoggingTag(java.lang.String loggingTag)
I2cDeviceSynchSimple
setLoggingTag
in interface I2cDeviceSynchSimple
loggingTag
- the logging tag to suepublic java.lang.String getLoggingTag()
getLoggingTag
in interface I2cDeviceSynchSimple
I2cDeviceSynchSimple.setLoggingTag(String)
public int getHeartbeatInterval()
I2cDeviceSynch
getHeartbeatInterval
in interface I2cDeviceSynch
I2cDeviceSynch.setHeartbeatInterval(int)
public void setHeartbeatInterval(int msHeartbeatInterval)
I2cDeviceSynch
setHeartbeatInterval
in interface I2cDeviceSynch
msHeartbeatInterval
- the new hearbeat interval, in millisecondsI2cDeviceSynch.getHeartbeatInterval()
public void setHeartbeatAction(I2cDeviceSynch.HeartbeatAction action)
I2cDeviceSynch
setHeartbeatAction
in interface I2cDeviceSynch
action
- the action to take at each heartbeat.I2cDeviceSynch.getHeartbeatAction()
,
I2cDeviceSynch.setHeartbeatInterval(int)
public I2cDeviceSynch.HeartbeatAction getHeartbeatAction()
I2cDeviceSynch
getHeartbeatAction
in interface I2cDeviceSynch
I2cDeviceSynch.setHeartbeatAction(HeartbeatAction)
protected void log(int verbosity, java.lang.String message)
protected void log(int verbosity, java.lang.String format, java.lang.Object... args)