-
Notifications
You must be signed in to change notification settings - Fork 28
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
[Feature Request] Better Level Management #254
Comments
If the data on disk has changed since the instance was created the data inside may no longer be correct. |
This is a quick and rough implementation but I believe it covers most of what is described here. It minimally changes the existing code and has reference tracking so after all references of a single world have been closed (from the perspective of Amulet Editor or whatever is asking to load said world) it will actually close the world (from the perspective of the Amulet API). I haven't added in atomic locking for when multiple threads are using the cache but that should be fairly simple to implement if this approach is taken. However, this doesn't handle the situation where data on the disk has changed (from either a second instance of Amulet API or from another program) but this could be solved by using the from __future__ import annotations
from typing import Dict, NamedTuple
from amulet import load_level
from amulet.api.level.base_level.base_level import BaseLevel
_cache: Dict[str, WorldCacheEntry] = {}
class WorldCacheEntry(NamedTuple):
world: BaseLevel
references: int
def wrap_close_method(path, world, old_close):
global _cache
def func():
if path not in _cache: # This is needed since the BaseLevel.__del__ will be called when the actual BaseLevel object is garbage collected, which then calls this method again
return
_, references = _cache[path]
_cache[path] = entry = WorldCacheEntry(world, references - 1)
if entry.references == 0:
del _cache[path]
old_close()
return func
def get(path):
global _cache
if path in _cache:
world, references = _cache[path]
_cache[path] = WorldCacheEntry(world, references + 1)
return _cache[path].world
world = load_level(path)
world.close = wrap_close_method(path, world, world.close) # Decorate/wrap the close method with something that handles the cache
_cache[path] = WorldCacheEntry(world, 1)
return world
if __name__ == "__main__":
world1 = get(
"/home/podshot/Amulet-Core/tests/data/worlds_src/java/vanilla/1_18/vanilla"
)
world2 = get(
"/home/podshot/Amulet-Core/tests/data/worlds_src/java/vanilla/1_18/vanilla"
)
print(world1)
print(world2)
print(world1 is world2)
print(_cache)
world1.close()
print(_cache)
world2.close()
print(_cache)
|
What you have implemented could be achieved much easier with a I have been thinking while refactoring this code that it might make sense to merge the Level and FormatWrapper classes. |
Yeah that would be a better implementation than what I have, the only concern I have is that the logic currently in the |
I don't like that because if something holds a reference it will never get closed. It is the responsibility of the owner to make sure nothing else expects the level to be open. |
The Problem
Currently a call to the
load_level
orload_format
method in our core library returns a new instance of the level wrapper.This has currently been fine because each level tab owns the level associated with it.
The issue happens when something separate to that level tab wants to access that world.
An example of this if a level is opened in a level tab and also in the converter in another level tab.
Feature Description
We should have some sort of caching system that can return a previously opened instance rather than opening it again since this can cause issues.
We would also need to add a system to release the level when it is finished with so it can be closed when everything is finished with it.
We would need to keep the normal close method so that a program can force close the world.
Everything up to now has been fine because only one element should be running at once but we should plan in case we want to allow multiple level tabs to run at once (eg allowing dragging a level tab out of the standard view)
Additional context
We should discuss the solution to this problem because I am sure my solution will have problems I have not thought of and this may have been solved before.
Implementation
The code that "owns" the level should close it when it is finished with it.
The
__del__
method should also call the close method to make sure it actually gets closed.The text was updated successfully, but these errors were encountered: