~ehmry/nim_tox

1f5558ce30799cdac882c07e76bb05c010cbec1e — Emery Hemingway 5 months ago 7e7a2bc master
Manage object lifetime with destructors
5 files changed, 80 insertions(+), 82 deletions(-)

M README.md
M src/toxcore.nim
M src/toxcore/av.nim
M tests/simple.nim
M tests/test1.nim
M README.md => README.md +19 -17
@@ 6,34 6,36 @@ import toxcore
from std/os import sleep

const
  bootstrapHost = "tox.neuland.technology"
  bootstrapKey = "15E9C309CFCB79FDDF0EBA057DABB49FE15F3803B1BFF06536AE2E5BA5E4690E".toPublicKey
  bootstrapHost = "85.143.221.42"
  bootstrapKey = "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43".toPublicKey

proc setupCallbacks(bot: Tox) =
type Bot = ref object
  tox: Tox

proc setupCallbacks(bot: Bot) =
  var echoCount = 0
    # bind a value for callback closure magic

  bot.onFriendRequest do (pk: PublicKey; msg: string):
    discard bot.addFriendNoRequest(pk)
  bot.tox.onFriendRequest do (pk: PublicKey; msg: string):
    discard bot.tox.addFriendNoRequest(pk)

  bot.onFriendMessage do (f: Friend; msg: string; kind: TOX_MESSAGE_TYPE):
    discard bot.send(f, msg, kind)
  bot.tox.onFriendMessage do (f: Friend; msg: string; kind: MessageType):
    discard bot.tox.send(f, msg, kind)
    inc echoCount
    bot.statusMessage = $echoCount & " echos served "
    bot.tox.statusMessage = $echoCount & " echos served "

proc newEchoBot(name: string): Tox =
  result = newTox()
  result.name = name
proc newEchoBot(name: string): Bot =
  result = Bot(tox: initTox())
  result.tox.name = name
  result.setupCallbacks()
  result.bootstrap(bootstrapHost, bootstrapKey)
  echo result.name, " echos messages to ", result.address
  echo result.name, " joins DHT with port ", result.udpPort, " address ", result.dhtId
  result.tox.bootstrap(bootstrapHost, bootstrapKey)
  echo result.tox.name, " echos messages to ", result.tox.address

let
  a = newEchoBot "alice"
  b = newEchoBot "bob"
while true:
  iterate a
  iterate b
  sleep min(a.iterationInterval, b.iterationInterval)
  iterate a.tox
  iterate b.tox
  sleep min(a.tox.iterationInterval, b.tox.iterationInterval)
```

M src/toxcore.nim => src/toxcore.nim +14 -17
@@ 249,7 249,7 @@ type
  CbFriendLosslessPacket* = proc (
    friend: Friend; data: pointer; size: int) {.gcsafe.}

  Tox* = ref object
  Tox* = object
    ## Tox instance.
    core*: Core
    selfConnectionStatus: CbSelfConnectionStatus


@@ 367,7 367,7 @@ type
proc tox_new(options: Options; error: ptr Err_New): Core {.
  importc, ctoxHeader.}

proc newTox*(configure: proc (opt: Options)): Tox =
proc initTox*(configure: proc (opt: Options)): Tox =
  ## Create a new Tox instance.
  ##
  ## .. code-block:: nim


@@ 386,22 386,19 @@ proc newTox*(configure: proc (opt: Options)): Tox =
  options_default(opt)
  configure(opt)
  ctoxCheck(TOX_ERR_NEW_OK) do:
    result = Tox(core: tox_new(opt, err))
    result.core = tox_new(opt, err)

proc newTox*(): Tox =
proc initTox*(): Tox =
  ## Create a new Tox instance.
  ctoxCheck(TOX_ERR_NEW_OK) do:
    result = Tox(core: tox_new(nil, err))
    result.core = tox_new(nil, err)

proc close*(tox: Tox) {.tags: [IOEffect].} =
  ## Shutdown and destroy a Tox instance.
  proc kill(core: Core) {.ctoxProc.}
proc `=destroy`*(tox: var Tox) =
  if not tox.core.pointer.isNil:
    tox.core.kill()
    proc kill(core: Core) {.ctoxProc.}
    kill tox.core
    reset tox.core

func isClosed*(tox: Tox): bool = tox.core.pointer.isNil

proc saveData*(tox: Tox): string =
  ## Retrieve save data of `tox` instance.
  ##


@@ 443,9 440,9 @@ proc addTcpRelay*(tox: Tox; host: string; port: uint16; key: PublicKey) =

template callbackSetter(name, body: untyped) =
  ## Magic for installing Nim closure callbacks into c-toxcore.
  proc `on name`*(tox: Tox; cb: `Cb name`) =
  proc `on name`*(tox: var Tox; cb: `Cb name`) =
    template callThru(args: varargs[untyped]) =
      let tox = cast[Tox](user_data)
      let tox = cast[ptr Tox](user_data)
      if not tox.name.isNil: tox.name(args)
    body
    proc name(core: Core; callback: cbType) {.ctoxCallbackProc.}


@@ 466,7 463,7 @@ proc iteration_interval*(tox: Tox): int =
  proc iteration_interval(core: Core): uint32 {.ctoxProc.}
  (int)tox.core.iteration_interval()

proc iterate*(tox: Tox) {.tags: [IOEffect].} =
proc iterate*(tox: var Tox) {.tags: [IOEffect].} =
  ##  Iterate the Tox main loop.
  ##
  ## .. code-block:: nim


@@ 474,7 471,7 @@ proc iterate*(tox: Tox) {.tags: [IOEffect].} =
  ##     poll(tox.iterationInterval)
  ##     tox.itererate()
  proc iterate(core: Core; user_data: pointer) {.ctoxProc.}
  tox.core.iterate(cast[pointer](tox))
  tox.core.iterate(addr tox)

proc address*(tox: Tox): Address =
  ## Retrieve the current address of `tox` that can be used to initiate


@@ 754,7 751,7 @@ callbackSetter friend_typing:
  proc wrapper(
      core: Core; friend: Friend; is_typing: bool;
      user_data: pointer) {.cdecl.} =
    let tox = cast[Tox](user_data)
    let tox = cast[ptr Tox](user_data)
    if not tox.friendTyping.isNil:
      tox.friendTyping(friend, is_typing)



@@ 798,7 795,7 @@ callbackSetter friend_read_receipt:
  proc wrapper(
      core: Core; friend: Friend;
      message_id: MessageId; user_data: pointer) {.cdecl.} =
    let tox = cast[Tox](user_data)
    let tox = cast[ptr Tox](user_data)
    if not tox.friendReadReceipt.isNil:
      tox.friendReadReceipt(friend, message_id)


M src/toxcore/av.nim => src/toxcore/av.nim +19 -22
@@ 69,7 69,7 @@ type
  AvCore = distinct pointer
    ## Opaque C type wrapped by the `ToxAV` object.

  ToxAV* = ref object
  ToxAv* = object
    ## Tox A/V instance
    core: AvCore
    call: CbCall


@@ 82,15 82,15 @@ type

template callbackSetter(name, body: untyped) =
  ## Magic for installing Nim closure callbacks into c-toxcore.
  proc `on name`*(av: ToxAV; cb: `Cb name`) =
  proc `on name`*(av: var ToxAV; cb: `Cb name`) =
    template callThru(args: varargs[untyped]) =
      let av = cast[ToxAV](user_data)
      let av = cast[ptr ToxAV](user_data)
      if not av.name.isNil: av.name(args)
    body
    proc name(core: AvCore; callback: cbType; userData: ToxAV) {.ctoxCallbackProc.}
    proc name(core: AvCore; callback: cbType; userData: ptr ToxAV) {.ctoxCallbackProc.}
    av.name = cb
    if cb.isNil: `name`(av.core, nil, nil)
    else: `name`(av.core, wrapper, av)
    else: `name`(av.core, wrapper, addr av)

callbackSetter call:
  type cbType = proc (


@@ 139,7 139,7 @@ callbackSetter video_receive_frame:
      ystride, ustride, vstride: int32; userData: pointer) {.cdecl.} =
    callThru(friend, width, height, y, u, v, ystride, ustride, vstride)

proc onGroupAudio*(av: ToxAv; callback: CbGroupAudio) =
proc onGroupAudio*(av: var ToxAv; callback: CbGroupAudio) =
  av.groupAudio = callback

type Err_New* {.ctoxError.} = enum


@@ 149,27 149,24 @@ type Err_New* {.ctoxError.} = enum
    ## One of the arguments to the function was NULL when it was not expected.
  TOXAV_ERR_NEW_MALLOC,
    ## Memory allocation failure while trying to allocate structures required
    ## for the A/V session.
      ## for the A/V session.
  TOXAV_ERR_NEW_MULTIPLE
    ## Attempted to create a second session for the same Tox instance.

proc newAv*(tox: Tox): ToxAV =
proc initAv*(tox: Tox): ToxAV =
  ## Start new A/V session. There can only be only one session per Tox instance.
  proc toxav_new(tox: Core; error: ptr Err_New): AvCore {.
    importc, ctoxHeader.}
  ctoxCheck(TOXAV_ERR_NEW_OK) do:
    result = ToxAv(core: toxav_new(tox.core, err))
    result.core = toxav_new(tox.core, err)

proc kill*(av: ToxAV) =
  proc kill(av: AvCore) {.ctoxProc.}
  kill(av.core)
  ## Releases all resources associated with the A/V session.
  ##
  ## If any calls were ongoing, these will be forcibly terminated without
  ## notifying peers. After calling this function, no other functions may be
  ## called and the av pointer becomes invalid.
proc `=destroy`*(av: var ToxAV) =
  if not av.core.pointer.isNil:
    proc kill(av: AvCore) {.ctoxProc.}
    kill av.core
    reset av.core

proc toxav_get_tox(av: AvCore): Core {.ctoxProc.}
proc get_tox(av: AvCore): Core {.ctoxProc.}
  ## Returns the Tox instance the A/V object was created for.

proc iterationInterval*(av: ToxAV): int =


@@ 178,7 175,7 @@ proc iterationInterval*(av: ToxAV): int =
  proc iteration_interval(av: AvCore): uint32 {.ctoxProc.}
  (int)iterationInterval(av.core)

proc iterate*(av: ToxAV) =
proc iterate*(av: var ToxAV) =
  ## Main loop for the session. This function needs to be called in intervals of
  ## ``iterationInterval`` milliseconds. It is best called in the separate
  ## thread from tox_iterate.


@@ 390,7 387,7 @@ proc audioSetBitRate*(av: ToxAv; friend: Friend; bitRate: uint32): bool =


proc videoSendFrame*(
    av: ptr ToxAV; friend: Friend;
    av: ToxAV; friend: Friend;
    width, height: uint16; y, u, v: ptr uint8): bool =
  ## Send a video frame to a friend.
  ##


@@ 425,7 422,7 @@ proc addAvGroupchat*(av: var ToxAv): Conference =
      userData: pointer): cint {.ctoxProc.}
  proc wrapper(tox: Core; group: Conference; peer: Peer; pcm: ptr int16;
      count: cuint; channels: uint8; rate: uint32; userData: pointer) {.cdecl.} =
    let av = cast[ToxAv](userData)
    let av = cast[ptr ToxAv](userData)
    if not av.groupAudio.isNil:
      av.groupAudio(group, peer, cast[Samples](pcm), (int)count, channels, rate)
  let res = add_av_groupchat(getTox(av.core), wrapper, addr av)


@@ 441,7 438,7 @@ proc joinAvGroupchat*(av: var ToxAv; friend: Friend;
      userData: pointer): cint {.ctoxProc.}
  proc wrapper(tox: Core; group: Conference; peer: Peer; pcm: ptr int16;
      count: cuint; channels: uint8; rate: uint32; userData: pointer) {.cdecl.} =
    let av = cast[ToxAv](userData)
    let av = cast[ptr ToxAv](userData)
    if not av.groupAudio.isNil:
      av.groupAudio(group, peer, cast[Samples](pcm), (int)count, channels, rate)
  let res = join_av_groupchat(

M tests/simple.nim => tests/simple.nim +21 -17
@@ 3,33 3,37 @@ import toxcore
from std/os import sleep

const
  bootstrapHost = "tox.neuland.technology"
  bootstrapKey = "15E9C309CFCB79FDDF0EBA057DABB49FE15F3803B1BFF06536AE2E5BA5E4690E".toPublicKey
  bootstrapHost = "85.143.221.42"
  bootstrapKey = "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43".toPublicKey

proc setupCallbacks(bot: Tox) =
type Bot = ref object
  tox: Tox

proc setupCallbacks(bot: Bot) =
  var echoCount = 0
    # bind a value for callback closure magic

  bot.onFriendRequest do (pk: PublicKey; msg: string):
    discard bot.addFriendNoRequest(pk)
  bot.tox.onFriendRequest do (pk: PublicKey; msg: string):
    discard bot.tox.addFriendNoRequest(pk)

  bot.onFriendMessage do (f: Friend; msg: string; kind: MessageType):
    discard bot.send(f, msg, kind)
  bot.tox.onFriendMessage do (f: Friend; msg: string; kind: MessageType):
    discard bot.tox.send(f, msg, kind)
    inc echoCount
    bot.statusMessage = $echoCount & " echos served "
    bot.tox.statusMessage = $echoCount & " echos served "

proc newEchoBot(name: string): Tox =
  result = newTox()
  result.name = name
proc newEchoBot(name: string): Bot =
  result = Bot(tox: initTox())
  result.tox.name = name
  result.setupCallbacks()
  result.bootstrap(bootstrapHost, bootstrapKey)
  echo result.name, " echos messages to ", result.address
  echo result.name, " joins DHT with port ", result.udpPort, " address ", result.dhtId
  result.tox.bootstrap(bootstrapHost, bootstrapKey)
  echo result.tox.name, " echos messages to ", result.tox.address
  echo result.tox.name, " joins DHT with port ", result.tox.udpPort,
      " address ", result.tox.dhtId

let
  a = newEchoBot "alice"
  b = newEchoBot "bob"
while true:
  iterate a
  iterate b
  sleep min(a.iterationInterval, b.iterationInterval)
  iterate a.tox
  iterate b.tox
  sleep min(a.tox.iterationInterval, b.tox.iterationInterval)

M tests/test1.nim => tests/test1.nim +7 -9
@@ 7,16 7,14 @@ suite "Tox Core":
    check versionIsCompatible()

  test "newTox":
    let tox = newTox()
    check(tox.address.isValid)
    iterate tox
    close tox
    var tox = initTox()
    check tox.address.isValid

suite "Tox AV":

  test "newAv":
    let
      tox = newTox()
      av = newAv(tox)
  test "initAv":
    var
      tox = initTox()
      av = initAv(tox)
    iterate tox
    iterate av
    kill av