Skip to content

Commit 3d86840

Browse files
committed
Refactor autosaver to use task system + more
auto saves are now ALWAYS in autosaves folder auto saves now are saved with the datetime temporarily (maybe?) remove the max files logic
1 parent ee474be commit 3d86840

5 files changed

Lines changed: 84 additions & 122 deletions

File tree

toontown/leveleditor/AutoSaver.py

Lines changed: 26 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,94 +2,50 @@
22
import shutil
33
import time
44
import threading
5+
from datetime import datetime
56

67
from .DNASerializer import DNASerializer
78

89

910
class AutoSaver:
10-
autoSaverToggled = False
11-
autoSaverInterval = 15.0
12-
maxAutoSaveCount = 10.0
1311

1412
@staticmethod
1513
def initializeAutoSaver():
16-
threading.Thread(target = AutoSaver.autoSaverProcess, daemon = True).start()
17-
# Creates 'autosaves' directory in user data directory
18-
if not os.path.isdir('leveleditor/autosaves'):
19-
os.mkdir('leveleditor/autosaves')
14+
# Remove any existing autosaver tasks (used if we change settings)
15+
taskMgr.remove('autosaver-task')
16+
17+
# Create 'autosaves' directory in user data directory
18+
if not os.path.isdir(f'{userfiles}/autosaves'):
19+
os.mkdir(f'{userfiles}/autosaves')
2020
print('Created "autosaves" dir')
21+
taskMgr.doMethodLater(settings.get('autosave-interval', 15) * 60, AutoSaver.autoSaverProcess, 'autosaver-task')
2122

2223
@staticmethod
23-
def autoSaverProcess():
24-
while True:
25-
autoSaverInterval = AutoSaver.autoSaverInterval * 60 # Converts global autoSaverInterval to minutes
26-
# Loops without doing anything if auto saver isn't toggled
27-
if AutoSaver.autoSaverToggled is False:
28-
time.sleep(0.1)
29-
30-
while AutoSaver.autoSaverToggled is True:
31-
# outputFile filename is empty, which may occur if filename is left blank in file prompt
32-
if DNASerializer.outputFile is None:
33-
print('No file loaded, exiting auto saving loop...')
34-
DNASerializer.autoSaveCount = 0
35-
DNASerializer.autoSaverMgrRunning = False
36-
AutoSaver.autoSaverToggled = False
37-
break
24+
def autoSaverProcess(task):
25+
autosaveEnabled = settings.get('autosave-enabled', True)
26+
# Loops without doing anything if auto saver isn't toggled
27+
if not autosaveEnabled:
28+
return task.again
3829

39-
# Epoch time of next auto save
40-
endTime = time.time() + autoSaverInterval
41-
# Loops until endTime is reached or the auto saver is un-toggled by user
42-
while time.time() <= endTime and AutoSaver.autoSaverToggled is True:
43-
time.sleep(0.1)
44-
# Only auto save if auto save is toggled
45-
if AutoSaver.autoSaverToggled is True:
46-
AutoSaver.manageAutoSaveFiles()
47-
# Sets autoSaveCount
48-
if DNASerializer.autoSaveCount >= AutoSaver.maxAutoSaveCount:
49-
DNASerializer.autoSaveCount = AutoSaver.maxAutoSaveCount
50-
else:
51-
DNASerializer.autoSaveCount += 1
30+
# Only auto save if auto save is toggled
31+
if autosaveEnabled:
32+
AutoSaver.manageAutoSaveFiles()
33+
return task.again
5234

5335
@staticmethod
5436
def manageAutoSaveFiles():
5537
DNASerializer.autoSaverMgrRunning = True
5638
autoSaveCount = int(DNASerializer.autoSaveCount)
39+
outputFile = DNASerializer.outputFile
40+
newout = outputFile
41+
if not outputFile:
42+
newout = os.path.join(userfiles, 'unsaved.dna')
5743

