The online racing simulator
PHP5 - InSim Mod.
(120 posts, started )
Quote from dawesdust_12 :Actually, on a more serious note, I would be willing to contribute as well, even if it's just writing example scripts or whatever.

Very cool, very cool! The more people who take up programming plugins for this, is always a good thing for the project. Hell, if this should get to a sufficient level, I bet Vic would even use it!

...

On another note, there was something about the register_handler function pointed back to the plugin class that rubbed me the wrong way. While I agree it is the fasest way to get something working I'm sure that I read somewhere it was not a good thing to do ... I just read over the PHP manual, and found that variables that reference themselves and make a pointer to it, followed by the object being unset can cause a memory leak ... So I'm going to do away with that so that you can unload plugins at runtime if you so wish without the memory leak problem.

So yeah, the first version of PHPInSimMod will allow for loading and unloading of plugins at runtime. How cool is that!
Quote from Dygear :... found that variables that reference themselves and make a pointer to it, followed by the object being unset can cause a memory leak ...

Yes I've run into that in another script... doing away w/ that is a good idea!
so, where can I get this stuff, so I can create another cruise server in PHP?

Quote from dawesdust_12 :so, where can I get this stuff, so I can create another cruise server in PHP?


Ripnet is reworking the startup procedure, I'm working on the plugin system and modules right now. All of the code has been thrown out, we are starting from scratch, so I don't know. I would think about a week or two we should have something ready for release. It should be noted that we are targeting 5.3 as our base requirement.

[edit]Ripnet also gave me admin access to the sourceforge files, so I might release a final update for that so it's up to the current release of InSim Mod's code base, with all of the improvements that morpha and myself have made.[/edit]
Quote from Dygear :It should be noted that we are targeting 5.3 as our base requirement.

*ouch* Are you sure about that one? I think 5.2 is a de facto standard nowadays since a lot of hosters do not use 5.3 yet ...
Quote from avetere :*ouch* Are you sure about that one? I think 5.2 is a de facto standard nowadays since a lot of hosters do not use 5.3 yet ...

If you're talking about web hosting you should note that this code is not intended to run on a web server.
Quote from filur :If you're talking about web hosting you should note that this code is not intended to run on a web server.

Took the words right out of my mouth!
Well I've finally registered with SF as "xigmorph". Not sure if I'll have the time for active development, but I sure am interested

My observations from over 600 days of use are that stability is not an issue, the implementation is bulletproof, it didn't crash a single time and handled well over 160 billion(!) packets. However, packet interpretation and object creation is also one of its major performance killers. I've found that performance could be increased by up to 66% (in my test environment, running 5.3.1 on Win2k3 x86) if a reference instance of every packet class is kept and cloned instead of always creating new instances. This will be particularly useful with incoming packets, since the constructor serves no purpose with those as they need to be unpacked.
As for memory usage, there might be room for improvement, but not much. The script I'm running on DriftWars (which is fairly complex) uses 8M with a total of ~24M allocated, the pyinsim running alongside uses approx. 6M but allocated 32M. I definitely prefer a slightly worse memory footprint over the performance impact of aggressive garbage collection.
Regarding the memory leak issues, I know of no solution that's entirely obscure to the script writer. We do need the object reference in the handler array and it will leak if the object is unset() because it will not be unset until the reference in the handler array is unset. The solution I have in mind is a plugin manager that takes care of all references, but for it to work, script authors must NEVER unset() loaded plugins themselves (instead use $plugin_manager->unload(...)) and must not create references to objects handled by the plugin manager (or if they do, unset them prior to an unload() call). I'm curious what kind of magic plugin system Mark is working on

And finally, I will be focusing on all the concepts I've neglected in the past, namely interfaces, abstract classes, magic methods and type hinting. A balance must be found between a semantically robust syntax and efficient code design. For example, this might be misleading at first, but to me it makes sense semantically:

<?php 
$dispatcher
[ISP_TINY] = array($this'tiny');
?>

Don't be fooled by the syntax, the dispatcher class implements ArrayAccess, this is not an assignment to an element of an array. Well, it is, but $dispatcher is not the array, it's an object behaving like an array. What happens is the dispatcher's offsetSet() will add the received array to its list of handlers for the passed packet type:

<?php 
// Sadly we cannot use type hinting as it would
// differ from the ArrayAccess interface's signature
public function offsetSet($packet_type$handler)
{
    if(
is_int($packet_type) && is_callable($handler))
    {
        
$this->handlers[$packet_type][] = $handler;
    }
    
// Instead of the else, the conditional above could return.
    // We need to discuss coding standards :P
    
else
    {
        
// Exceptions or error reporting? I'm for exceptions.
        
throw new InvalidArgumentException('...');
    }
}
?>

Thoughts?

