Jak wdrożyć świat testowy, który nigdy się nie restartuje?

23

Szukam pomysłów, jak wykonać następujące czynności: Chcę napisać prosty „świat” w Javie. Jeden, który mógłbym rozpocząć, a następnie dodać nowe obiekty w późniejszym terminie, aby symulować / obserwować różne zachowania między istniejącymi obiektami. Planuje się następnie zakodować nowsze obiekty po pewnym czasie oglądania starych, a następnie załadować / upuścić w istniejącym świecie. Problem polega na tym, że nie chcę nigdy zatrzymywać ani restartować świata po jego uruchomieniu, chcę, aby działał przez kilka tygodni, ale potrzebuję możliwości upuszczania obiektów i ponawiania / przepisywania / usuwania / tworzenia / mutowania ich z czasem bez konieczności ponownego uruchamiania. Świat może być tak prosty, jak tablica 100 x 100 lokalizacji X / Y, z możliwym graficznym interfejsem kafelkowym, który wizualnie reprezentuje świat. Wiem, że potrzebuję pewnego rodzaju procesu ticktimer, aby monitorować obiekty i dać każdemu „szansę na działanie”

Przykład: koduję World.java w poniedziałek i zostawiam włączony. Następnie we wtorek piszę nową klasę o nazwie Rock.java (która się nie porusza). Następnie ładuję / upuszczam (jakoś?) Do tego już działającego świata (który po prostu upuszcza go gdzieś losowo w tablicy świata i nigdy się nie rusza). Następnie w środę tworzę nową klasę o nazwie Cat.java i upuszczam ją na świat, ponownie umieszczoną losowo, ale ten nowy obiekt może poruszać się po świecie (w pewnym przedziale czasu), a następnie w czwartek piszę klasę o nazwie Pies. java, która również się porusza, ale może „oddziaływać” na inny obiekt, jeśli znajduje się on w sąsiedztwie i na odwrót.

To jest ta rzecz. Nie wiem, jakiego rodzaju struktury / projektu potrzebowałbym do kodowania rzeczywistej światowej klasy, aby wiedzieć, jak wykrywać / ładować / śledzić przyszłe (i obecnie nieistniejące) obiekty.

Jakieś pomysły na to, jak zrobiłbyś coś takiego przy użyciu Java?

d33j
źródło
2
Brzmi jak zamiana na gorąco . Być może istnieje literatura na ten temat, która może być pomocna. W każdym razie bardzo interesujące pytanie. +1…
Konrad Rudolph

Odpowiedzi:

5

Zasadniczo szukasz systemu z możliwością podłączenia na gorąco. Uruchamiasz główną aplikację, a następnie dodajesz wtyczki w czasie wykonywania, które integrują się z pętlą zdarzeń. Najpierw zacznij od myślenia o tym, czego Twój świat oczekuje od bytu gry. Na przykład (na podstawie twojego opisu):

interface Entity {
   void init(World world);
   // Called when loaded for the first time in the world and adds itself
   // to the world (correct position in the array, scheduler for updates)

   void update(World world);
   // Called when scheduler fires (allows the entity to think about its
   // next move)

   Image getImage();
   // Called by the GUI when it is time to draw the entity on the screen
}

Oczywiście możesz dodać inne metody, które uznasz za konieczne. Zanotuj parametr World za pomocą dwóch odpowiednich metod. To pozwala twojemu nowemu bytowi patrzeć na świat podczas konfigurowania lub aktualizacji. Na przykład w klasie psów możesz poprosić świat o wszystkie koty w okolicy. Następnie tworzysz swój świat, który działa z tym interfejsem i systemem do dynamicznego kompilowania i ładowania kodu Java. Przykład tego można znaleźć tutaj .

void injectEntity(String filename, World world) {
    // Load the class as described in the mentioned link
    Entity e = (Entity) loadClass(filename);
    e.init(world);
}

Wywołaj tę metodę ze światowego GUI, aby dodać nowe podmioty. W zależności od implementacji World funkcja inicjująca Entity może wyglądać następująco:

