######################### # CODE INCLUDED BY BOTH # COMPILATION UNITS. # Trait definition type DrawableVTable* = tuple[ drawType: int, preDraw: proc(self: ref TObject): bool {.nimcall.}, draw: proc(self: ref TObject) {.nimcall.} ] # Fat pointer: (object with type erased, pointer to v-table). type Drawable* = tuple[self: ref TObject, vtable: ptr DrawableVTable] # Convenience functions for the trait. proc drawType*(d: Drawable): int {.inline.} = result = d.vtable.drawType proc preDraw*(d: Drawable): bool {.inline.} = result = d.vtable.preDraw(d.self) proc draw*(d: Drawable) {.inline.} = d.vtable.draw(d.self) ######################### # COMPILATION UNIT 1 # (dynamic library) # Main library with general-purpose drawObject # function which uses all drawable types. type DrawingEngine* = object objs : seq[Drawable] # Function using trait. proc drawObjects(engine: DrawingEngine) = for obj in engine.objs: echo("----") if obj.preDraw(): echo("drawType = " & $obj.drawType) obj.draw() proc addDrawable*(engine: var DrawingEngine, obj: Drawable) = engine.objs.add(obj) # Global engine. Should also be exposed through a trait. var engine = DrawingEngine(objs: @[]) ######################### # COMPILATION UNIT 2 # 3-party plugin # (dynamic library) import strutils # Object implementing that trait type Circle = object x, y, r : float proc preDrawC(self: ref TObject): bool {.procvar.} = # Some data-ready check. let circle = cast[ref Circle](self) echo("Pre-draw for circle (x: $1, y: $2, r: $3)") result = circle.r != 0 proc drawC(self: ref TObject) {.procvar.} = let circle = cast[ref Circle](self) echo(format("Drawing circle (x: $1, y: $2, r: $3)", circle.x, circle.y, circle.r)) proc toDrawable(circle: ref Circle): Drawable = # Global vtable for Circle's Drawable implementation. var CircleVTable {.global.} = DrawableVTable(( drawType: 5, preDraw: preDrawC, draw: drawC)) result = (cast[ref TObject](circle), addr CircleVTable) proc newCircle(x, y, r: float): ref Circle = result.new result.x = x result.y = y result.r = r # The drawing engine would be exposed. var circle1 = newCircle(0, 0, 1) var circle2 = newCircle(0, 0, 0) engine.addDrawable(circle1.toDrawable()) engine.addDrawable(circle2.toDrawable()) ######################### # This would be called eventually within the engine itself. engine.drawObjects()