From 856c51212ab369c613e10c410af3c1f3bab27258 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Fri, 17 Jun 2022 15:42:53 -0700 Subject: [PATCH] autosave slightly less aggressively It might reduce wear and tear on disk, and losing 3 seconds of data doesn't feel catastrophic (short of a C-z rampage). Thanks to the love2d.org community for the suggestion: https://love2d.org/forums/viewtopic.php?f=14&t=93173 --- app.lua | 13 +++++++++++++ drawing_tests.lua | 30 ++++++++++++++++++++++++++++++ main.lua | 35 +++++++++++++++++++++++------------ text.lua | 12 ++++++------ 4 files changed, 72 insertions(+), 18 deletions(-) diff --git a/app.lua b/app.lua index 99cef81..a887588 100644 --- a/app.lua +++ b/app.lua @@ -157,6 +157,14 @@ function App.screen.print(msg, x,y) end end +App.time = 1 +function App.getTime() + return App.time +end +function App.wait_fake_time(t) + App.time = App.time + t +end + -- LÖVE's Text primitive retains no trace of the string it was created from, -- so we'll wrap it for our tests. -- @@ -329,10 +337,14 @@ function App.disable_tests() -- test methods are disallowed outside tests App.screen.init = nil App.filesystem = nil + App.time = nil App.run_after_textinput = nil App.run_after_keychord = nil App.keypress = nil App.keyrelease = nil + App.run_after_mouse_click = nil + App.run_after_mouse_press = nil + App.run_after_mouse_release = nil App.fake_key_pressed = nil App.fake_key_press = nil App.fake_key_release = nil @@ -346,6 +358,7 @@ function App.disable_tests() App.width = function(text) return text:getWidth() end App.open_for_reading = function(filename) return io.open(filename, 'r') end App.open_for_writing = function(filename) return io.open(filename, 'w') end + App.getTime = love.timer.getTime App.getClipboardText = love.system.getClipboardText App.setClipboardText = love.system.setClipboardText App.modifier_down = love.keyboard.isDown diff --git a/drawing_tests.lua b/drawing_tests.lua index b8e1953..faf94ce 100644 --- a/drawing_tests.lua +++ b/drawing_tests.lua @@ -10,6 +10,12 @@ function test_creating_drawing_saves() App.draw() -- click on button to create drawing App.run_after_mouse_click(8,Margin_top+8, 1) + -- file not immediately saved + App.update(0.01) + check_nil(App.filesystem['foo'], 'F - test_creating_drawing_saves/early') + -- wait until save + App.wait_fake_time(3.1) + App.update(0) -- filesystem contains drawing and an empty line of text check_eq(App.filesystem['foo'], '```lines\n```\n\n', 'F - test_creating_drawing_saves') end @@ -41,6 +47,9 @@ function test_draw_line() check_eq(p1.y, 6, 'F - test_draw_line/p1:y') check_eq(p2.x, 35, 'F - test_draw_line/p2:x') check_eq(p2.y, 36, 'F - test_draw_line/p2:y') + -- wait until save + App.wait_fake_time(3.1) + App.update(0) -- The format on disk isn't perfectly stable. Table fields can be reordered. -- So just reload from disk to verify. Lines = load_from_disk(Filename) @@ -382,6 +391,9 @@ function test_name_point() App.run_after_keychord('return') check_eq(Current_drawing_mode, 'line', 'F - test_name_point/mode:3') check_eq(p2.name, 'A', 'F - test_name_point') + -- wait until save + App.wait_fake_time(3.1) + App.update(0) -- change is saved Lines = load_from_disk(Filename) local p2 = Lines[1].points[drawing.shapes[1].p2] @@ -410,6 +422,9 @@ function test_move_point() check_eq(p2.x, 35, 'F - test_move_point/baseline/p2:x') check_eq(p2.y, 36, 'F - test_move_point/baseline/p2:y') check_nil(p2.name, 'F - test_move_point/baseline/p2:name') + -- wait until save + App.wait_fake_time(3.1) + App.update(0) -- line is saved to disk Lines = load_from_disk(Filename) local drawing = Lines[1] @@ -433,6 +448,9 @@ function test_move_point() App.run_after_mouse_click(Margin_left+26, Margin_top+Drawing_padding_top+44, 1) check_eq(Current_drawing_mode, 'line', 'F - test_move_point/mode:3') check_eq(drawing.pending, {}, 'F - test_move_point/pending') + -- wait until save + App.wait_fake_time(3.1) + App.update(0) -- change is saved Lines = load_from_disk(Filename) local p2 = Lines[1].points[drawing.shapes[1].p2] @@ -462,6 +480,9 @@ function test_delete_lines_at_point() App.run_after_keychord('C-d') check_eq(drawing.shapes[1].mode, 'deleted', 'F - test_delete_lines_at_point/shape:1') check_eq(drawing.shapes[2].mode, 'deleted', 'F - test_delete_lines_at_point/shape:2') + -- wait for some time + App.wait_fake_time(3.1) + App.update(0) -- deleted points disappear after file is reloaded Lines = load_from_disk(Filename) check_eq(#Lines[1].shapes, 0, 'F - test_delete_lines_at_point/save') @@ -586,6 +607,9 @@ function test_undo_name_point() local p2 = drawing.points[drawing.shapes[1].p2] check_eq(Next_history, 3, 'F - test_undo_name_point/next_history') check_eq(p2.name, '', 'F - test_undo_name_point') -- not quite what it was before, but close enough + -- wait until save + App.wait_fake_time(3.1) + App.update(0) -- undo is saved Lines = load_from_disk(Filename) local p2 = Lines[1].points[drawing.shapes[1].p2] @@ -632,6 +656,9 @@ function test_undo_move_point() check_eq(Next_history, 2, 'F - test_undo_move_point/next_history') check_eq(p2.x, 35, 'F - test_undo_move_point/x') check_eq(p2.y, 36, 'F - test_undo_move_point/y') + -- wait until save + App.wait_fake_time(3.1) + App.update(0) -- undo is saved Lines = load_from_disk(Filename) local p2 = Lines[1].points[drawing.shapes[1].p2] @@ -668,6 +695,9 @@ function test_undo_delete_point() check_eq(Next_history, 3, 'F - test_undo_move_point/next_history') check_eq(drawing.shapes[1].mode, 'line', 'F - test_undo_delete_point/shape:1') check_eq(drawing.shapes[2].mode, 'line', 'F - test_undo_delete_point/shape:2') + -- wait until save + App.wait_fake_time(3.1) + App.update(0) -- undo is saved Lines = load_from_disk(Filename) check_eq(#Lines[1].shapes, 2, 'F - test_undo_delete_point/save') diff --git a/main.lua b/main.lua index def3ec2..5f4f2d1 100644 --- a/main.lua +++ b/main.lua @@ -76,6 +76,7 @@ Drawing_padding_bottom = 10 Drawing_padding_height = Drawing_padding_top + Drawing_padding_bottom Filename = love.filesystem.getUserDirectory()..'/lines.txt' +Next_save = nil -- undo History = {} @@ -168,7 +169,7 @@ function love.resize(w, h) App.screen.width, App.screen.height = w, h Line_width = math.min(40*App.width(Em), App.screen.width-50) Text.redraw_all() - Last_resize_time = love.timer.getTime() + Last_resize_time = App.getTime() end function initialize_font_settings(font_height) @@ -208,7 +209,7 @@ function App.draw() -- some hysteresis while resizing if Last_resize_time then - if love.timer.getTime() - Last_resize_time < 0.1 then + if App.getTime() - Last_resize_time < 0.1 then return else Last_resize_time = nil @@ -235,7 +236,7 @@ function App.draw() if Cursor1.line >= line_index then Cursor1.line = Cursor1.line+1 end - save_to_disk(Lines, Filename) + schedule_save() record_undo_event({before=Drawing.before, after=snapshot(line_index-1, line_index+1)}) end}) if Search_term == nil then @@ -272,13 +273,23 @@ function App.update(dt) Cursor_time = Cursor_time + dt -- some hysteresis while resizing if Last_resize_time then - if love.timer.getTime() - Last_resize_time < 0.1 then + if App.getTime() - Last_resize_time < 0.1 then return else Last_resize_time = nil end end Drawing.update(dt) + if Next_save and Next_save < App.getTime() then + save_to_disk(Lines, Filename) + Next_save = nil + end +end + +function schedule_save() + if Next_save == nil then + Next_save = App.getTime() + 3 -- short enough that you're likely to still remember what you did + end end function App.mousepressed(x,y, mouse_button) @@ -317,7 +328,7 @@ function App.mousereleased(x,y, button) if Search_term then return end if Lines.current_drawing then Drawing.mouse_released(x,y, button) - save_to_disk(Lines, Filename) + schedule_save() if Drawing.before then record_undo_event({before=Drawing.before, after=snapshot(Lines.current_drawing_index)}) Drawing.before = nil @@ -358,7 +369,7 @@ function App.textinput(t) else Text.textinput(t) end - save_to_disk(Lines, Filename) + schedule_save() end function App.keychord_pressed(chord) @@ -409,7 +420,7 @@ function App.keychord_pressed(chord) Selection1 = deepcopy(src.selection) patch(Lines, event.after, event.before) Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaks - save_to_disk(Lines, Filename) + schedule_save() end elseif chord == 'C-y' then for _,line in ipairs(Lines) do line.y = nil end -- just in case we scroll @@ -421,7 +432,7 @@ function App.keychord_pressed(chord) Selection1 = deepcopy(src.selection) patch(Lines, event.before, event.after) Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaks - save_to_disk(Lines, Filename) + schedule_save() end -- clipboard elseif chord == 'C-c' then @@ -436,7 +447,7 @@ function App.keychord_pressed(chord) if s then App.setClipboardText(s) end - save_to_disk(Lines, Filename) + schedule_save() elseif chord == 'C-v' then for _,line in ipairs(Lines) do line.y = nil end -- just in case we scroll -- We don't have a good sense of when to scroll, so we'll be conservative @@ -456,7 +467,7 @@ function App.keychord_pressed(chord) if Cursor_y >= App.screen.height - Line_height then Text.snap_cursor_to_bottom_of_screen() end - save_to_disk(Lines, Filename) + schedule_save() record_undo_event({before=before, after=snapshot(before_line, Cursor1.line)}) -- dispatch to drawing or text elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then @@ -466,7 +477,7 @@ function App.keychord_pressed(chord) local before = snapshot(drawing_index) Drawing.keychord_pressed(chord) record_undo_event({before=before, after=snapshot(drawing_index)}) - save_to_disk(Lines, Filename) + schedule_save() end elseif chord == 'escape' and App.mouse_down(1) then local _,drawing = Drawing.current_drawing() @@ -496,7 +507,7 @@ function App.keychord_pressed(chord) end record_undo_event({before=before, after=snapshot(Lines.current_drawing_index)}) end - save_to_disk(Lines, Filename) + schedule_save() else for _,line in ipairs(Lines) do line.y = nil end -- just in case we scroll Text.keychord_pressed(chord) diff --git a/text.lua b/text.lua index 1ab6303..143a9d0 100644 --- a/text.lua +++ b/text.lua @@ -182,7 +182,7 @@ function Text.keychord_pressed(chord) if (Cursor_y + Line_height) > App.screen.height then Text.snap_cursor_to_bottom_of_screen() end - save_to_disk(Lines, Filename) + schedule_save() record_undo_event({before=before, after=snapshot(before_line, Cursor1.line)}) elseif chord == 'tab' then local before = snapshot(Cursor1.line) @@ -193,12 +193,12 @@ function Text.keychord_pressed(chord) Text.snap_cursor_to_bottom_of_screen() --? print('=>', Screen_top1.line, Screen_top1.pos, Cursor1.line, Cursor1.pos, Screen_bottom1.line, Screen_bottom1.pos) end - save_to_disk(Lines, Filename) + schedule_save() record_undo_event({before=before, after=snapshot(Cursor1.line)}) elseif chord == 'backspace' then if Selection1.line then Text.delete_selection() - save_to_disk(Lines, Filename) + schedule_save() return end local before @@ -235,12 +235,12 @@ function Text.keychord_pressed(chord) Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaks end assert(Text.le1(Screen_top1, Cursor1)) - save_to_disk(Lines, Filename) + schedule_save() record_undo_event({before=before, after=snapshot(Cursor1.line)}) elseif chord == 'delete' then if Selection1.line then Text.delete_selection() - save_to_disk(Lines, Filename) + schedule_save() return end local before @@ -271,7 +271,7 @@ function Text.keychord_pressed(chord) table.remove(Lines, Cursor1.line+1) end end - save_to_disk(Lines, Filename) + schedule_save() record_undo_event({before=before, after=snapshot(Cursor1.line)}) --== shortcuts that move the cursor elseif chord == 'left' then