Edit: Also, regarding the project title, will the "new" project be called PHPLFS?
Another edit: Since I'd like the plugin system to be as robust as possible, it should not allow for inclusion of plugins that cause fatal errors. Anyone got a better solution for checking than:
php -l plugin_file

?
I've regged on SF as dawesdust12
Quote from morpha :Don't be fooled by the syntax, the dispatcher class implements ArrayAccess

This would get a -1 from me, the semantics are incorrect and AFAIK there's no way to have a similar syntax for the reverse (removal) operation.
Quote from filur :This would get a -1 from me, the semantics are incorrect and AFAIK there's no way to have a similar syntax for the reverse (removal) operation.

You are probably right about the removal, at best it would be an inelegant construct like

<?php 
unset($dispatcher[ISP_TINY][array($this'tiny')]);
?>

where both the dispatcher and the dispatcher's internal handler list would have to implement ArrayAccess.

And yes, you're also right about the semantics... I tried to make myself like it, but objectively... Right, let's forget about ArrayAccessing the dispatcher, however, here's a proposal for dispatching packets:

<?php 
$dispatcher
($packet);
?>

possible through the magic method __invoke(). Yea or nay?
Quote from morpha :Edit: Also, regarding the project title, will the "new" project be called PHPLFS?

PHPInSimMod, all one word. Short name is PISM.

Quote from morpha :Another edit: Since I'd like the plugin system to be as robust as possible, it should not allow for inclusion of plugins that cause fatal errors. Anyone got a better solution for checking than:
php -l plugin_file

?


<?php 
    
public function isSafeToInclude($filePath)
    {
        if (!
file_exists($filePath))
            return 
FALSE;

        
system('php -l ' escapeshellcmd($filePath), $status);
        if (
$status)
            return 
FALSE;
        else
            return 
TRUE;
    }
?>

Well ahead of you sir! This is what is going to allow for loading of plugins on the fly, as well as unloading of plugins. Basically, you can keep PHPInSimMod running while programming the new plugins ... just call the load and unload handler for the plugin via the !pism load <plugin> and !pism unload <plugin> That way you can program the without having to close and restart the program all of the time. All of the runtime environment information will remain there!

Modules will also be loaded from startup, but I'm not sure if I'm going to protect them in the same way, while I will allow for loading and unloading at runtime, I expect module programmer to be much more into the fundamentals of the system, and thus no such safety net will be available to them.
Quote from Dygear :Basically, you can keep PHPInSimMod running while programming the new plugins ... just call the load and unload handler for the plugin

