-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add asynchronous processing option via coroutines #6
Conversation
coroutine.yield() is not preferred solution. I would like a DecompressBegin/DecompressContinue/DecompressEnd style API. |
I agree coroutine is an easy way. |
examples/example.lua
Outdated
@@ -92,6 +92,39 @@ local decompress_deflate_with_level = LibDeflate:DecompressDeflate( | |||
compress_deflate_with_level) | |||
assert(decompress_deflate_with_level == example_input) | |||
|
|||
-- Compress with async coroutine, within World of Warcraft |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your changes to examples/example.lua does not work outside World of Warcraft.
You need to ensure WoW specific code does not run out of WoW.
This is not a WoW specific library
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no need to use WoW API here.
Just use coroutine as an example
@@ -2732,13 +2740,14 @@ end | |||
-- If the decompression fails (The first return value of this function is nil), | |||
-- this return value is undefined. | |||
-- @see LibDeflate:CompressDeflate | |||
function LibDeflate:DecompressDeflate(str) | |||
local arg_valid, arg_err = IsValidArguments(str) | |||
function LibDeflate:DecompressDeflate(str, configs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add some tests in tests/Test.lua, which should covers all new code in LibDeflate.lua
LibDeflate.lua
Outdated
local arg_valid, arg_err = IsValidArguments(str) | ||
function LibDeflate:DecompressDeflate(str, configs) | ||
if not configs then configs = {} end | ||
local arg_valid, arg_err = IsValidArguments(str, false, nil, true, configs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not reuse "IsValidArguments", which is for Compress
For completeness, add coroutine support for all Compress/Decompress APIs |
I guess this is for WeakAuras during Import? |
WeakAuras might use it but I primarily made these changes for a WagoLib I'm making to easily let any addon add Wago.io support.
This isn't exactly comprehensive but here are in-game results with a fairly large 3.5MB table test: AsyncSerialize Time: 0.931 seconds SyncAll one frame so GetTime() does not update, but with a stopwatch I timed the combined serialize+compress process to 15.17 seconds. |
@oratory if you want to measure performance of the synchronous version, take a look at |
@oratory I plan to make a new version this week to merge your change. Need to add some tests for code coverage |
Great I'll make some updates and the tests today or tomorrow. |
I've cleaned up the lib and removed the wow-specific stuff. Also added in a handful async tests and all are working. |
|
LibDeflate.lua
Outdated
|
||
local isWoW = function() | ||
return CreateFrame and true or false | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove added WoW specific codes
Please help to test if "async" option significantly decrease compression/decompression speed, in WoW. |
Will change tab indentation to 4space indentation later. P.S. This lib uses tab indent because WoW official Lua code uses tab when this lib first releases |
LibDeflate.lua
Outdated
end | ||
return CompressZlibInternal(str, dictionary, configs, callback or true) | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this function used to?
If you add new public API, modify |
LibDeflate.lua
Outdated
@@ -105,7 +105,7 @@ do | |||
-- 1 : v1.0.0 | |||
-- 2 : v1.0.1 | |||
-- 3 : v1.0.2 | |||
local _MINOR = 3 | |||
local _MINOR = 4 | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not modify version in PR. I will handle this
LibDeflate.lua
Outdated
local total_bitlen, result = FlushWriter(_FLUSH_MODE_OUTPUT) | ||
local padding_bitlen = (8-total_bitlen%8)%8 | ||
return result, padding_bitlen | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try to remove duplicate code
LibDeflate.lua
Outdated
local bitlen_left = state.ReaderBitlenLeft() | ||
local bytelen_left = (bitlen_left - bitlen_left % 8) / 8 | ||
return result, bytelen_left | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try to remove duplicate code
Will do. I think a "max_time_spent_per_chunk" might be better in this case but I suppose I could add both options. |
I don't like max_time_spent_per_chunk, which involves time measurement, which is not stable. |
local decompress_resume, decompress_complete = LibDeflate:DecompressDeflate(compress_deflate, {async=true}) | ||
repeat until not decompress_resume() | ||
local decompress_async = decompress_complete() | ||
assert(decompress_async == example_input) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think two functions need to be returned. One function should be enough.
Note: Do not start (de)compression on direct calls to (De)CompressDeflate in async mode
Squash your commits into single commit, and use git to remove all format-only lines of changes. |
I changed things around a bit. Instead of being a typical async function, I'm calling it Chunks Mode which returns a handler which gets called on repeat until processing is finished. Some rough benchmarks Seralize & CompressSmall table: 115 Bytes
Medium Table: 163584 Bytes
Large Table: 1750073 Bytes
X-Large Table: 3511073 Bytes
Decompress and DeserializeSmall table: 115 Bytes
Medium Table: 163584 Bytes
Large Table: 1750073 Bytes
X-Large Table: 3511073 Bytes
Chunks Mode does have a small increase in processing time but has the benefit (in WoW and likely other environments) of not locking up the game and making the OS think it's non-responsive when processing large data. Similar changes made on my LibSerialize PR. rossnichols/LibSerialize#4 |
Thanks. I get vocation on May 1-5. Will merge your PR during that period |
|
I would keep the chunk_size option, but remove chunk_time option. |
I had asked him to add it for LibSerialize - conceptually, yielding by size/count doesn't achieve the goal of the feature, which is to minimize impact on frame rate. With size/count, a given operation will always be divided into the same number of chunks, whereas a time based approach allows for fluctuation based on CPU performance. Maybe they can be combined into a single option though? The user supplies a function "shouldYield(count)" and can choose to implement it as "count > threshold" or "elapsed time > threshold". The library resets the count and yields when the function returns true. |
The example in README of this PR
If time is a concern, change the code to
|
time-based chunk will only be provided if it is much better than the alternative of size-based chunk in a while loop, |
Will decide whether to add time option after I have completed code and done a benchmark |
A handler function would be better than a time option configs = { The prototype of chunk_yield_handler is:
The default handler return true for every chunk_size |
I can see that coroutine yield may be costly, so I will consider the handler approach. |
I wasn't suggesting that the library itself measure time - that's why I said that the user-supplied handler is the entity that measures elapsed time. Your example code above doesn't actually help time measurement, because it's I agree that a handler-based approach seems the most flexible and allows the library code to be generic. I don't necessarily see a value to returning
|
Recently very busy and no time to work on this. Hopefully I can work on this on this weekend. |
Currently not maintaining. Will pick up if I would like to |
This adds an
async=true
config option tocoroutine.yield()
in the compress and decompress functions. This removes or reduces framerate loss while processing large strings.