void init(World world) {
   // Register entity with world for easy access
   world.register(this, "RockImpl A");

   // Place the entity on the world map
   Point initialLocation = Point(10,5);
   world.place(this, initialLocation);

   // Schedule its update method for every 5 seconds
   world.schedule(this, 5);
}
duch
źródło
1
Słowa kluczowe dla google: „Kontenery IoC”, „Odwrócenie kontroli”, „Wstrzykiwanie zależności”. Są to techniki dla ogólnych systemów podłączanych podczas pracy, które mogą wykrywać i ładować komponenty w czasie wykonywania.
Nevermind
16

Zrobiliśmy coś takiego w Stendhal na naloty.

Nie staraliśmy się całkowicie unikać ponownego uruchamiania. Dlatego zmiany w naszych podstawowych usługach infrastrukturalnych, takich jak komunikacja klient / serwer, wymagają ponownego uruchomienia. Ale dodawanie bytów, stworzeń i NPC oraz modyfikowanie istniejących obiektów działa. (Aha, a czasem naprawa błędów na żywo, odbicie może być użyte do manipulowania nawet polami prywatnymi).

Ponieważ chcemy nie tylko nowego obiektu opartego na nowych danych (jak inna skóra), ale chcemy dodać nowe zachowanie, program światowy musi mieć możliwość ładowania nowych plików klas . Nazywamy je „skryptami”, ale są prawdziwymi skompilowanymi klasami Java. Klasy te implementują interfejs Script.java .

Maria.java jest prostym przykładem. Jest nową postacią niezależną, która sprzedaje napoje i jedzenie graczom. Możemy również zdefiniować tam bardzo złożone obiekty .

Nowa klasa jest ładowana w ten sposób:

// create a new class loader, with the script folder as classpath.
final File file = new File("./data/script");
final ClassLoader loader = new URLClassLoader(new URL[]{file.toURI().toURL()});

// load class through new loader
final Class< ? > aClass = loader.loadClass(classname);
script = (Script) aClass.newInstance();

Jeśli możesz zapewnić unikalne nazwy i nigdy nie chcesz zwalniać klas, masz już dość niskiego poziomu.

Rozładunek wydaje się jednak bardzo ważny. Aby to osiągnąć, musisz utworzyć instancję modułu ładującego nową klasę, ilekroć chcesz wstrzyknąć nowy kod. Aby GC mógł wykonać swoją pracę po usunięciu ostatniego odwołania do tego kodu.

W naszym interfejsie mamy komendę / unload, która wywołuje metodę unload, dzięki czemu skrypty mogą przeprowadzać czyszczenie. Rzeczywisty rozładunek odbywa się automatycznie przez GC.

Podczas najazdów często tworzymy wiele tymczasowych obiektów . I chcemy, aby wszystkie zostały usunięte po zakończeniu nalotu. Na przykład Raid Gnomes, który odradza wiele gnomów w pobliżu niewidzialnego administratora, używamy tego kodu: GnomeRaid.java rozszerza CreateRaid.java .

Skrypt może uzyskać bezpośredni dostęp do świata (jak pokazuje pierwszy przykład) i wykonać własne czyszczenie w metodzie unload (). Ale kodery Java nie są używane do czyszczenia i jest to denerwujące. Stworzyliśmy więc piaskownicę, z której mogą korzystać skrypty. Po rozładowaniu wszystkie obiekty dodane do świata za pośrednictwem klasy Sandbox są usuwane.

Hendrik Brummermann
źródło
3

Uratuj świat (bez zamierzonej gry słów) i cały jego stan (pozycje, prędkości, wszystkie zmienne).

  • Zamknij program.
  • Wprowadź zmiany (zapewniając zgodność wsteczną z zapisami).
  • Ponownie skompiluj program.
  • Uruchom ponownie program.
  • Załaduj świat i stan.
  • Powtarzać

Jest to jednak ogólne rozwiązanie, nie znam specyfiki Javy, aby wiedzieć, czy możesz dynamicznie implementować bloki kodu i klasy, jak masz nadzieję ...

Opcja 2 polega na powiązaniu języka skryptowego, który można załadować w locie.

zwalniał
źródło
+1 dla języka skryptowego. Jeśli nie jesteś związany z Javą, wypróbuj także niektóre sterowniki MUD i mudlibs oparte na LPC.
Martin Sojka
1

W przypadku Java wszystko czego potrzebujesz to platforma OSGi . Dzięki temu jest to proste w przypadku modułów lub aplikacji wymienianych podczas pracy, a nawet zdalnego zarządzania lub częściowych aktualizacji.


źródło