Wrap caching FFI functions in Swift library.
`CacheDirection` enum is used only on the Swift side to provide a usable interface. FFI calls are more explicit. Tests ensure that a cached query is faster than the uncached one.
This commit is contained in:
parent
dddcd1c17f
commit
312d3bf4e0
4 changed files with 107 additions and 2 deletions
|
@ -29,6 +29,12 @@ protocol Observable {
|
||||||
func unregister(key: String)
|
func unregister(key: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum CacheDirection {
|
||||||
|
case forward;
|
||||||
|
case reverse;
|
||||||
|
case both;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The primary class for accessing Mentat's API.
|
The primary class for accessing Mentat's API.
|
||||||
This class provides all of the basic API that can be found in Mentat's Store struct.
|
This class provides all of the basic API that can be found in Mentat's Store struct.
|
||||||
|
@ -56,11 +62,36 @@ class Mentat: RustObject {
|
||||||
self.init(raw: store_open(storeURI))
|
self.init(raw: store_open(storeURI))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Add an attribute to the cache. The {@link CacheDirection} determines how that attribute can be
|
||||||
|
looked up.
|
||||||
|
|
||||||
|
- Parameter attribute: The attribute to cache
|
||||||
|
- Parameter direction: The direction the attribute should be keyed.
|
||||||
|
`FORWARD` caches values for an attribute keyed by entity
|
||||||
|
(i.e. find values and entities that have this attribute, or find values of attribute for an entity)
|
||||||
|
`REVERSE` caches entities for an attribute keyed by value.
|
||||||
|
(i.e. find entities that have a particular value for an attribute).
|
||||||
|
`BOTH` adds an attribute such that it is cached in both directions.
|
||||||
|
|
||||||
|
- Throws: `ResultError.error` if an error occured while trying to cache the attribute.
|
||||||
|
*/
|
||||||
|
func cache(attribute: String, direction: CacheDirection) throws {
|
||||||
|
switch direction {
|
||||||
|
case .forward:
|
||||||
|
try store_cache_attribute_forward(self.raw, attribute).pointee.tryUnwrap()
|
||||||
|
case .reverse:
|
||||||
|
try store_cache_attribute_reverse(self.raw, attribute).pointee.tryUnwrap()
|
||||||
|
case .both:
|
||||||
|
try store_cache_attribute_bi_directional(self.raw, attribute).pointee.tryUnwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Simple transact of an EDN string.
|
Simple transact of an EDN string.
|
||||||
- Parameter transaction: The string, as EDN, to be transacted
|
- Parameter transaction: The string, as EDN, to be transacted
|
||||||
|
|
||||||
- Throws: `MentatError` if the an error occured during the transaction, or the TxReport is nil.
|
- Throws: `ResultError.error` if the an error occured during the transaction, or the TxReport is nil.
|
||||||
|
|
||||||
- Returns: The `TxReport` of the completed transaction
|
- Returns: The `TxReport` of the completed transaction
|
||||||
*/
|
*/
|
||||||
|
@ -99,6 +130,8 @@ class Mentat: RustObject {
|
||||||
Creates a new transaction (`InProgress`) and returns an `EntityBuilder` for the entity with `entid`
|
Creates a new transaction (`InProgress`) and returns an `EntityBuilder` for the entity with `entid`
|
||||||
for that transaction.
|
for that transaction.
|
||||||
|
|
||||||
|
- Parameter entid: The `Entid` for this entity.
|
||||||
|
|
||||||
- Throws: `ResultError.error` if the creation of the transaction fails.
|
- Throws: `ResultError.error` if the creation of the transaction fails.
|
||||||
- Throws: `ResultError.empty` if no `EntityBuilder` is created.
|
- Throws: `ResultError.empty` if no `EntityBuilder` is created.
|
||||||
|
|
||||||
|
@ -113,6 +146,8 @@ class Mentat: RustObject {
|
||||||
Creates a new transaction (`InProgress`) and returns an `EntityBuilder` for a new entity with `tempId`
|
Creates a new transaction (`InProgress`) and returns an `EntityBuilder` for a new entity with `tempId`
|
||||||
for that transaction.
|
for that transaction.
|
||||||
|
|
||||||
|
- Parameter tempId: The temporary identifier for this entity.
|
||||||
|
|
||||||
- Throws: `ResultError.error` if the creation of the transaction fails.
|
- Throws: `ResultError.error` if the creation of the transaction fails.
|
||||||
- Throws: `ResultError.empty` if no `EntityBuilder` is created.
|
- Throws: `ResultError.empty` if no `EntityBuilder` is created.
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ class InProgress: OptionalRustObject {
|
||||||
/**
|
/**
|
||||||
Creates an `EntityBuilder` using this `InProgress` for the entity with `entid`.
|
Creates an `EntityBuilder` using this `InProgress` for the entity with `entid`.
|
||||||
|
|
||||||
- Parameter tempId: The `Entid` for this entity.
|
- Parameter entid: The `Entid` for this entity.
|
||||||
|
|
||||||
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the `InProgress`
|
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the `InProgress`
|
||||||
has already been committed, or converted into a Builder.
|
has already been committed, or converted into a Builder.
|
||||||
|
|
|
@ -114,6 +114,11 @@ void in_progress_destroy(struct InProgress* _Nullable obj);
|
||||||
void in_progress_builder_destroy(struct InProgressBuilder* _Nullable obj);
|
void in_progress_builder_destroy(struct InProgressBuilder* _Nullable obj);
|
||||||
void entity_builder_destroy(struct EntityBuilder* _Nullable obj);
|
void entity_builder_destroy(struct EntityBuilder* _Nullable obj);
|
||||||
|
|
||||||
|
// caching
|
||||||
|
struct Result*_Nonnull store_cache_attribute_forward(struct Store*_Nonnull store, const char* _Nonnull attribute);
|
||||||
|
struct Result*_Nonnull store_cache_attribute_reverse(struct Store*_Nonnull store, const char* _Nonnull attribute);
|
||||||
|
struct Result*_Nonnull store_cache_attribute_bi_directional(struct Store*_Nonnull store, const char* _Nonnull attribute);
|
||||||
|
|
||||||
// transact
|
// transact
|
||||||
struct Result*_Nonnull store_transact(struct Store*_Nonnull store, const char* _Nonnull transaction);
|
struct Result*_Nonnull store_transact(struct Store*_Nonnull store, const char* _Nonnull transaction);
|
||||||
const int64_t* _Nullable tx_report_entity_for_temp_id(const struct TxReport* _Nonnull report, const char* _Nonnull tempid);
|
const int64_t* _Nullable tx_report_entity_for_temp_id(const struct TxReport* _Nonnull report, const char* _Nonnull tempid);
|
||||||
|
|
|
@ -1175,5 +1175,70 @@ class MentatTests: XCTestCase {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCaching() {
|
||||||
|
let query = """
|
||||||
|
[:find ?district :where
|
||||||
|
[?neighborhood :neighborhood/name \"Beacon Hill\"]
|
||||||
|
[?neighborhood :neighborhood/district ?d]
|
||||||
|
[?d :district/name ?district]]
|
||||||
|
"""
|
||||||
|
|
||||||
|
let mentat = openAndInitializeCitiesStore()
|
||||||
|
|
||||||
|
struct QueryTimer {
|
||||||
|
private var _start: UInt64
|
||||||
|
private var _end: UInt64
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self._start = 0
|
||||||
|
self._end = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private func currentTimeNanos() -> UInt64 {
|
||||||
|
var info = mach_timebase_info()
|
||||||
|
guard mach_timebase_info(&info) == KERN_SUCCESS else { return 0 }
|
||||||
|
let currentTime = mach_absolute_time()
|
||||||
|
return currentTime * UInt64(info.numer) / UInt64(info.denom)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mutating func start() {
|
||||||
|
self._start = self.currentTimeNanos()
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func end() {
|
||||||
|
self._end = self.currentTimeNanos()
|
||||||
|
}
|
||||||
|
|
||||||
|
func duration() -> UInt64 {
|
||||||
|
return self._end - self._start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var uncachedTimer = QueryTimer()
|
||||||
|
uncachedTimer.start()
|
||||||
|
|
||||||
|
XCTAssertNoThrow(try mentat.query(query: query).run { (result) in
|
||||||
|
uncachedTimer.end()
|
||||||
|
XCTAssertNotNil(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
XCTAssertNoThrow(try mentat.cache(attribute: ":neighborhood/name", direction: CacheDirection.reverse))
|
||||||
|
XCTAssertNoThrow(try mentat.cache(attribute: ":neighborhood/district", direction: CacheDirection.forward))
|
||||||
|
|
||||||
|
var cachedTimer = QueryTimer()
|
||||||
|
cachedTimer.start()
|
||||||
|
|
||||||
|
XCTAssertNoThrow(try mentat.query(query: query).run { (result) in
|
||||||
|
cachedTimer.end()
|
||||||
|
XCTAssertNotNil(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
let timingDifference = uncachedTimer.duration() - cachedTimer.duration()
|
||||||
|
print("Cached query is \(timingDifference) nanoseconds faster than the uncached query")
|
||||||
|
|
||||||
|
XCTAssertLessThan(cachedTimer.duration(), uncachedTimer.duration())
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Add tests for transaction observation
|
// TODO: Add tests for transaction observation
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue