Skip to content

Commit

Permalink
Merge pull request #57 from JuliaRobotics/tk/fix-decode-uint8-vector
Browse files Browse the repository at this point in the history
Fix #56, decode breakage after UnsafeArrays change.
  • Loading branch information
tkoolen authored Nov 19, 2018
2 parents 71bf886 + c1c23d7 commit 9cca4bf
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 154 deletions.
2 changes: 1 addition & 1 deletion REQUIRE
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ BinDeps 0.8
@osx Homebrew 0.3.0
CMakeWrapper 0.2
StaticArrays 0.5
FastIOBuffers 0.0.1
FastIOBuffers 0.2.0
UnsafeArrays 0.2.0
6 changes: 3 additions & 3 deletions src/lcmtype.jl
Original file line number Diff line number Diff line change
Expand Up @@ -348,11 +348,11 @@ function encodefield(io::IO, A::AbstractArray)
end

# Sugar
encode(data::Vector{UInt8}, x::LCMType) = encode(FastWriteBuffer(data), x)
encode(data::AbstractVector{UInt8}, x::LCMType) = encode(FastWriteBuffer(data), x)
encode(x::LCMType) = (stream = FastWriteBuffer(); encode(stream, x); flush(stream); take!(stream))

decode!(x::LCMType, data::Vector{UInt8}) = decode!(x, FastReadBuffer(data))
decode(data::Vector{UInt8}, ::Type{T}) where {T<:LCMType} = decode!(T(), data)
decode!(x::LCMType, data::AbstractVector{UInt8}) = decode!(x, FastReadBuffer(data))
decode(data::AbstractVector{UInt8}, ::Type{T}) where {T<:LCMType} = decode!(T(), data)

