C++ GRX SDK · Transactions & UndoFiler

Nested Database Transactions, Partial UndoFiler Serialization & Rollback Safety in GRX (dev-cookbook-19)

Architect atomic multi-entity database modifications in GstarCAD 2027 C++ GRX using AcTransactionManager and custom partial undo recording via AcDbDwgFiler.

1. Why `AcTransactionManager` Outperforms Manual `acdbOpenObject()` Chains

In complex engineering operations—such as automatically routing a 40-segment fire-sprinkler branch network and simultaneously updating a summary schedule table—manually calling `acdbOpenObject()` and `close()` across dozens of interdependent entities is error-prone. If a geometric validation fails halfway through the routine, rolling back the initial edits requires tedious cleanup code. GstarCAD 2027's `actrTransactionManager` provides ACID-style nested transactions over the active `AcDbDatabase`.

When you open objects via `actrTransactionManager->getObject()`, you can freely upgrade an object from `AcDb::kForRead` to `AcDb::kForWrite` within the same transaction or nested sub-transaction without closing it first. Calling `abortTransaction()` instantaneously restores the exact pre-transaction binary state of every touched entity via the database's internal undo filer.

2. Custom Partial Undo (`assertWriteEnabled(false)`) for High-Frequency Objects

By default, calling `assertWriteEnabled()` inside a custom `AcDbObject` or `AcDbEntity` triggers a **Full Object Undo Snapshot**—serializing the entire entity via `dwgOutFields()` into the undo stream before your setter modifies a single member variable. If your custom object holds a 50,000-vertex civil TIN surface array (`AcGePoint3dArray`) and the user elevates a single vertex, full-object undo serialization will consume hundreds of megabytes of undo buffer space. Implementing **Partial Undo** via `applyPartialUndo()` reduces undo overhead to just 32 bytes per edit:

#include "grxHeaders.h"
#include "dbfiler.h"

enum TinSurfaceUndoOp : Adesk::Int16 {
    kUndoModifySingleVertex = 101
};

Acad::ErrorStatus GcTinSurfaceEntity::setVertexElevationAt(Adesk::Int32 idx, double newZ) {
    // Pass autoUndo = false so GRX does NOT serialize the entire 50,000-vertex array
    assertWriteEnabled(Adesk::kFalse);

    AcDbDwgFiler* pUndoFiler = undoFiler();
    if (pUndoFiler != nullptr) {
        // Record class descriptor, operation op-code, vertex index, and previous Z value
        pUndoFiler->writeAddress(GcTinSurfaceEntity::desc());
        pUndoFiler->writeInt16(kUndoModifySingleVertex);
        pUndoFiler->writeInt32(idx);
        pUndoFiler->writeDouble(m_vertices[idx].z);
    }

    m_vertices[idx].z = newZ;
    recordGraphicsModified(true);
    return Acad::eOk;
}

Acad::ErrorStatus GcTinSurfaceEntity::applyPartialUndo(
    AcDbDwgFiler* pUndoFiler,
    AcRxClass* pClassObj)
{
    if (pClassObj != GcTinSurfaceEntity::desc()) {
        return AcDbEntity::applyPartialUndo(pUndoFiler, pClassObj);
    }
    Adesk::Int16 opCode = 0;
    pUndoFiler->readInt16(&opCode);

    if (opCode == kUndoModifySingleVertex) {
        Adesk::Int32 idx = 0;
        double oldZ = 0.0;
        pUndoFiler->readInt32(&idx);
        pUndoFiler->readDouble(&oldZ);
        // Call setter during UNDO so Redo stack automatically records the inverse!
        setVertexElevationAt(idx, oldZ);
        return Acad::eOk;
    }
    return Acad::eInvalidInput;
}

3. Transaction Nesting Rules & Memory Benchmarks

When combining `AcTransaction` scopes with custom partial undo filers in GstarCAD 2027, adhere to these three transaction invariants:

  1. Never Call `pObj->close()` on Transaction-Managed Pointers: Objects obtained via `pTrans->getObject()` are owned by the transaction stack; calling `close()` directly corrupts the transaction reference count and triggers `eNotOpenForWrite` during `endTransaction()`.
  2. Newly Appended Entities: When appending a brand-new `AcDbEntity` to Model Space inside a transaction, first add it to the `AcDbBlockTableRecord` via `appendAcDbEntity(objId, pNewEnt)`, and immediately register the pointer with `actrTransactionManager->addNewlyCreatedDBRObject(pNewEnt)`.
  3. Empirical Undo Memory Reduction: On a 100,000-triangle civil grading mesh, switching from default `assertWriteEnabled(true)` to `applyPartialUndo()` reduces per-edit undo stream allocation from **4.8 MB to 38 bytes**—a **99.99% memory reduction** that keeps `UNDO` / `REDO` instantaneous across 500+ interactive grading steps.

4. Diagnosing `eNotOpenForWrite` & `eAtMaxReaders` in Deep Transaction Trees

When debugging complex C++ GRX transaction trees in Visual Studio 2022 attached to `gcad.exe`, watch for two classic status codes returned by `actrTransactionManager->getObject()`:

  • Acad::eWasOpenForNotify: Occurs if you attempt to open an entity for write inside an object reactor callback (`AcDbObjectReactor::modified()`) while the notifying object is still mid-close. Defer secondary modifications to an `AcEditorReactor::commandEnded()` handler.
  • Undo Stream Alignment Errors (`eInvalidInput` in `applyPartialUndo`): Every byte written to `undoFiler()` during `setVertexElevationAt()` must be read back in the exact same sequence and data type inside `applyPartialUndo()`. Leaving unread bytes in the undo filer stream will desynchronize subsequent undo op-codes.

Validate This Workflow in GstarCAD 2027

Engineered by Suzhou Gstarsoft Co., Ltd. (SSE: 688657), GstarCAD 2027 delivers 100% native AC1032 DWG binary fidelity, perpetual licensing sovereignty, and zero-day AutoCAD command parity.

Download Free 30-Day Evaluation → Explore AutoCAD Migration Hub Take Skill Certification Quiz