5844
# Defining outputFile name properties
59-
base = os.path.basename(DNASerializer.outputFile)
60-
dir = os.path.dirname(DNASerializer.outputFile)
61-
basename, extension = os.path.splitext(base)
62-
63-
# Renames output file to auto save file naming convention
64-
if autoSaveCount == 0:
65-
# Only save & manage 'latest' file
66-
if AutoSaver.maxAutoSaveCount == 0:
67-
# Only save auto save latest file
68-
if basename[-16:] == '_autosave-latest':
69-
DNASerializer.outputDNADefaultFile() # Saves working DNA file
70-
return
71-
DNASerializer.outputFile = os.path.join(dir, 'autosaves', basename + '_autosave-latest' + extension)
72-
# Replaces any back slashes in outputFile with forward slashes
73-
DNASerializer.outputFile = DNASerializer.outputFile.replace('\\', '/')
74-
# Creates new 'autosaves' directory if none is found
75-
# This also accounts for if the directory of outputFile is not in the default user data folder
76-
if not os.path.isdir(os.path.join(dir, 'autosaves')):
77-
os.mkdir(os.path.join(dir, 'autosaves'))
78-
79-
# Deletes 'latest' from filename
80-
basename = basename[:-6]
81-
82-
# Copies auto save files
83-
while autoSaveCount != 0:
84-
filename = os.path.join(dir, basename + str(autoSaveCount) + extension)
85-
oldFilename = os.path.join(dir, basename + str(autoSaveCount - 1) + extension)
86-
# Copies working auto save file
87-
if autoSaveCount == 1:
88-
shutil.copy2(DNASerializer.outputFile, filename)
89-
# Incrementally copies each auto save file (i.e. 3400_autosave-1.dna -> 3400_autosave-2.dna)
90-
if autoSaveCount > 1:
91-
shutil.copy2(oldFilename, filename)
92-
autoSaveCount -= 1
45+
bn = os.path.basename(newout)
46+
basename, extension = os.path.splitext(bn)
9347

94-
DNASerializer.outputDNADefaultFile() # Saves working DNA file
48+
DNASerializer.outputDNA(
49+
f'{userfiles}/autosaves/auto_{basename}_{datetime.now().strftime("%Y_%m_%d-%I_%M_%S_%p")}.dna',
50+
True)
9551
DNASerializer.autoSaverMgrRunning = False

toontown/leveleditor/DNASerializer.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def saveToSpecifiedDNAFile():
6060
DNASerializer.outputFile = dnaFilename
6161

6262
@staticmethod
63-
def loadDNAFromFile(filename):
63+
def loadDNAFromFile(filename: str):
6464
DNASerializer.notify.debug("Filename: %s" % filename)
6565
# Reset level, destroying existing scene/DNA hierarcy
6666
base.le.reset(fDeleteToplevel = 1, fCreateToplevel = 0,
@@ -71,7 +71,8 @@ def loadDNAFromFile(filename):
7171
node = loadDNAFile(DNASTORE, Filename.fromOsSpecific(filename).cStr(), CSDefault, 1)
7272
except Exception:
7373
DNASerializer.notify.debug(
74-
"Couldn't load specified DNA file. Please make sure storage code has been specified in Config.prc file")
74+
"Couldn't load specified DNA file. Please make sure storage code has been specified in Config.prc "
75+
"file")
7576
return
7677
if node.getNumParents() == 1:
7778
# If the node already has a parent arc when it's loaded, we must
@@ -117,15 +118,23 @@ def outputDNADefaultFile():
117118
DNASerializer.outputDNA(file)
118119

119120
@staticmethod
120-
def outputDNA(filename):
121+
def outputDNA(filename: str, isAutoSave: bool = False):
122+
"""
123+
Output current DNA to specified file
124+
:param filename: Output filename
125+
:param isAutoSave: Specifies whether this is an auto or a manual save
126+
"""
121127
print('Saving DNA to: ', filename)
122128
binaryFilename = Filename(filename)
123129
binaryFilename.setBinary()
124130
base.le.DNAData.writeDna(binaryFilename, Notify.out(), DNASTORE)
125-
base.le.popupNotification(f"Saved to {os.path.basename(binaryFilename)}")
126-
if ConfigVariableString("compiler") in ['libpandadna', 'clash']:
127-
print(f"Compiling PDNA for {ConfigVariableString('compiler')}")
128-
DNASerializer.compileDNA(binaryFilename)
131+
if isAutoSave:
132+
base.le.popupNotification(f"Autosaved as {os.path.basename(binaryFilename)}")
133+
else:
134+
base.le.popupNotification(f"Saved to {os.path.basename(binaryFilename)}")
135+
if ConfigVariableString("compiler") in ['libpandadna', 'clash']:
136+
print(f"Compiling PDNA for {ConfigVariableString('compiler')}")
137+
DNASerializer.compileDNA(binaryFilename)
129138

130139
@staticmethod
131140
def compileDNA(filename):