# @lcmtypesetup macro and related functions
"""
Expand Down
100 changes: 46 additions & 54 deletions src/readlog.jl
Original file line number Diff line number Diff line change
@@ -1,108 +1,100 @@
# Read LCM log files directly

mutable struct lcm_eventlog_t
f::Ptr{Cvoid}
eventcount::Int64 #Clonglong
f::Ptr{Cvoid}
eventcount::Int64 #Clonglong
end


struct lcm_eventlog_event_t
eventnum::Int64 #Clonglong
timestamp::Int64 #Clonglong
channellen::Int32 #Cint
datalen::Int32 #Cint
channel::Ptr{Cuchar}
data::Ptr{Cuchar}
eventnum::Int64 #Clonglong
timestamp::Int64 #Clonglong
channellen::Int32 #Cint
datalen::Int32 #Cint
channel::Ptr{Cuchar}
data::Ptr{Cuchar}
end


function lcm_eventlog_create(file::S where S <: AbstractString)
ccall(
ccall(
(:lcm_eventlog_create, LCMCore.liblcm),
Ptr{lcm_eventlog_t},
(Cstring, Cstring),
file, "r"
)
)
end


function lcm_eventlog_destroy(_log::Ptr{lcm_eventlog_t})
ccall(
ccall(
(:lcm_eventlog_destroy, LCMCore.liblcm),
Cvoid,
(Ptr{lcm_eventlog_t}, ),
_log
)
)
end


function lcm_eventlog_read_next_event(_log::Ptr{lcm_eventlog_t})
ccall(
(:lcm_eventlog_read_next_event, LCMCore.liblcm),
Ptr{lcm_eventlog_event_t},
(Ptr{lcm_eventlog_t},),
_log
(:lcm_eventlog_read_next_event, LCMCore.liblcm),
Ptr{lcm_eventlog_event_t},
(Ptr{lcm_eventlog_t},),
_log
)
end

#
# Doesn't work yet
# event = unsafe_wrap(lcm_eventlog_event_t, _event, dim??)



mutable struct LCMLog
_log::Ptr{lcm_eventlog_t}
subscriptions::Dict{AbstractString, LCMCore.SubscriptionOptions}
function LCMLog(filename::S) where {S <: AbstractString}
_log = lcm_eventlog_create(filename)
if !isgood(_log)
throw(ArgumentError("Cannot open the LCM log file at: $filename"))
_log::Ptr{lcm_eventlog_t}
subscriptions::Dict{AbstractString, LCMCore.SubscriptionOptions}
function LCMLog(filename::S) where {S <: AbstractString}
_log = lcm_eventlog_create(filename)
if !isgood(_log)
throw(ArgumentError("Cannot open the LCM log file at: $filename"))
end
return new(_log, Dict{AbstractString, LCMCore.SubscriptionOptions}())
end
return new(_log, Dict{AbstractString, LCMCore.SubscriptionOptions}())
end
end



function close(lcmlog::LCMLog)
if isgood(lcmlog)
lcm_eventlog_destroy(lcmlog._log)
end
if isgood(lcmlog)
lcm_eventlog_destroy(lcmlog._log)
end
end

isgood(_log::Ptr{lcm_eventlog_t}) = _log != C_NULL
isgood(lcmlog::LCMLog) = isgood(lcmlog._log)
isgood(_event::Ptr{lcm_eventlog_event_t}) = _event != C_NULL

function read_next_event(lcmlog::LCMLog)::Union{Nothing, lcm_eventlog_event_t}
_event = lcm_eventlog_read_next_event(lcmlog._log)
if isgood(_event)
return unsafe_load(_event)
end
nothing
_event = lcm_eventlog_read_next_event(lcmlog._log)
if isgood(_event)
return unsafe_load(_event)
end
nothing
end

function handle(lcmlog::LCMLog)::Bool
# do memory copy and then check for subscribed channel -- This is not efficient, but easiest to do. TBD is `unsafe_wrap` to avoid memory copy of _event
event = read_next_event(lcmlog)
if event != nothing
# need a convert from lcm_eventlog_event_t to (RecvBuf, channelbytes, chnlen)
rb = LCMCore.RecvBuf(event.data, UInt32(event.datalen), event.timestamp, 0)
# need a LCMCore.SubscriptionOptions{T}
chn = unsafe_string(event.channel, event.channellen)
if haskey(lcmlog.subscriptions, chn)
opts = lcmlog.subscriptions[chn]
# use onresponse similar to regular live LCM traffic
LCMCore.onresponse(rb, event.channel, opts)
end
return true
# need a convert from lcm_eventlog_event_t to (RecvBuf, channelbytes, chnlen)
rb = LCMCore.RecvBuf(event.data, UInt32(event.datalen), event.timestamp, 0)
# need a LCMCore.SubscriptionOptions{T}
chn = unsafe_string(event.channel, event.channellen)
if haskey(lcmlog.subscriptions, chn)
opts = lcmlog.subscriptions[chn]
# use onresponse similar to regular live LCM traffic
LCMCore.onresponse(rb, event.channel, opts)
end
return true
end
return false
end

function subscribe(lcmlog::LCMLog, channel::S, callback::F, msgtype=Nothing) where {S <: AbstractString, F <: Function}
opts = SubscriptionOptions(msgtype, callback, channel)
lcmlog.subscriptions[channel] = opts
nothing
function subscribe(lcmlog::LCMLog, channel::S, callback::F, msgtype=Nothing) where {S <: AbstractString, F}
opts = SubscriptionOptions(msgtype, callback, channel)
lcmlog.subscriptions[channel] = opts
nothing
end
16 changes: 16 additions & 0 deletions test/mymessage.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
mutable struct MyMessage
field1::Int32
field2::Float64
end

function encode(msg::MyMessage)
buf = IOBuffer()
write(buf, hton(msg.field1))
write(buf, hton(msg.field2))
buf.data
end

function decode(data, msg::Type{MyMessage})
buf = IOBuffer(data)
MyMessage(ntoh(read(buf, Int32)), ntoh(read(buf, Float64)))
end
20 changes: 2 additions & 18 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ using Dates: Second, Millisecond
using FileWatching: poll_fd
import LCMCore: encode, decode

include("mymessage.jl")

@testset "close multiple times" begin
lcm = LCM()
close(lcm)
Expand Down Expand Up @@ -57,23 +59,6 @@ end
publish(lcm, channel, data)
end

mutable struct MyMessage
field1::Int32
field2::Float64
end

function encode(msg::MyMessage)
buf = IOBuffer()
write(buf, hton(msg.field1))
write(buf, hton(msg.field2))
buf.data
end

function decode(data, msg::Type{MyMessage})
buf = IOBuffer(data)
MyMessage(ntoh(read(buf, Int32)), ntoh(read(buf, Float64)))
end

@testset "encode and decode" begin
lcm = LCM()
msg = MyMessage(23, 1.234)
Expand Down Expand Up @@ -237,4 +222,3 @@ end

include("test_lcmtype.jl")
include("test_readlog.jl")

22 changes: 22 additions & 0 deletions test/test_lcmtype.jl
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,25 @@ end
@test d === lcmt2.d
@test f === lcmt2.f
end

@testset "LCMType: handle" begin
channel = "CHANNEL_1"
msg = rand(lcm_test_type_1)

# start listening
sublcm = LCM()
check_msg = let expected = msg
(channel, msg) -> @test(msg == expected)
end
sub = subscribe(sublcm, channel, check_msg, lcm_test_type_1)
set_queue_capacity(sub, 2)

# publish two messages
publcm = LCM()
for _ = 1 : 2
publish(publcm, channel, msg)
end

# handle
LCMCore.lcm_handle(sublcm)
end
103 changes: 25 additions & 78 deletions test/test_readlog.jl
Original file line number Diff line number Diff line change
@@ -1,88 +1,38 @@
# testing ccall of LCM log file handling

## Not repeating the definitions from earlier in the runtests.jl file
# using Base: Test
# using LCMCore
#
# import LCMCore: encode, decode
#
# mutable struct MyMessage
# field1::Int32
# field2::Float64
# end
#
# function encode(msg::MyMessage)
# buf = IOBuffer()
# write(buf, hton(msg.field1))
# write(buf, hton(msg.field2))
# buf.data
# end
#
# function decode(data, msg::Type{MyMessage})
# buf = IOBuffer(data)
# MyMessage(ntoh(read(buf, Int32)), ntoh(read(buf, Float64)))
# end

compare(a::MyMessage, b::MyMessage; tol::Float64=1e-14) = a.field1 == b.field1 && abs(a.field2 - b.field2) < tol

function handledata(channel, msgdata, groundtruth)
msg = decode(msgdata, MyMessage)
@test compare(groundtruth, msg)
nothing
end

function handletype(channel, msg, groundtruth)
@test compare(groundtruth, msg)
nothing
function compare(a::MyMessage, b::MyMessage; tol::Float64=1e-14)
a.field1 == b.field1 && isapprox(a.field2, b.field2, atol=tol)
end

function handlefile(lcl; N=1)
for i in 1:N
handle(lcl) ? nothing : break
end
nothing
function handle_file(lcmlog::LCMLog; N=1)
for i in 1 : N
handle(lcmlog) || break
end
nothing
end

function foroverrun()
# record a temporary log file
lcmlogdir = joinpath(dirname(@__FILE__),"testdata","testlog.lcm")
function make_test_lcmlog()
# record a temporary log file
lcmlogdir = joinpath(@__DIR__, "testdata", "testlog.lcm")

# recreate the messages locally for comparison with those in the test log file
msg1 = MyMessage(23, 1.234)
msg2 = MyMessage(24, 2.345)
# recreate the messages locally for comparison with those in the test log file
msg1 = MyMessage(23, 1.234)
msg2 = MyMessage(24, 2.345)

lc = LCMLog(lcmlogdir)
subscribe(lc, "CHANNEL_1", (c, d) -> handledata(c, d, msg1) )
subscribe(lc, "CHANNEL_2", (c, m) -> handletype(c, m, msg2), MyMessage)
# Consume the log file
handlefile(lc, N=100)
@test true
close(lc)
nothing
lcmlog = LCMLog(lcmlogdir)
subscribe(lcmlog, "CHANNEL_1", (channel, data) -> @test(compare(decode(data, MyMessage), msg1)))
subscribe(lcmlog, "CHANNEL_2", (channel, msg) -> @test(compare(msg, msg2)), MyMessage)
lcmlog
end

function whilestyle()
# record a temporary log file
lcmlogdir = joinpath(dirname(@__FILE__),"testdata","testlog.lcm")

# recreate the messages locally for comparison with those in the test log file
msg1 = MyMessage(23, 1.234)
msg2 = MyMessage(24, 2.345)

lc = LCMLog(lcmlogdir)
subscribe(lc, "CHANNEL_1", (c, d) -> handledata(c, d, msg1) )
subscribe(lc, "CHANNEL_2", (c, m) -> handletype(c, m, msg2), MyMessage)
# Consume the log file
while handle(lc); end
@test true
close(lc)
nothing
@testset "LCM log overrun" begin
lcmlog = make_test_lcmlog()
handle_file(lcmlog, N=100)
close(lcmlog)
end

@testset "reading LCM log directly" begin
foroverrun()
whilestyle()
@test_throws ArgumentError LCMLog("doesnt.exs")
@testset "Read until end" begin
lcmlog = make_test_lcmlog()
while handle(lcmlog); end
close(lcmlog)
end

## Code used to create the testlog.lcm LCM log file used in this test
Expand All @@ -94,6 +44,3 @@ end
# publish(lcm, "CHANNEL_2", msg2)
# # terminate lcm-logger
# close(lcm)


#

0 comments on commit 9cca4bf

Please sign in to comment.