hi, so ive got this script that reads from and also saves to an external config file, the loading part works as intended it can happily read a value like r_prop = 1 from the config file and only read the value (1) but I cant get it to save over the value part only.
At the moment with the script below the save function overwrites the entire line with the property its fed from the engine. I don’t know how to feed it the identity value (r_prop) with the property in game to overwrite just its value of (1).
Think I must just be tired and not seeing it clear but
who ever can shed some light will have my eternal thanks :yes:
from bge import logic
path = logic.expandPath('//')
file = open(path + 'settings.cfg').readlines()
def load_settings():
cont = logic.getCurrentController()
own = cont.owner
def get_value(identity):
return [line for line in file if line.startswith(identity)][0].split(' = ')[1].strip()
prop = int(get_value('property'))
own['prop'] = prop
def save_settings():
cont = logic.getCurrentController()
own = cont.owner
def write_value(identity):
return [line for line in file if line.startswith(identity)][0].split(' = ')[1].strip()
prop = str(own['property'])
file = open(path + 'settings.cfg', 'w')
file.write(str(prop))
Well, if that’s the whole code it’s a nice try but files are way dumber than that.
To change a line in a text file you have to:
find where the line start and ends (A, B), both in byte indices
split the file in two parts, from 0 to A-1 and from B to filesize-1
write the first chunk of bytes, write the new property line, write the second chunk.
You can avoid rewriting the first chunk by complicating your life even more.
Unless your configuration file is a multimegabyte mass of data - in which case you should probably use a random access data format, effectively creating an embedded database system - you can:
load the whole file as a string
replace the line you want in that string (with split, strip, format… the way you like more really)
rewrite the whole string to the content file
There is no overhead unless you overload the buffering cache of the file system - which, again, requires more than a few kilobytes of data.
Thanks but that doesn’t really help me, there’s no need for me to find where a line starts or ends or even define a line to look at because if you look at def read_value(identity) it looks through the file for a line starting with something ive defined say for instance (v_fov) and then it just does the split at that line that’s been identified. This works already with the load_settings def. Not sure why file size would matter its just a game config file its hardly gonna hit anywhere near megabytes in size.
Basically I need to be able to do the opposite of whats happening here
prop = int(get_value(‘v_fov’))
own[‘prop’] = prop
This looks for the value v_fov in the text file and then applies the int value in the config file
to prop, which can be used however one likes.
I wish to do the exact opposite, I need to get the property value from the object I want saving
get the line I wish to write to using the same method as the get_Value def and then write to the
file with that information.
something like this
def write_value(identity):
return [line for line in file if line.startswith(identity)][0].split(' = ')[1].strip()
prop = str(own['fov'])
prop = int(write_value('v_fov'))
file = open(path + 'settings.cfg', 'w')
file.write(str(prop))
I really want to use this way of finding the lines I want because its the most elegant way of doing it with minimal lines of code
def write_value(identity):
return [line for line in file if line.startswith(identity)][0].split(' = ')[1].strip()
The way you find the line is fine. But when you write:
file.write(str(prop))
you’re not changing that line in the file, you are changing the whole file.
It also writes in the file something that doesn’t seem to match your format (which is probably a sequence of key = value entries), because you’re writing just the value part, without key or =, that is why you are probably unable to re-read written properties.
I understand that you would like to change just the old line. You cannot do it that way. The way you would be able to do it is way more complex, because of how information is stored in files. That’s where the index stuff comes in and that’s what you should avoid, if possible, because it doesn’t add performance or functionality, just complexity.
Your code makes sense for the read part. The write one does not. It looks like a specular transformation of the read part but it doesn’t really write what you would like to.
First, you need to pass the name of the property with the value. With that you create a line in the form:
name = value
then you can read the file line by line, change the line that starts with the same name with the new, entire token “name = value”, then rewrite all the lines to the file.
Here’s an example:
conf = "config.txt"
def read_value(name, defaultValue=None):
with open(conf, "r") as f:
for line in f:
if line.startswith(name):
token = line.strip()
keyValuePair = token.split("=")
value = keyValuePair[1]
return value
return defaultValue
def write_value(name, value):
buffer = []
newKeyValuePair = name + "=" + str(value) + "
"
replaced = False
with open(conf, "r") as f:
for line in f:
token = line.strip()
print("token", token)
if token.startswith(name):
buffer.append(newKeyValuePair)
replaced = True
else:
buffer.append(token + "
")
if replaced == False:
buffer.append(newKeyValuePair)
with open(conf, "w") as f:
f.writelines(buffer)
#value = read_value("some name", "a default value")
#write_value("some name", "some value")
Thanks for the nice example, yeah the write method just overwrites the whole file cause I don’t know how to feed it the write_value key but your saying this isn’t possible without this indexing way, which seems like im gonna have to look into cause I don’t want to feed the config file any info from the script itself but integer properties from objects.
I just thought that if you can read only a certain line in a text file with a specific key you could also write on any specific line you wish with a specified key.
I should have just uploaded an example blend to show the situation, but would need to be in a zip with the settings/config file separate.
Do you need to find the key given the value then replace the old value with a new one, under the same key? That can be done.
I mean, once you know that the format of the file is key=value, one token per line, you can do everything you want with keys and values. The way you actually modify the contents of the config file is not relevant.
Um well yes that would work as the key would never be changed just the integer value of the key if im understanding you right. So are you saying if I have a key called (prop) in the config file with a value of (1) I can change just that value? (thats what ive been trying to do all along) I dont need to change any text in the config file I only need to write integers on the fly in engine.
The goal is to have a config file that holds basic info camera fov screen resolution just basic stuff that can be handled with ints. then have this info loaded on game start (ive got this part down) then i just want to save new integers to the config file from in game objects.
So say an object with a property and an int value set, the save part of the script is run it gets the int value from that objects property and saves the int to the config file with the specified key.
I have modified the script in the file with a read-write that works and should represent your idea. I have used a class because I think it makes it easier to understand the separation between what you would like to be able to do and the implementation details that don’t matter:
from bge import logic
import os
CONFIGURATION_FILE_PATH = logic.expandPath('//settings.cfg')
#a class that contains the implementation details of reading writing properties in the config file
class ConfigurationManager():
def __init__(self, filepath):
self.path = filepath
def getValues(self):#this loads the entire configuration file and returns the
#key-value pairs it contains as a string-string dictionary
data = {}
if not os.path.isfile(self.path): return data#file doesn't exist
with open(self.path, "r") as lines:
for line in lines:
keyValuePair = line.strip().split("=")
key = keyValuePair[0].strip()
value = keyValuePair[1].strip()
data[key] = value
return data
def _setValues(self, dataDict):#this overwrites all properties in the file
with open(self.path, "w+") as datafile:#create the file if it doesn't exist
for key in dataDict.keys():
keyValuePair = str(key) + "=" + str(dataDict[key]) + "
"
datafile.write(keyValuePair)
def getValue(self, key): #this returns the value of a property in the configuration
#file (or None if the configuration file doesn't contain that property)
dataDict = self.getValues()
return dataDict.get(key)
def setValue(self, key, value):#this sets a single property in the configuration file
data = self.getValues()
data[key] = value
self._setValues(data)
pass
#end of the class. Below the usage
def load_settings():
cont = logic.getCurrentController()
own = cont.owner
configuration = ConfigurationManager(CONFIGURATION_FILE_PATH)
prop1 = int(configuration.getValue("r_prop1"))
own['PROP1'] = prop1
prop2 = int(configuration.getValue("r_prop2"))
own['PROP2'] = prop2
def save_settings():
cont = logic.getCurrentController()
own = cont.owner
prop = own['PROP1']
configuration = ConfigurationManager(CONFIGURATION_FILE_PATH)
configuration.setValue("r_prop1", prop)
You may also find it easier to play with the code, for example by adding a “getIntValue” function that internally converts the string to a in int, so that the user code part will look cleaner.
JSon format could be a valid alternative, giving that the python json api provides the basic converters between json and python types (number are read as numbers, arrays as arrays, things like that).
Wow, just been studying that through now for a while, and that’s a really smart and clean way of handling it all, it works wonderfully well. Yes I agree a class is the best way to handle scripts of this nature.
The way I was trying to do it seems really bad now even if the way I was writing it was slightly less code it would have become very cluttered over time I think, with lots of doubling up on code. I can see now after looking though that code after trying to write my own how this sort of thing is best done now.
Some ways of python are a little beyond my knowledge of the language at the moment but I get majority of python its the little things that stop me in my tracks sometimes. oh yeah I did see some mentioning of a json api in the python docs think i will look into that. Mainly im trying to get by with just integers I found booleans true/false values to be a bit funny in the engine when reading and writing them.
I cant thank you enough for all your help and for writing out all that code. Can only wish nice people like you all the best in life.