toontown/leveleditor/LevelEditor.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from otp.otpbase import OTPGlobals
2727
from toontown.hood.GenericAnimatedProp import *
2828
from toontown.toon import RobotToon, LEAvatar
29+
from .AutoSaver import AutoSaver
2930
from . import LevelEditorGlobals
3031
from . import LevelEditorPanel
3132
from . import VisGroupsEditor
@@ -343,6 +344,8 @@ def startUp(self, dnaPath = None):
343344
self.boxStartMouse = (0, 0)
344345
self.boxEndMouse = (0, 0)
345346

347+
AutoSaver.initializeAutoSaver()
348+
346349
# ENABLE/DISABLE
347350
def enable(self):
348351
""" Enable level editing and show level """
@@ -4107,7 +4110,7 @@ def selectionBoxTask(self, task):
41074110
return task.again
41084111

41094112
def finishBoxSelection(self):
4110-
''' Calculates all the stuff in the selection '''
4113+
""" Calculates all the stuff in the selection """
41114114
base.direct.deselectAll()
41124115

41134116
# The following is mostly from direct.directtools.DirectManipulation, but modified for dnanodes instead of geom nodes.

toontown/leveleditor/LevelEditorPanel.py

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,6 @@ def __init__(self, levelEditor, parent = None, **kw):
129129
label = 'Injector',
130130
command = self.showInjector)
131131
menuBar.addmenuitem('Advanced', 'separator')
132-
menuBar.addmenuitem('Advanced', 'checkbutton',
133-
'Toggle Auto-saver On/Off',
134-
label = 'Toggle Auto Saver',
135-
command = self.toggleAutoSaver)
136132
menuBar.addmenuitem('Advanced', 'command',
137133
'User Set Auto Saver Options',
138134
label = 'Auto Saver Options',
@@ -192,23 +188,32 @@ def __init__(self, levelEditor, parent = None, **kw):
192188
command = self.setAutoSaverInterval)
193189
self.autoSaverDialog.withdraw()
194190