How would you handle Fatal error: Cannot redeclare foo() (previously declared in ... ?
Quote from filur :How would you handle Fatal error: Cannot redeclare foo() (previously declared in ... ?

It won't but it will catch parse errors.
Quote from Dygear :PHPInSimMod, all one word. Short name is PISM.

PISM has funny ring to it, but I approve

Quote from Dygear :Well ahead of you sir! This is what is going to allow for loading of plugins on the fly, as well as unloading of plugins. Basically, you can keep your InSim running while programming the new plugins (...)

I expected no less
Few observations/suggestions though
  • Attempting to include a non-existent file only throws a warning, so it's technically still safe to include.
  • exec() instead of system() as the latter will output any output to STDOUT.
  • Since the user input is quasi-sanitized by simply not exec()uting (or in your case, system()ing) if the specified file does not exist, escapeshellcmd() serves no purpose.
  • I'd prefer it if the function provided more feedback, specifically exceptions for ... well, exceptions, like when something other than a string is passed to it (which can happen since it's public... actually why is it public?) or indeed if the file does not exist. Obviously we'll have to make sure all exceptions are caught at some point, I guess we'll log to file or STDOUT / STDERR.
  • Lastly, the premature optimizer in me wants $filePath to be passed by reference since we're not manipulating it
Quote from filur :How would you handle Fatal error: Cannot redeclare foo() (previously declared in ... ?

Good catch! Handling this is entirely possible, but the solution I'm currently thinking of is probably not efficient enough.
Quote from Dygear :Short name is PISM.

Invent an extra 'r' and it would become PRISM, which would be an awesome name.
Quote from DarkTimes :Invent an extra 'r' and it would become PRISM, which would be an awesome name.

True, we should!
PHP [rugged, robust, reliable, rapid, ...?] InSimMod

Also Mark, keep escapeshellcmd(), better yet, replace it with escapeshellarg(). A potential attacker could simply create a file named '-i myfile.php', the file_exists would return true but the exec/system would return a whole lot of info about php and more importantly, always return 0!
Edit: Crap, neither escapeshellcmd nor -arg are helpful in that case, we'll have to

<?php 
str_replace
('-'''$filePath);
?>

Edit2: Make that trim();
Edit3: Strike that, make it

<?php 
ltrim
($filePath'-');
?>

Yup the problem of a word beginning with R is an issue. The word 'race' is the best I can think of...

Php Race InSim Mod
Or if we really wanna rub it in, PHP Revolutionary InSimMod
Perhaps PHP Reincarnated InSimMod, seeing as it will be a joint effort by developers of prior PHP based InSim implementations?

PRISM is nice, we have to find something

Edit: Here's a good one; PHP Retitled [relabled|renamed] InSimMod
Edit2: Man I'm on fire tonight, James Brown, Red Bull, chinese food and everything! Anyway, regarding irrecoverable runtime fatals, the only one I cannot think of a solution for is eval()
-
(DarkTimes) DELETED by DarkTimes
Quote from morpha :PRISM is nice, we have to find something

I must admit, I like it! I wonder what Ripnet thinks.

As for the improvements that you've posted, I tend to agree, but I'm not so sure about the whole attacker thing. It might just me being naive, but who is going to be attacking this from the command line? I would say that if anyone get's CMD access this would be the least of their worry.
Quote from morpha :Edit2: Man I'm on fire tonight, James Brown, Red Bull, chinese food and everything! Anyway, regarding irrecoverable runtime fatals, the only one I cannot think of a solution for is eval()

Are runtime fatals catchable, I forget? How about refactoring the plugin system with the built in reflector class, I'm pretty sure we can test the code that way.
Quote from Dygear :I must admit, I like it! I wonder what Ripnet thinks.

Perhaps something along the lines of: "Hey... my nickname starts with 'r'!"
Quote from Dygear :As for the improvements that you've posted, I tend to agree, but I'm not so sure about the whole attacker thing. It might just me being naive, but who is going to be attacking this from the command line? I would say that if anyone get's CMD access this would be the least of their worry.

They don't have to, since we can't make sure script authors sanitize their user input (or can we? perhaps we can... gotta think about it), any connected player could potentially start an attack of some sort. And yes, I realize this is an incredible long-shot.

Quote from Dygear :Are runtime fatals catchable, I forget? How about refactoring the plugin system with the built in reflector class, I'm pretty sure we can test the code that way.

Sadly no, there are recoverable/catchable fatals but very few of them are.
Quote from morpha :Perhaps something along the lines of: "Hey... my nickname starts with 'r'!"

LMAO!

Quote from morpha :They don't have to, since we can't make sure script authors sanitize their user input (or can we? perhaps we can... gotta think about it), any connected player could potentially start an attack of some sort. And yes, I realize this is an incredible long-shot.

You do it for them, before they get the chance. You can still provide them the raw packet data, and the raw packet object, but pass them sanitized input.


Quote from morpha :Sadly no, there are recoverable/catchable fatals but very few of them are.

Yeah, I knew that ... I wonder if the reflection api will help with this, I'll have to do some digging.
Quote from Dygear :You do it for them, before they get the chance. You can still provide them the raw packet data, and the raw packet object, but pass them sanitized input.

So we will.
Quote from Dygear :Yeah, I knew that ... I wonder if the reflection api will help with this, I'll have to do some digging.

Doubt it, but then it might not actually be a problem. The only context in which eval() is needed (that I can think of anyway) is an ingame command to execute user code. That's stupid and we might do good by disallowing eval() altogether, or if we want to retain a full php feature set, we implement a proxy eval() function that checks whether evaluating the string would cause a fatal.

To sum up what we've got so far, prior to inclusion:
  • Make sure the file exists
  • Make sure the syntax is valid using lint
  • Scan file for function-, class- and interface declarations (and, if any, assignments to global vars that might not be entirely desireable)
  • Replace any eval() call with a call to our proxy
  • Do the same for any files the file-to-be-loaded includes or requires.
One hell of a validation function

Edit: Question is, do we rename duplicate declarations (which would allow execution even if - in file at least - two functions/classes/interfaces share the same name) or do we simple throw an exception and tell the user the plugin contains a redeclaration?

Edit2: Okay, we can do very well, but I don't see a way to have it be 100% bulletproof. One particular case:

<?php 
$a 
= clone $b;
?>

will result in a fatal. We can scan a file for assignments, track all variables ... essentially parse the whole damn file, but if $b is

<?php 
$b 
unserialize(file_get_contents('myNeedlesslyObscureValueThatIsntAClass'));
?>

we're out of luck, unless we really want to parse ALL of it. Would be a damn lot easier if we could just tell PHP to parse but not execute.

Well at least we're considering lots of improbable cases, which is good! Just a shame that there's so many of them that require ridiculous workarounds
Configuration should be handled by .ini files at runtime, and .cfg files.

Ini files will be parsed by parse_ini_file and so should cfg files, from the $lfsdir/addons/PHPInSimMod/config/ dir and load them into the $this->cvars array.

PHP5 - InSim Mod.
(120 posts, started )
FGED GREDG RDFGDR GSFDG