191+
self.autoSaverEnabled = IntVar()
192+
self.autoSaverEnabled.set(settings['autosave-enabled'])
193+
self.autoSaverEnable = ttk.Checkbutton(
194+
self.autoSaverDialog.interior(),
195+
text = 'Enable Autosaving', width = 20,
196+
variable = self.autoSaverEnabled)
197+
195198
self.autoSaverDialogInterval = Pmw.Counter(self.autoSaverDialog.interior(),
196199
labelpos = 'w',
197200
label_text = 'Auto save interval in minutes:',
198201
entry_width = 10,
199-
entryfield_value = int(AutoSaver.autoSaverInterval),
202+
entryfield_value = int(settings['autosave-interval']),
200203
entryfield_validate = {'validator': 'real',
201-
'min': 1, 'max': 60})
202-
203-
self.autoSaverDialogMax = Pmw.Counter(self.autoSaverDialog.interior(),
204-
labelpos = 'w',
205-
label_text = 'Max auto save files:',
206-
entry_width = 10,
207-
entryfield_value = int(AutoSaver.maxAutoSaveCount),
208-
entryfield_validate = {'validator': 'numeric',
209-
'min': 0, 'max': 99})
210-
211-
counters = (self.autoSaverDialogInterval, self.autoSaverDialogMax)
204+
'min': 1, 'max': 60
205+
})
206+
207+
#self.autoSaverDialogMax = Pmw.Counter(self.autoSaverDialog.interior(),
208+
# labelpos = 'w',
209+
# label_text = 'Max auto save files:',
210+
# entry_width = 10,
211+
# entryfield_value = int(settings['autosave-max-files']),
212+
# entryfield_validate = {'validator': 'numeric',
213+
# 'min': 0, 'max': 99
214+
# })
215+
216+
counters = (self.autoSaverEnable, self.autoSaverDialogInterval)#, self.autoSaverDialogMax)
212217
Pmw.alignlabels(counters)
213218
for counter in counters:
214219
counter.pack(fill = 'both', expand = 1)
@@ -412,7 +417,7 @@ def __init__(self, levelEditor, parent = None, **kw):
412417
landmarkBuildingsPage, width = 24,
413418
textvariable = self.landmarkBuildingNameString)
414419
self.landmarkBuildingNameBox.pack(expand = 0, fill = X)
415-
420+
416421
self.bldgLabels = IntVar()
417422
self.bldgLabels.set(0)
418423
self.bldgLabelsButton = ttk.Checkbutton(
@@ -999,9 +1004,6 @@ def toggleWidgetHandles(s = self):
9991004
# Make sure input variables processed
10001005
self.initialiseoptions(LevelEditorPanel)
10011006

1002-
# Initializes auto saver for use
1003-
AutoSaver.initializeAutoSaver()
1004-
10051007
def updateInfo(self, page):
10061008
if page == 'Signs':
10071009
self.updateSignPage()
@@ -1509,8 +1511,11 @@ def setBattleCellType(self, name):
15091511
def setAutoSaverInterval(self, i):
15101512
if i == 'Save Options':
15111513
try:
1512-
AutoSaver.autoSaverInterval = float(self.autoSaverDialogInterval.get())
1513-
AutoSaver.maxAutoSaveCount = float(self.autoSaverDialogMax.get())
1514+
settings['autosave-enabled'] = bool(self.autoSaverEnabled.get())
1515+
settings['autosave-interval'] = int(self.autoSaverDialogInterval.get())
1516+
#settings['autosave-max-files'] = float(self.autoSaverDialogMax.get())
1517+
# Reset the autosaver
1518+
AutoSaver.initializeAutoSaver()
15141519
except ValueError as e:
15151520
# Non-float was passed
15161521
raise e
@@ -1585,16 +1590,3 @@ def toggleDrive(self):
15851590
self.levelEditor.useDriveMode()
15861591
else:
15871592
self.levelEditor.useDirectFly()
1588-
1589-
def toggleAutoSaver(self):
1590-
if AutoSaver.autoSaverToggled is False:
1591-
# If no working DNA outputFile is selected, one is chosen here
1592-
if DNASerializer.outputFile is None:
1593-
DNASerializer.saveToSpecifiedDNAFile()
1594-
print(f'Starting auto saver on an interval of {AutoSaver.autoSaverInterval} minutes')
1595-
# Toggles auto saver to begin auto saving loop
1596-
AutoSaver.autoSaverToggled = True
1597-
else:
1598-
print('Stopping auto saver...')
1599-
# Stops auto saving loop
1600-
AutoSaver.autoSaverToggled = False

ttle.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@
2929
DEFAULT_SERVER = TOONTOWN_ONLINE
3030

3131
DEFAULT_SETTINGS = {
32-
'autosave-enabled': True,
33-
'autosave-interval': 15,
34-
'autosave-max-files': 10,
32+
'autosave-enabled': True,
33+
'autosave-interval': 15,
34+
'autosave-max-files': 10,
3535
'fps-meter-update-rate': 0
3636
}
3737

38+
3839
class ToontownLevelEditor(ShowBase):
3940
notify = directNotify.newCategory("Open Level Editor")
4041
APP_VERSION = open('ver', 'r').read()
@@ -99,8 +100,8 @@ def __init__(self):
99100
loadPrcFileData("", "png-textures true")
100101
else:
101102
messagebox.showerror(
102-
message = "There was an error located resources!\n"
103-
"Make sure you put the phase folders in the root folder!")
103+
message = "There was an error located resources!\n"
104+
"Make sure you put the phase folders in the root folder!")
104105

105106
server = SERVER_TO_ID.get(args.server[0].lower(), DEFAULT_SERVER)
106107
self.server = server
@@ -148,7 +149,8 @@ def toggleFrameRateMeter(self, flag):
148149
if flag:
149150
if not self.frameRateMeter:
150151
self.frameRateMeter = OnscreenText(parent = base.a2dTopRight, text = '', pos = (-0.01, -0.05, 0.0),
151-
scale = 0.05, style = 3, bg = (0, 0, 0, 0.4), align = TextNode.ARight,
152+
scale = 0.05, style = 3, bg = (0, 0, 0, 0.4),
153+
align = TextNode.ARight,
152154
font = ToontownGlobals.getToonFont())
153155
taskMgr.add(self.updateFrameRateMeter, 'fps')
154156
else:
@@ -176,7 +178,7 @@ def updateFrameRateMeter(self, task):
176178
text = f'{round(fps, 1)} FPS'
177179
self.frameRateMeter.setText(text)
178180
self.frameRateMeter.setFg(color)
179-
task.delayTime = settings['fps-meter-update-rate']/1000
181+
task.delayTime = settings['fps-meter-update-rate'] / 1000
180182
return task.again
181183

182184
def __checkForFiles(self):
@@ -230,8 +232,8 @@ async def __checkUpdates(self):
230232
self.notify.info("Client is up to date!")
231233
except:
232234
messagebox.showerror(
233-
message = "There was an error checking for updates! This is likely an issue with your connection. "
234-
"Press OK to continue using the application.")
235+
message = "There was an error checking for updates! This is likely an issue with your connection. "
236+
"Press OK to continue using the application.")
235237

236238

237239
# Run it

0 commit comments

Comments
 (0)